PageRenderTime 41ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/SignalR.Hosting.Self45/Infrastructure/DisconnectHandler.cs

https://github.com/kpmrafeeq/SignalR
C# | 104 lines | 69 code | 12 blank | 23 comment | 9 complexity | 3f1441a0fa1b9cf28a7602accc930d9a MD5 | raw file
Possible License(s): MIT
  1. using SignalR.Hosting.Self.Infrastructure;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Net;
  10. using System.Reflection;
  11. using System.Runtime.InteropServices;
  12. namespace SignalR.Hosting.Self
  13. {
  14. public unsafe class DisconnectHandler
  15. {
  16. private readonly ConcurrentDictionary<ulong, Lazy<CancellationToken>> _connectionCancellationTokens = new ConcurrentDictionary<ulong, Lazy<CancellationToken>>();
  17. private readonly HttpListener _listener;
  18. private CriticalHandle _requestQueueHandle;
  19. /// <summary>
  20. /// Initializes a new instance of <see cref="DisconnectHandler"/>.
  21. /// </summary>
  22. /// <param name="listener">The <see cref="Server"/>'s HttpListener</param>
  23. public DisconnectHandler(HttpListener listener)
  24. {
  25. _listener = listener;
  26. }
  27. /// <summary>
  28. /// Initializes the Request Queue Handler. Meant to be called once the servers <see cref="HttpListener"/> has been started.
  29. /// </summary>
  30. public void Initialize()
  31. {
  32. // HACK: Get the request queue handle so we can register for disconnect
  33. var requestQueueHandleField = typeof(HttpListener).GetField("m_RequestQueueHandle", BindingFlags.Instance | BindingFlags.NonPublic);
  34. if (requestQueueHandleField != null)
  35. {
  36. _requestQueueHandle = (CriticalHandle)requestQueueHandleField.GetValue(_listener);
  37. }
  38. }
  39. /// <summary>
  40. /// Gets the <see cref="CancellationToken"/> associated with the <paramref name="context"/>.
  41. /// If the <see cref="CancellationToken"/> does not exist for the given <paramref name="context"/> then <see cref="CreateToken"/> is called.
  42. /// </summary>
  43. /// <param name="context">The context for the current connection.</param>
  44. /// <returns>A cancellation token that is registered for disconnect for the current connection.</returns>
  45. public CancellationToken GetDisconnectToken(HttpListenerContext context)
  46. {
  47. FieldInfo connectionIdField = typeof(HttpListenerRequest).GetField("m_ConnectionId", BindingFlags.Instance | BindingFlags.NonPublic);
  48. ulong connectionId = (ulong)connectionIdField.GetValue(context.Request);
  49. if (connectionIdField != null && _requestQueueHandle != null)
  50. {
  51. return _connectionCancellationTokens.GetOrAdd(connectionId, key => new Lazy<CancellationToken>(() => CreateToken(key))).Value;
  52. }
  53. else
  54. {
  55. Debug.WriteLine("Server: Unable to resolve requestQueue handle. Disconnect notifications will be ignored");
  56. return CancellationToken.None;
  57. }
  58. }
  59. /// <summary>
  60. /// Creates a <see cref="CancellationTokenSource"/> for the given <paramref name="connectionId"/> and registers it for disconnect.
  61. /// </summary>
  62. /// <param name="connectionId">The connection id.</param>
  63. /// <returns>A <see cref="CancellationTokenSource"/> that is registered for disconnect for the connection associated with the <paramref name="connectionId"/>.</returns>
  64. public CancellationToken CreateToken(ulong connectionId)
  65. {
  66. Debug.WriteLine("Server: Registering connection for disconnect for connection ID: " + connectionId);
  67. // Create a nativeOverlapped callback so we can register for disconnect callback
  68. var overlapped = new Overlapped();
  69. var cts = new CancellationTokenSource();
  70. var nativeOverlapped = overlapped.UnsafePack((errorCode, numBytes, pOVERLAP) =>
  71. {
  72. Debug.WriteLine("Server: http.sys disconnect callback fired for connection ID: " + connectionId);
  73. // Free the overlapped
  74. Overlapped.Free(pOVERLAP);
  75. // Pull the token out of the list and Cancel it.
  76. Lazy<CancellationToken> token;
  77. _connectionCancellationTokens.TryRemove(connectionId, out token);
  78. cts.Cancel();
  79. },
  80. null);
  81. uint hr = NativeMethods.HttpWaitForDisconnect(_requestQueueHandle, connectionId, nativeOverlapped);
  82. if (hr != NativeMethods.HttpErrors.ERROR_IO_PENDING &&
  83. hr != NativeMethods.HttpErrors.NO_ERROR)
  84. {
  85. // We got an unknown result so throw
  86. Debug.WriteLine("Unable to register disconnect callback");
  87. return CancellationToken.None;
  88. }
  89. return cts.Token;
  90. }
  91. }
  92. }