Handle posting to the libuv thread after StopAsync (#2388)

- Check if the post handle is disposed and noop if it is.
We also catch an ObjectDisposedException because it's an inherent race condition.
This commit is contained in:
David Fowler 2018-03-13 19:37:39 -07:00 committed by GitHub
parent f6108928d8
commit 572627e88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 3 deletions

View File

@ -177,11 +177,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
public void Post<T>(Action<T> callback, T state)
{
// Handle is closed to don't bother scheduling anything
if (_post.IsClosed)
{
return;
}
var work = new Work
{
CallbackAdapter = CallbackAdapter<T>.PostCallbackAdapter,
Callback = callback,
//TODO: This boxes
// TODO: This boxes
State = state
};
@ -189,7 +195,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
{
_workAdding.Enqueue(work);
}
_post.Send();
try
{
_post.Send();
}
catch (ObjectDisposedException)
{
// There's an inherent race here where we're in the middle of shutdown
}
}
private void Post(Action<LibuvThread> callback)
@ -199,6 +213,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
public Task PostAsync<T>(Action<T> callback, T state)
{
// Handle is closed to don't bother scheduling anything
if (_post.IsClosed)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var work = new Work
{
@ -212,7 +232,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
{
_workAdding.Enqueue(work);
}
_post.Send();
try
{
_post.Send();
}
catch (ObjectDisposedException)
{
// There's an inherent race here where we're in the middle of shutdown
}
return tcs.Task;
}

View File

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
{
public class LibuvThreadTests
{
[Fact]
public async Task LibuvThreadDoesNotThrowIfPostingWorkAfterDispose()
{
var mockConnectionHandler = new MockConnectionHandler();
var mockLibuv = new MockLibuv();
var transportContext = new TestLibuvTransportContext() { ConnectionHandler = mockConnectionHandler };
var transport = new LibuvTransport(mockLibuv, transportContext, null);
var thread = new LibuvThread(transport);
var ranOne = false;
var ranTwo = false;
var ranThree = false;
var ranFour = false;
await thread.StartAsync();
await thread.PostAsync<object>(_ =>
{
ranOne = true;
},
null);
Assert.Equal(1, mockLibuv.PostCount);
// Shutdown the libuv thread
await thread.StopAsync(TimeSpan.FromSeconds(5));
Assert.Equal(2, mockLibuv.PostCount);
var task = thread.PostAsync<object>(_ =>
{
ranTwo = true;
},
null);
Assert.Equal(2, mockLibuv.PostCount);
thread.Post<object>(_ =>
{
ranThree = true;
},
null);
Assert.Equal(2, mockLibuv.PostCount);
thread.Schedule(_ =>
{
ranFour = true;
},
(object)null);
Assert.Equal(2, mockLibuv.PostCount);
Assert.True(task.IsCompleted);
Assert.True(ranOne);
Assert.False(ranTwo);
Assert.False(ranThree);
Assert.False(ranFour);
}
}
}