diff --git a/src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs b/src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs index 91e4a570..d8bb5160 100644 --- a/src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs +++ b/src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs @@ -16,6 +16,9 @@ namespace Microsoft.Owin.Host.HttpListener internal class DisconnectHandler { + // Win8 minimum + private static bool SkipIOCPCallbackOnSuccess = Environment.OSVersion.Version >= new Version(6, 2); + private readonly ConcurrentDictionary _connectionCancellationTokens; private readonly System.Net.HttpListener _listener; private readonly CriticalHandle _requestQueueHandle; @@ -114,6 +117,15 @@ namespace Microsoft.Owin.Host.HttpListener cts.Cancel(); } + if (hr == NativeMethods.HttpErrors.NO_ERROR && SkipIOCPCallbackOnSuccess) + { + // IO operation completed synchronously - callback won't be called to signal completion + Overlapped.Free(nativeOverlapped); + ConnectionCancellation cancellation; + _connectionCancellationTokens.TryRemove(connectionId, out cancellation); + cts.Cancel(); + } + return returnToken; } diff --git a/tests/Microsoft.Owin.Host.HttpListener.Tests/OwinHttpListenerTests.cs b/tests/Microsoft.Owin.Host.HttpListener.Tests/OwinHttpListenerTests.cs index 7775a354..926b13f6 100644 --- a/tests/Microsoft.Owin.Host.HttpListener.Tests/OwinHttpListenerTests.cs +++ b/tests/Microsoft.Owin.Host.HttpListener.Tests/OwinHttpListenerTests.cs @@ -342,6 +342,46 @@ namespace Microsoft.Owin.Host.HttpListener.Tests } } + [Fact] + public void Disconnect_ClientDisconnects_Before_CancellationToken_Created() + { + var requestReceived = new ManualResetEvent(false); + var requestCanceled = new ManualResetEvent(false); + + var clientDisposed = new ManualResetEvent(false); + + OwinHttpListener listener = CreateServer( + env => + { + requestReceived.Set(); + + // lets wait for client to be gone + Assert.True(clientDisposed.WaitOne(1000)); + + // the most important part is not to observe CancellationToken before client disconnects + + GetCallCancelled(env).Register(() => requestCanceled.Set()); + return Task.FromResult(0); + }, + HttpServerAddress); + + using (listener) + { + using (var client = new HttpClient()) + { + var requestTask = client.GetAsync(HttpClientAddress); + Assert.True(requestReceived.WaitOne(1000)); + client.CancelPendingRequests(); + + Assert.Throws(() => requestTask.Result); + } + + clientDisposed.Set(); + + Assert.True(requestCanceled.WaitOne(1000)); + } + } + private static CancellationToken GetCallCancelled(IDictionary env) { return env.Get("owin.CallCancelled");