Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 04c606d

Browse files
BrennanConroynatemcmaster
authored andcommitted
LongPolling: Setting connection to inactive while there is still an active poll (#2769)
1 parent ef533ca commit 04c606d

2 files changed

Lines changed: 47 additions & 48 deletions

File tree

src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public HttpConnectionContext(string id, IDuplexPipe transport, IDuplexPipe appli
8888

8989
public Task TransportTask { get; set; }
9090

91+
public Task PreviousPollTask { get; set; } = Task.CompletedTask;
92+
9193
public Task ApplicationTask { get; set; }
9294

9395
public DateTime LastSeenUtc { get; set; }
@@ -180,6 +182,8 @@ public async Task DisposeAsync(bool closeGracefully = false)
180182
{
181183
Task disposeTask;
182184

185+
Cancellation?.Dispose();
186+
183187
await StateLock.WaitAsync();
184188
try
185189
{

src/Microsoft.AspNetCore.Http.Connections/Internal/HttpConnectionDispatcher.cs

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
188188
return;
189189
}
190190

191+
// Create a new Tcs every poll to keep track of the poll finishing, so we can properly wait on previous polls
192+
var currentRequestTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
193+
191194
await connection.StateLock.WaitAsync();
192195
try
193196
{
@@ -205,17 +208,17 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
205208
{
206209
var existing = connection.GetHttpContext();
207210
Log.ConnectionAlreadyActive(_logger, connection.ConnectionId, existing.TraceIdentifier);
211+
}
208212

209-
using (connection.Cancellation)
210-
{
211-
// Cancel the previous request
212-
connection.Cancellation?.Cancel();
213+
using (connection.Cancellation)
214+
{
215+
// Cancel the previous request
216+
connection.Cancellation?.Cancel();
213217

214-
// Wait for the previous request to drain
215-
await connection.TransportTask;
218+
// Wait for the previous request to drain
219+
await connection.PreviousPollTask;
216220

217-
Log.PollCanceled(_logger, connection.ConnectionId, existing.TraceIdentifier);
218-
}
221+
connection.PreviousPollTask = currentRequestTcs.Task;
219222
}
220223

221224
// Mark the connection as active
@@ -267,57 +270,49 @@ private async Task ExecuteAsync(HttpContext context, ConnectionDelegate connecti
267270

268271
var resultTask = await Task.WhenAny(connection.ApplicationTask, connection.TransportTask);
269272

270-
var pollAgain = true;
271-
272-
// If the application ended before the transport task then we potentially need to end the connection
273-
if (resultTask == connection.ApplicationTask)
273+
try
274274
{
275-
// Complete the transport (notifying it of the application error if there is one)
276-
connection.Transport.Output.Complete(connection.ApplicationTask.Exception);
275+
var pollAgain = true;
277276

278-
// Wait for the transport to run
279-
await connection.TransportTask;
280-
281-
// If the status code is a 204 it means the connection is done
282-
if (context.Response.StatusCode == StatusCodes.Status204NoContent)
277+
// If the application ended before the transport task then we potentially need to end the connection
278+
if (resultTask == connection.ApplicationTask)
283279
{
284-
// We should be able to safely dispose because there's no more data being written
285-
// We don't need to wait for close here since we've already waited for both sides
286-
await _manager.DisposeAndRemoveAsync(connection, closeGracefully: false);
280+
// Complete the transport (notifying it of the application error if there is one)
281+
connection.Transport.Output.Complete(connection.ApplicationTask.Exception);
287282

288-
// Don't poll again if we've removed the connection completely
289-
pollAgain = false;
290-
}
291-
}
292-
else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
293-
{
294-
// Don't poll if the transport task was canceled
295-
pollAgain = false;
296-
}
283+
// Wait for the transport to run
284+
await connection.TransportTask;
297285

298-
if (pollAgain)
299-
{
300-
// Otherwise, we update the state to inactive again and wait for the next poll
301-
await connection.StateLock.WaitAsync();
302-
try
303-
{
304-
if (connection.Status == HttpConnectionStatus.Active)
286+
// If the status code is a 204 it means the connection is done
287+
if (context.Response.StatusCode == StatusCodes.Status204NoContent)
305288
{
306-
// Mark the connection as inactive
307-
connection.LastSeenUtc = DateTime.UtcNow;
308-
309-
connection.Status = HttpConnectionStatus.Inactive;
289+
// We should be able to safely dispose because there's no more data being written
290+
// We don't need to wait for close here since we've already waited for both sides
291+
await _manager.DisposeAndRemoveAsync(connection, closeGracefully: false);
310292

311-
// Dispose the cancellation token
312-
connection.Cancellation?.Dispose();
313-
314-
connection.Cancellation = null;
293+
// Don't poll again if we've removed the connection completely
294+
pollAgain = false;
315295
}
316296
}
317-
finally
297+
else if (context.Response.StatusCode == StatusCodes.Status204NoContent)
318298
{
319-
connection.StateLock.Release();
299+
// Don't poll if the transport task was canceled
300+
pollAgain = false;
320301
}
302+
303+
if (pollAgain)
304+
{
305+
// Mark the connection as inactive
306+
connection.LastSeenUtc = DateTime.UtcNow;
307+
308+
connection.Status = HttpConnectionStatus.Inactive;
309+
}
310+
}
311+
finally
312+
{
313+
// Artificial task queue
314+
// This will cause incoming polls to wait until the previous poll has finished updating internal state info
315+
currentRequestTcs.TrySetResult(null);
321316
}
322317
}
323318
}

0 commit comments

Comments
 (0)