Skip to content

Commit 5d6e25b

Browse files
committed
Handle early disconnects #141
1 parent 0156279 commit 5d6e25b

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ namespace Microsoft.Owin.Host.HttpListener
1616

1717
internal class DisconnectHandler
1818
{
19+
// Win8 minimum
20+
private static bool SkipIOCPCallbackOnSuccess = Environment.OSVersion.Version >= new Version(6, 2);
21+
1922
private readonly ConcurrentDictionary<ulong, ConnectionCancellation> _connectionCancellationTokens;
2023
private readonly System.Net.HttpListener _listener;
2124
private readonly CriticalHandle _requestQueueHandle;
@@ -114,6 +117,15 @@ private unsafe CancellationToken CreateToken(ulong connectionId)
114117
cts.Cancel();
115118
}
116119

120+
if (hr == NativeMethods.HttpErrors.NO_ERROR && SkipIOCPCallbackOnSuccess)
121+
{
122+
// IO operation completed synchronously - callback won't be called to signal completion
123+
Overlapped.Free(nativeOverlapped);
124+
ConnectionCancellation cancellation;
125+
_connectionCancellationTokens.TryRemove(connectionId, out cancellation);
126+
cts.Cancel();
127+
}
128+
117129
return returnToken;
118130
}
119131

tests/Microsoft.Owin.Host.HttpListener.Tests/OwinHttpListenerTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,46 @@ public void Disconnect_ClientDisconnects_EventFires()
342342
}
343343
}
344344

345+
[Fact]
346+
public void Disconnect_ClientDisconnects_Before_CancellationToken_Created()
347+
{
348+
var requestReceived = new ManualResetEvent(false);
349+
var requestCanceled = new ManualResetEvent(false);
350+
351+
var clientDisposed = new ManualResetEvent(false);
352+
353+
OwinHttpListener listener = CreateServer(
354+
env =>
355+
{
356+
requestReceived.Set();
357+
358+
// lets wait for client to be gone
359+
Assert.True(clientDisposed.WaitOne(1000));
360+
361+
// the most important part is not to observe CancellationToken before client disconnects
362+
363+
GetCallCancelled(env).Register(() => requestCanceled.Set());
364+
return Task.FromResult(0);
365+
},
366+
HttpServerAddress);
367+
368+
using (listener)
369+
{
370+
using (var client = new HttpClient())
371+
{
372+
var requestTask = client.GetAsync(HttpClientAddress);
373+
Assert.True(requestReceived.WaitOne(1000));
374+
client.CancelPendingRequests();
375+
376+
Assert.Throws<AggregateException>(() => requestTask.Result);
377+
}
378+
379+
clientDisposed.Set();
380+
381+
Assert.True(requestCanceled.WaitOne(1000));
382+
}
383+
}
384+
345385
private static CancellationToken GetCallCancelled(IDictionary<string, object> env)
346386
{
347387
return env.Get<CancellationToken>("owin.CallCancelled");

0 commit comments

Comments
 (0)