SignalR 326 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. commit 04c606d55f8dc62c2965223b261cb2c4392c4ceb
  2. Author: BrennanConroy <[email protected]>
  3. Date: Tue Aug 28 09:44:50 2018 -0700
  4. LongPolling: Setting connection to inactive while there is still an active poll (#2769)
  5. diff --git a/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs b/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs
  6. index 0ea9f1c394e..045e821ee10 100644
  7. --- a/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs
  8. +++ b/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs
  9. @@ -88,6 +88,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
  10. public Task TransportTask { get; set; }
  11. + public Task PreviousPollTask { get; set; } = Task.CompletedTask;
  12. +
  13. public Task ApplicationTask { get; set; }
  14. public DateTime LastSeenUtc { get; set; }
  15. @@ -180,6 +182,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
  16. {
  17. Task disposeTask;
  18. + Cancellation?.Dispose();
  19. +
  20. await StateLock.WaitAsync();
  21. try
  22. {
  23. diff --git a/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs
  24. index 4d4bd93c1d4..6662dd72c0e 100644
  25. --- a/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs
  26. +++ b/src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs
  27. @@ -188,6 +188,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
  28. return;
  29. }
  30. + // Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
  31. + var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
  32. +
  33. await connection.StateLock.WaitAsync();
  34. try
  35. {
  36. @@ -205,17 +208,17 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
  37. {
  38. var existing = connection.GetHttpContext();
  39. Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);
  40. + }
  41. - using (connection.Cancellation)
  42. - {
  43. - // Cancel the previous request
  44. - connection.Cancellation?.Cancel();
  45. + using (connection.Cancellation)
  46. + {
  47. + // Cancel the previous request
  48. + connection.Cancellation?.Cancel();
  49. - // Wait for the previous request to drain
  50. - await connection.TransportTask;
  51. + // Wait for the previous request to drain
  52. + await connection.PreviousPollTask;
  53. - Log.PollCanceled(_logger, connection.ConnectionId, existing.TraceIdentifier);
  54. - }
  55. + connection.PreviousPollTask = currentRequestTcs.Task;
  56. }
  57. // Mark the connection as active
  58. @@ -267,57 +270,49 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
  59. var resultTask = await Task.WhenAny(connection.ApplicationTask, connection.TransportTask);
  60. - var pollAgain = true;
  61. -
  62. - // If the application ended before the transport task then we potentially need to end the connection
  63. - if (resultTask == connection.ApplicationTask)
  64. + try
  65. {
  66. - // Complete the transport (notifying it of the application error if there is one)
  67. - connection.Transport.Output.Complete(connection.ApplicationTask.Exception);
  68. + var pollAgain = true;
  69. - // Wait for the transport to run
  70. - await connection.TransportTask;
  71. -
  72. - // If the status code is a 204 it means the connection is done
  73. - if (context.Response.StatusCode == StatusCodes.Status204NoContent)
  74. + // If the application ended before the transport task then we potentially need to end the connection
  75. + if (resultTask == connection.ApplicationTask)
  76. {
  77. - // We should be able to safely dispose because there's no more data being written
  78. - // We don't need to wait for close here since we've already waited for both sides
  79. - await _manager.DisposeAndRemoveAsync(connection, closeGracefully: false);
  80. + // Complete the transport (notifying it of the application error if there is one)
  81. + connection.Transport.Output.Complete(connection.ApplicationTask.Exception);
  82. - // Don't poll again if we've removed the connection completely
  83. - pollAgain = false;
  84. - }
  85. - }
  86. - else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
  87. - {
  88. - // Don't poll if the transport task was canceled
  89. - pollAgain = false;
  90. - }
  91. + // Wait for the transport to run
  92. + await connection.TransportTask;
  93. - if (pollAgain)
  94. - {
  95. - // Otherwise, we update the state to inactive again and wait for the next poll
  96. - await connection.StateLock.WaitAsync();
  97. - try
  98. - {
  99. - if (connection.Status == HttpConnectionStatus.Active)
  100. + // If the status code is a 204 it means the connection is done
  101. + if (context.Response.StatusCode == StatusCodes.Status204NoContent)
  102. {
  103. - // Mark the connection as inactive
  104. - connection.LastSeenUtc = DateTime.UtcNow;
  105. -
  106. - connection.Status = HttpConnectionStatus.Inactive;
  107. + // We should be able to safely dispose because there's no more data being written
  108. + // We don't need to wait for close here since we've already waited for both sides
  109. + await _manager.DisposeAndRemoveAsync(connection, closeGracefully: false);
  110. - // Dispose the cancellation token
  111. - connection.Cancellation?.Dispose();
  112. -
  113. - connection.Cancellation = null;
  114. + // Don't poll again if we've removed the connection completely
  115. + pollAgain = false;
  116. }
  117. }
  118. - finally
  119. + else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
  120. {
  121. - connection.StateLock.Release();
  122. + // Don't poll if the transport task was canceled
  123. + pollAgain = false;
  124. }
  125. +
  126. + if (pollAgain)
  127. + {
  128. + // Mark the connection as inactive
  129. + connection.LastSeenUtc = DateTime.UtcNow;
  130. +
  131. + connection.Status = HttpConnectionStatus.Inactive;
  132. + }
  133. + }
  134. + finally
  135. + {
  136. + // Artificial task queue
  137. + // This will cause incoming polls to wait until the previous poll has finished updating internal state info
  138. + currentRequestTcs.TrySetResult(null);
  139. }
  140. }
  141. }