Skip to content

Commit

Permalink
Handle early disconnects #141
Browse files Browse the repository at this point in the history
  • Loading branch information
Tratcher committed Jan 30, 2018
1 parent 0156279 commit 5d6e25b
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/Microsoft.Owin.Host.HttpListener/DisconnectHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ulong, ConnectionCancellation> _connectionCancellationTokens;
private readonly System.Net.HttpListener _listener;
private readonly CriticalHandle _requestQueueHandle;
Expand Down Expand Up @@ -114,6 +117,15 @@ private unsafe CancellationToken CreateToken(ulong connectionId)
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,46 @@ public void Disconnect_ClientDisconnects_EventFires()
}
}

[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<AggregateException>(() => requestTask.Result);
}

clientDisposed.Set();

Assert.True(requestCanceled.WaitOne(1000));
}
}

private static CancellationToken GetCallCancelled(IDictionary<string, object> env)
{
return env.Get<CancellationToken>("owin.CallCancelled");
Expand Down

0 comments on commit 5d6e25b

Please sign in to comment.