From 60323839c2342543137ddb44e20c491338d19a0f Mon Sep 17 00:00:00 2001 From: sakno Date: Tue, 15 Oct 2024 22:30:19 +0300 Subject: [PATCH] Fixed auto reset behavior --- .../Threading/AsyncExclusiveLockTests.cs | 17 +++++---- .../Threading/AsyncReaderWriterLockTests.cs | 34 ++++++++---------- .../Threading/AsyncResetEventTests.cs | 36 ++++++++++++++++--- .../Threading/AsyncAutoResetEvent.cs | 32 ++++++++++++++--- .../Threading/AsyncManualResetEvent.cs | 13 ++++++- .../Threading/QueuedSynchronizer.cs | 12 ------- 6 files changed, 95 insertions(+), 49 deletions(-) diff --git a/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs b/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs index 2f7bb885e..246adb4a4 100644 --- a/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs +++ b/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs @@ -195,25 +195,24 @@ public static async Task MixedLock() await using var l = new AsyncExclusiveLock(); True(await l.TryAcquireAsync(DefaultTimeout)); - var t = new Thread(() => True(l.TryAcquire(DefaultTimeout))) { IsBackground = true }; - t.Start(); + var t = Task.Factory.StartNew(() => True(l.TryAcquire(DefaultTimeout)), TaskCreationOptions.LongRunning); l.Release(); - - True(t.Join(DefaultTimeout)); + + await t; False(l.TryAcquire()); l.Release(); } [Fact] - public static void DisposedWhenSynchronousLockAcquired() + public static async Task DisposedWhenSynchronousLockAcquired() { var l = new AsyncExclusiveLock(); True(l.TryAcquire()); - var t = new Thread(() => Throws(() => l.TryAcquire(DefaultTimeout))) { IsBackground = true }; - t.Start(); - + var t = Task.Factory.StartNew(() => Throws(() => l.TryAcquire(DefaultTimeout)), + TaskCreationOptions.LongRunning); + l.Dispose(); - True(t.Join(DefaultTimeout)); + await t; } } \ No newline at end of file diff --git a/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs b/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs index 6fa4b857b..4ff8569b1 100644 --- a/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs +++ b/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs @@ -198,46 +198,45 @@ public static async Task LockStealing2() } [Fact] - public static void DisposedWhenSynchronousReadLockAcquired() + public static async Task DisposedWhenSynchronousReadLockAcquired() { var l = new AsyncReaderWriterLock(); True(l.TryEnterReadLock()); - var t = new Thread(() => Throws(() => l.TryEnterWriteLock(DefaultTimeout))) { IsBackground = true }; - t.Start(); + var t = Task.Factory.StartNew(() => Throws(() => l.TryEnterWriteLock(DefaultTimeout)), + TaskCreationOptions.LongRunning); l.Dispose(); - True(t.Join(DefaultTimeout)); + await t; } [Fact] - public static void DisposedWhenSynchronousWriteLockAcquired() + public static async Task DisposedWhenSynchronousWriteLockAcquired() { var l = new AsyncReaderWriterLock(); True(l.TryEnterWriteLock()); - var t = new Thread(() => Throws(() => l.TryEnterReadLock(DefaultTimeout))) { IsBackground = true }; - t.Start(); + var t = Task.Factory.StartNew(() => Throws(() => l.TryEnterReadLock(DefaultTimeout)), + TaskCreationOptions.LongRunning); l.Dispose(); - True(t.Join(DefaultTimeout)); + await t; } [Fact] - public static void AcquireReadWriteLockSynchronously() + public static async Task AcquireReadWriteLockSynchronously() { using var l = new AsyncReaderWriterLock(); True(l.TryEnterReadLock(DefaultTimeout)); True(l.TryEnterReadLock(DefaultTimeout)); Equal(2L, l.CurrentReadCount); - var t = new Thread(() => True(l.TryEnterWriteLock(DefaultTimeout))) { IsBackground = true }; - t.Start(); + var t = Task.Factory.StartNew(() => True(l.TryEnterWriteLock(DefaultTimeout)), TaskCreationOptions.LongRunning); l.Release(); l.Release(); - True(t.Join(DefaultTimeout)); + await t; True(l.IsWriteLockHeld); l.Release(); @@ -245,19 +244,16 @@ public static void AcquireReadWriteLockSynchronously() } [Fact] - public static void ResumeMultipleReadersSynchronously() + public static async Task ResumeMultipleReadersSynchronously() { using var l = new AsyncReaderWriterLock(); True(l.TryEnterWriteLock()); - var t1 = new Thread(TryEnterReadLock) { IsBackground = true }; - var t2 = new Thread(TryEnterReadLock) { IsBackground = true }; - t1.Start(); - t2.Start(); + var t1 = Task.Factory.StartNew(TryEnterReadLock, TaskCreationOptions.LongRunning); + var t2 = Task.Factory.StartNew(TryEnterReadLock, TaskCreationOptions.LongRunning); l.Release(); - True(t1.Join(DefaultTimeout)); - True(t2.Join(DefaultTimeout)); + await Task.WhenAll(t1, t2); Equal(2L, l.CurrentReadCount); diff --git a/src/DotNext.Tests/Threading/AsyncResetEventTests.cs b/src/DotNext.Tests/Threading/AsyncResetEventTests.cs index 09c82ceb0..7a324c002 100644 --- a/src/DotNext.Tests/Threading/AsyncResetEventTests.cs +++ b/src/DotNext.Tests/Threading/AsyncResetEventTests.cs @@ -113,17 +113,16 @@ public static async Task RegressionIssue82() [Theory] [MemberData(nameof(GetResetEvents))] - public static void ManualResetEventSynchronousCompletion(IAsyncResetEvent resetEvent) + public static async Task ManualResetEventSynchronousCompletion(IAsyncResetEvent resetEvent) { using (resetEvent) { False(resetEvent.IsSet); - var t = new Thread(() => True(resetEvent.Wait(DefaultTimeout))) { IsBackground = true }; - t.Start(); + var t = Task.Factory.StartNew(() => True(resetEvent.Wait(DefaultTimeout)), TaskCreationOptions.LongRunning); True(resetEvent.Signal()); - True(t.Join(DefaultTimeout)); + await t; Equal(resetEvent.ResetMode is EventResetMode.ManualReset, resetEvent.IsSet); } } @@ -138,4 +137,33 @@ public static void AlreadySignaledEvents(IAsyncResetEvent resetEvent) True(resetEvent.Wait(DefaultTimeout)); } } + + [Fact] + public static async Task AutoResetOnSyncWait() + { + using var are = new AsyncAutoResetEvent(false); + var t = Task.Factory.StartNew(() => True(are.Wait(DefaultTimeout)), TaskCreationOptions.LongRunning); + True(are.Set()); + + await t; + False(are.IsSet); + } + + [Fact] + public static async Task ResumeSuspendedCallersSequentially() + { + using var are = new AsyncAutoResetEvent(false); + var t1 = Task.Factory.StartNew(Wait, TaskCreationOptions.LongRunning); + var t2 = Task.Factory.StartNew(Wait, TaskCreationOptions.LongRunning); + + True(are.Set()); + + await Task.WhenAny(t1, t2); + True(t1.IsCompleted ^ t2.IsCompleted); + + True(are.Set()); + await Task.WhenAll(t1, t2); + + void Wait() => True(are.Wait(DefaultTimeout)); + } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs b/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs index 3384a8b8b..8867c7ca2 100644 --- a/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs +++ b/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs @@ -86,7 +86,7 @@ public bool Reset() } /// - /// Sets the state of the event to signaled, allowing one or more awaiters to proceed. + /// Sets the state of the event to signaled, resuming the suspended caller. /// /// if the operation succeeds; otherwise, . /// The current instance has already been disposed. @@ -119,7 +119,7 @@ public bool Set() } } - Monitor.PulseAll(SyncRoot); + Monitor.Pulse(SyncRoot); } } @@ -154,7 +154,7 @@ public ValueTask WaitAsync(TimeSpan timeout, CancellationToken token = def /// The operation has been canceled. public ValueTask WaitAsync(CancellationToken token = default) => AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token)); - + /// /// Blocks the current thread until this event is set. /// @@ -166,6 +166,30 @@ public ValueTask WaitAsync(CancellationToken token = default) public bool Wait(TimeSpan timeout) { ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this); - return Wait(new(timeout), ref manager); + return Wait(new Timeout(timeout)); + } + + [UnsupportedOSPlatform("browser")] + private bool Wait(Timeout timeout) + { + bool result; + lock (SyncRoot) + { + if (TryAcquire(ref manager)) + { + result = true; + } + else if (timeout.TryGetRemainingTime(out var remainingTime) && Monitor.Wait(SyncRoot, remainingTime)) + { + result = true; + manager.Value = false; + } + else + { + result = false; + } + } + + return result; } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs b/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs index 0f54c9b37..2de454feb 100644 --- a/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs +++ b/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs @@ -167,6 +167,17 @@ public ValueTask WaitAsync(CancellationToken token = default) public bool Wait(TimeSpan timeout) { ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this); - return Wait(new(timeout), ref manager); + return Wait(new Timeout(timeout)); + } + + [UnsupportedOSPlatform("browser")] + private bool Wait(Timeout timeout) + { + lock (SyncRoot) + { + return TryAcquire(ref manager) || + timeout.TryGetRemainingTime(out var remainingTime) + && Monitor.Wait(SyncRoot, remainingTime); + } } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/QueuedSynchronizer.cs b/src/DotNext.Threading/Threading/QueuedSynchronizer.cs index ab94f59a8..8d2c43e82 100644 --- a/src/DotNext.Threading/Threading/QueuedSynchronizer.cs +++ b/src/DotNext.Threading/Threading/QueuedSynchronizer.cs @@ -188,18 +188,6 @@ private protected bool TryAcquire(Timeout timeout, ref TLockManage return true; } - [UnsupportedOSPlatform("browser")] - private protected bool Wait(Timeout timeout, ref TLockManager manager) - where TLockManager : struct, ILockManager - { - lock (SyncRoot) - { - return TryAcquire(ref manager) || - timeout.TryGetRemainingTime(out var remainingTime) - && Monitor.Wait(SyncRoot, remainingTime); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryAcquireOrThrow(ref TLockManager manager) where TLockManager : struct, ILockManager