Skip to content

Commit d7253fa

Browse files
committed
Added support of custom exception
1 parent c0cb3fd commit d7253fa

File tree

4 files changed

+86
-25
lines changed

4 files changed

+86
-25
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace DotNext.Threading;
2+
3+
public sealed class QueuedSynchronizerTests : Test
4+
{
5+
[Fact]
6+
public static async Task ThrowOnAcquisitionAsync()
7+
{
8+
await using var synchronizer = new MySynchronizer();
9+
await ThrowsAsync<ArithmeticException>(synchronizer.ThrowAsync().AsTask);
10+
False(synchronizer.TryAcquire());
11+
}
12+
13+
private sealed class MySynchronizer : QueuedSynchronizer<bool>
14+
{
15+
public ValueTask ThrowAsync(CancellationToken token = default)
16+
=> AcquireAsync(context: false, token);
17+
18+
public bool TryAcquire() => TryAcquire(context: false);
19+
20+
protected override bool CanAcquire(bool context) => context;
21+
22+
protected override Exception GetAcquisitionException(bool canAcquire)
23+
=> canAcquire ? null : new ArithmeticException();
24+
}
25+
}

src/DotNext.Threading/Threading/QueuedSynchronizer.Acquisition.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ private protected interface ITaskBuilder<out TOutput> : ITaskBuilder, ISupplier<
3030
where TOutput : struct, IEquatable<TOutput>
3131
{
3232
static abstract bool ThrowOnTimeout { get; }
33+
34+
static abstract TOutput FromException(Exception e);
3335
}
3436

3537
[StructLayout(LayoutKind.Auto)]
@@ -84,6 +86,8 @@ readonly ValueTask ISupplier<ValueTask>.Invoke()
8486
}
8587

8688
static bool ITaskBuilder<ValueTask>.ThrowOnTimeout => true;
89+
90+
static ValueTask ITaskBuilder<ValueTask>.FromException(Exception e) => ValueTask.FromException(e);
8791
}
8892

8993
[StructLayout(LayoutKind.Auto)]
@@ -164,6 +168,10 @@ readonly ValueTask<bool> ISupplier<ValueTask<bool>>.Invoke()
164168
static bool ITaskBuilder<ValueTask>.ThrowOnTimeout => true;
165169

166170
static bool ITaskBuilder<ValueTask<bool>>.ThrowOnTimeout => false;
171+
172+
static ValueTask ITaskBuilder<ValueTask>.FromException(Exception e) => ValueTask.FromException(e);
173+
174+
static ValueTask<bool> ITaskBuilder<ValueTask<bool>>.FromException(Exception e) => ValueTask.FromException<bool>(e);
167175
}
168176

169177
[StructLayout(LayoutKind.Auto)]
@@ -198,6 +206,8 @@ void IDisposable.Dispose()
198206
T ISupplier<T>.Invoke() => Builder.Invoke();
199207

200208
static bool ITaskBuilder<T>.ThrowOnTimeout => TBuilder.ThrowOnTimeout;
209+
210+
static T ITaskBuilder<T>.FromException(Exception e) => TBuilder.FromException(e);
201211
}
202212

203213
private protected CancellationTokenOnly CreateTaskBuilder(CancellationToken token)

src/DotNext.Threading/Threading/QueuedSynchronizer.Queue.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ private bool SignalCurrent(in Result<bool> result)
180180

181181
public bool SignalCurrent() => SignalCurrent(result: true);
182182

183+
public void SignalCurrent(Exception e) => SignalCurrent(result: new(e));
184+
183185
public bool SignalCurrent<TLockManager>(ref TLockManager manager)
184186
where TLockManager : struct, ILockManager
185187
{

src/DotNext.Threading/Threading/QueuedSynchronizer.T.cs

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,16 @@ protected QueuedSynchronizer()
4141
/// </summary>
4242
/// <param name="context">The context associated with the suspended caller or supplied externally.</param>
4343
/// <returns><see langword="true"/> if acquisition is allowed; otherwise, <see langword="false"/>.</returns>
44+
/// <exception cref="Exception">A custom exception is thrown.</exception>
4445
protected abstract bool CanAcquire(TContext context);
4546

47+
/// <summary>
48+
/// Returns an optional exception if <see cref="CanAcquire"/> returns <see langword="false"/>.
49+
/// </summary>
50+
/// <param name="context">The acquisition context.</param>
51+
/// <returns>The exception; or <see langword="null"/>.</returns>
52+
protected virtual Exception? GetAcquisitionException(TContext context) => null;
53+
4654
/// <summary>
4755
/// Modifies the internal state according to acquisition semantics.
4856
/// </summary>
@@ -69,11 +77,19 @@ private protected sealed override void DrainWaitQueue(ref WaitQueueVisitor waitQ
6977
{
7078
for (; !waitQueueVisitor.IsEndOfQueue<WaitNode, TContext>(out var context); waitQueueVisitor.Advance())
7179
{
72-
if (!CanAcquire(context))
73-
break;
74-
75-
if (waitQueueVisitor.SignalCurrent())
76-
AcquireCore(context);
80+
switch (CanAcquire(context))
81+
{
82+
case false when GetAcquisitionException(context) is { } e:
83+
waitQueueVisitor.SignalCurrent(e);
84+
goto default;
85+
case false:
86+
return;
87+
case true when waitQueueVisitor.SignalCurrent():
88+
AcquireCore(context);
89+
goto default;
90+
default:
91+
continue;
92+
}
7793
}
7894
}
7995

@@ -194,6 +210,34 @@ protected ValueTask AcquireAsync(TContext context, CancellationToken token)
194210
return AcquireAsync<ValueTask, CancellationTokenOnly>(context, ref builder);
195211
}
196212

213+
private T AcquireAsync<T, TBuilder>(TContext context, ref TBuilder builder)
214+
where T : struct, IEquatable<T>
215+
where TBuilder : struct, ITaskBuilder<T>
216+
{
217+
T result;
218+
if (!builder.IsCompleted)
219+
{
220+
var acquired = TryAcquireCore(context);
221+
if (!acquired && GetAcquisitionException(context) is { } e)
222+
{
223+
result = TBuilder.FromException(e);
224+
goto exit;
225+
}
226+
227+
if (Acquire<T, TBuilder, WaitNode>(ref builder, acquired) is { } node)
228+
{
229+
node.DrainOnReturn = true;
230+
node.Context = context;
231+
}
232+
}
233+
234+
result = builder.Invoke();
235+
236+
exit:
237+
builder.Dispose();
238+
return result;
239+
}
240+
197241
private bool TryAcquireCore(TContext context)
198242
{
199243
Debug.Assert(Monitor.IsEntered(SyncRoot));
@@ -206,24 +250,4 @@ private bool TryAcquireCore(TContext context)
206250

207251
return false;
208252
}
209-
210-
private T AcquireAsync<T, TBuilder>(TContext context, ref TBuilder builder)
211-
where T : struct, IEquatable<T>
212-
where TBuilder : struct, ITaskBuilder<T>
213-
{
214-
switch (builder.IsCompleted)
215-
{
216-
case true:
217-
goto default;
218-
case false when Acquire<T, TBuilder, WaitNode>(ref builder, TryAcquireCore(context)) is { } node:
219-
node.DrainOnReturn = true;
220-
node.Context = context;
221-
goto default;
222-
default:
223-
builder.Dispose();
224-
break;
225-
}
226-
227-
return builder.Invoke();
228-
}
229253
}

0 commit comments

Comments
 (0)