Skip to content

Commit

Permalink
Throws PendingTaskInterruptedException on reset to invalidate suspend…
Browse files Browse the repository at this point in the history
…ed callers
  • Loading branch information
sakno committed Nov 16, 2023
1 parent 4c8f642 commit ae22cef
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 14 deletions.
9 changes: 9 additions & 0 deletions src/DotNext.Tests/Threading/AsyncCountdownEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ public static void CheckStateTransitions(long initCount, long increms, bool take
True(ev.Reset());
Equal(ev.InitialCount, ev.CurrentCount);
}

[Fact]
public static async Task AbortSuspendedCallers()
{
using var countdown = new AsyncCountdownEvent(4);
var task = countdown.WaitAsync().AsTask();
countdown.Reset();
await ThrowsAsync<PendingTaskInterruptedException>(Func.Constant(task));
}
}
39 changes: 25 additions & 14 deletions src/DotNext.Threading/Threading/AsyncCountdownEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,33 @@ public void AddCount(long signalCount)
/// <summary>
/// Resets the <see cref="CurrentCount"/> to the value of <see cref="InitialCount"/>.
/// </summary>
/// <remarks>
/// All suspended callers will be resumed with <see cref="PendingTaskInterruptedException"/> exception.
/// </remarks>
/// <returns><see langword="true"/>, if state of this object changed from signaled to non-signaled state; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
public bool Reset()
{
ThrowIfDisposed();
return ResetCore(1L);
bool result;
LinkedValueTaskCompletionSource<bool>? suspendedCallers;
lock (SyncRoot)
{
result = manager.Current is 0L;
manager.Current = manager.Initial;
suspendedCallers = DetachWaitQueue()?.SetException(new PendingTaskInterruptedException(), out _);
}

suspendedCallers?.Unwind();
return result;
}

/// <summary>
/// Resets the <see cref="InitialCount"/> property to a specified value.
/// </summary>
/// <remarks>
/// All suspended callers will be resumed with <see cref="PendingTaskInterruptedException"/> exception.
/// </remarks>
/// <param name="count">The number of signals required to set this event.</param>
/// <returns><see langword="true"/>, if state of this object changed from signaled to non-signaled state; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
Expand All @@ -207,21 +223,16 @@ public bool Reset(long count)
throw new ArgumentOutOfRangeException(nameof(count));

ThrowIfDisposed();
return ResetCore(count);
}

private bool ResetCore(long count)
{
Debug.Assert(count >= 0L);

bool result;
Monitor.Enter(SyncRoot);

// the following code never throws, avoid try-finally overhead
result = manager.Current is 0L;
manager.Current = manager.Initial = count;
LinkedValueTaskCompletionSource<bool>? suspendedCallers;
lock (SyncRoot)
{
result = manager.Current is 0L;
manager.Current = manager.Initial = count;
suspendedCallers = DetachWaitQueue()?.SetException(new PendingTaskInterruptedException(), out _);
}

Monitor.Exit(SyncRoot);
suspendedCallers?.Unwind();
return result;
}

Expand Down

0 comments on commit ae22cef

Please sign in to comment.