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

Commit

Permalink
Fix deadlock in SocketOutput (#1304).
Browse files Browse the repository at this point in the history
  • Loading branch information
Cesar Blum Silveira committed Jan 25, 2017
1 parent ba3976a commit 9051bbf
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public Task WriteAsync(
_tasksPending.Enqueue(new WaitingTask()
{
CancellationToken = cancellationToken,
CancellationRegistration = cancellationToken.Register(_connectionCancellation, this),
CancellationRegistration = cancellationToken.SafeRegister(_connectionCancellation, this),
BytesToWrite = buffer.Count,
CompletionSource = tcs
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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;

namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{
internal static class CancellationTokenExtensions
{
public static IDisposable SafeRegister(this CancellationToken cancellationToken, Action<object> callback, object state)
{
var callbackWrapper = new CancellationCallbackWrapper(callback, state);
var registration = cancellationToken.Register(s => InvokeCallback(s), callbackWrapper);
var disposeCancellationState = new DisposeCancellationState(callbackWrapper, registration);

return new DisposableAction(s => Dispose(s), disposeCancellationState);
}

private static void InvokeCallback(object state)
{
((CancellationCallbackWrapper)state).TryInvoke();
}

private static void Dispose(object state)
{
((DisposeCancellationState)state).TryDispose();
}

private class DisposeCancellationState
{
private readonly CancellationCallbackWrapper _callbackWrapper;
private readonly CancellationTokenRegistration _registration;

public DisposeCancellationState(CancellationCallbackWrapper callbackWrapper, CancellationTokenRegistration registration)
{
_callbackWrapper = callbackWrapper;
_registration = registration;
}

public void TryDispose()
{
if (_callbackWrapper.TrySetInvoked())
{
_registration.Dispose();
}
}
}

private class CancellationCallbackWrapper
{
private readonly Action<object> _callback;
private readonly object _state;
private int _callbackInvoked;

public CancellationCallbackWrapper(Action<object> callback, object state)
{
_callback = callback;
_state = state;
}

public bool TrySetInvoked()
{
return Interlocked.Exchange(ref _callbackInvoked, 1) == 0;
}

public void TryInvoke()
{
if (TrySetInvoked())
{
_callback(_state);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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;

namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{
internal class DisposableAction : IDisposable
{
public static readonly DisposableAction Empty = new DisposableAction(() => { });

private Action<object> _action;
private readonly object _state;

public DisposableAction(Action action)
: this(state => ((Action)state).Invoke(), state: action)
{
}

public DisposableAction(Action<object> action, object state)
{
_action = action;
_state = state;
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Interlocked.Exchange(ref _action, (state) => { }).Invoke(_state);
}
}

public void Dispose()
{
Dispose(true);
}
}
}

0 comments on commit 9051bbf

Please sign in to comment.