-
Notifications
You must be signed in to change notification settings - Fork 119
/
Scope.cs
186 lines (163 loc) · 6.42 KB
/
Scope.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Debug = System.Diagnostics.Debug;
namespace DotNext.Runtime.CompilerServices;
using ExceptionAggregator = ExceptionServices.ExceptionAggregator;
/// <summary>
/// Represents a collection of callbacks to be executed at the end of the lexical scope.
/// </summary>
/// <remarks>
/// This type allows to avoid usage of try-finally blocks within the code. It is suitable
/// for asynchronous and synchronous scenarios. However, you should not pass an instance of this type
/// as an argument to or return it from the method.
/// </remarks>
[StructLayout(LayoutKind.Auto)]
public struct Scope : IDisposable, IAsyncDisposable
{
private sealed class DynamicTuple : List<object?>, ITuple
{
int ITuple.Length => Count;
}
// null, or Action, or Func<ValueTask>, or IDisposable, or IAsyncDisposable
private (object? Callback0, object? Callback1, object? Callback2, object? Callback3) callbacks;
private DynamicTuple? rest;
private void Add(object callback)
{
if (callback is null)
throw new ArgumentNullException(nameof(callback));
Debug.Assert(callback is Action or Func<ValueTask> or IDisposable or IAsyncDisposable);
foreach (ref var slot in callbacks.AsSpan())
{
if (slot is null)
{
slot = callback;
return;
}
}
rest ??= new();
rest.Add(callback);
}
/// <summary>
/// Attaches callback to this lexical scope.
/// </summary>
/// <param name="callback">The callback to be attached to the current scope.</param>
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
public void Defer(Action callback) => Add(callback);
/// <summary>
/// Attaches callback to this lexical scope.
/// </summary>
/// <param name="callback">The callback to be attached to the current scope.</param>
/// <exception cref="ArgumentNullException"><paramref name="callback"/> is <see langword="null"/>.</exception>
public void Defer(Func<ValueTask> callback) => Add(callback);
/// <summary>
/// Registers an object for disposal.
/// </summary>
/// <param name="disposable">The object to be disposed.</param>
public void RegisterForDispose(IDisposable disposable) => Add(disposable);
/// <summary>
/// Registers an object for asynchronous disposal.
/// </summary>
/// <param name="disposable">The object to be disposed asynchronously.</param>
public void RegisterForDisposeAsync(IAsyncDisposable disposable) => Add(disposable);
/// <summary>
/// Executes all attached callbacks synchronously.
/// </summary>
public void Dispose()
{
var exceptions = new ExceptionAggregator();
ExecuteCallbacks(callbacks.AsReadOnlySpan(), ref exceptions);
if (rest is not null)
{
ExecuteCallbacks(CollectionsMarshal.AsSpan(rest)!, ref exceptions);
rest.Clear();
}
this = default;
exceptions.ThrowIfNeeded();
static void ExecuteCallbacks(ReadOnlySpan<object?> callbacks, ref ExceptionAggregator aggregator)
{
Task t;
foreach (var cb in callbacks)
{
try
{
switch (cb)
{
case null:
return;
case Action callback:
callback();
break;
case Func<ValueTask> callback:
using (t = callback().AsTask())
{
t.Wait();
}
break;
case IDisposable disposable:
// IDisposable in synchronous implementation has higher priority than IAsyncDisposable
disposable.Dispose();
break;
case IAsyncDisposable disposable:
using (t = disposable.DisposeAsync().AsTask())
{
t.Wait();
}
break;
}
}
catch (Exception e)
{
aggregator.Add(e);
}
}
}
}
/// <summary>
/// Executes all attached callbacks asynchronously.
/// </summary>
/// <returns>The task representing asynchronous execution.</returns>
public readonly async ValueTask DisposeAsync()
{
var exceptions = BoxedValue<ExceptionAggregator>.Box(new());
await ExecuteCallbacksAsync(callbacks, exceptions).ConfigureAwait(false);
if (rest is not null)
{
await ExecuteCallbacksAsync(rest, exceptions).ConfigureAwait(false);
rest.Clear();
}
exceptions.Value.ThrowIfNeeded();
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
static async ValueTask ExecuteCallbacksAsync<T>(T callbacks, BoxedValue<ExceptionAggregator> exceptions)
where T : notnull, ITuple
{
for (int i = 0, count = callbacks.Length; i < count; i++)
{
try
{
switch (callbacks[i])
{
case null:
return;
case Action callback:
callback();
break;
case Func<ValueTask> callback:
await callback().ConfigureAwait(false);
break;
case IAsyncDisposable disposable:
// IAsyncDisposable in asynchronous implementation has higher priority than IDisposable
await disposable.DisposeAsync().ConfigureAwait(false);
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
catch (Exception e)
{
exceptions.Value.Add(e);
}
}
}
}
}