Skip to content

Commit

Permalink
Fixed auto reset behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed Oct 15, 2024
1 parent 99e0565 commit 6032383
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 49 deletions.
17 changes: 8 additions & 9 deletions src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ObjectDisposedException>(() => l.TryAcquire(DefaultTimeout))) { IsBackground = true };
t.Start();
var t = Task.Factory.StartNew(() => Throws<ObjectDisposedException>(() => l.TryAcquire(DefaultTimeout)),
TaskCreationOptions.LongRunning);

l.Dispose();
True(t.Join(DefaultTimeout));
await t;
}
}
34 changes: 15 additions & 19 deletions src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,66 +198,62 @@ 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<ObjectDisposedException>(() => l.TryEnterWriteLock(DefaultTimeout))) { IsBackground = true };
t.Start();
var t = Task.Factory.StartNew(() => Throws<ObjectDisposedException>(() => 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<ObjectDisposedException>(() => l.TryEnterReadLock(DefaultTimeout))) { IsBackground = true };
t.Start();
var t = Task.Factory.StartNew(() => Throws<ObjectDisposedException>(() => 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();
False(l.IsWriteLockHeld);
}

[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);

Expand Down
36 changes: 32 additions & 4 deletions src/DotNext.Tests/Threading/AsyncResetEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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));
}
}
32 changes: 28 additions & 4 deletions src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public bool Reset()
}

/// <summary>
/// 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.
/// </summary>
/// <returns><see langword="true"/> if the operation succeeds; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
Expand Down Expand Up @@ -119,7 +119,7 @@ public bool Set()
}
}

Monitor.PulseAll(SyncRoot);
Monitor.Pulse(SyncRoot);
}
}

Expand Down Expand Up @@ -154,7 +154,7 @@ public ValueTask<bool> WaitAsync(TimeSpan timeout, CancellationToken token = def
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public ValueTask WaitAsync(CancellationToken token = default)
=> AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token));

/// <summary>
/// Blocks the current thread until this event is set.
/// </summary>
Expand All @@ -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;
}
}
13 changes: 12 additions & 1 deletion src/DotNext.Threading/Threading/AsyncManualResetEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
12 changes: 0 additions & 12 deletions src/DotNext.Threading/Threading/QueuedSynchronizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,18 +188,6 @@ private protected bool TryAcquire<TLockManager>(Timeout timeout, ref TLockManage
return true;
}

[UnsupportedOSPlatform("browser")]
private protected bool Wait<TLockManager>(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<TLockManager>(ref TLockManager manager)
where TLockManager : struct, ILockManager
Expand Down

0 comments on commit 6032383

Please sign in to comment.