forked from dotnet/dotnet-monitor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCallbackAction.cs
258 lines (216 loc) · 8.95 KB
/
CallbackAction.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Diagnostics.Monitoring.WebApi;
using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Actions;
using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Configuration;
using Microsoft.Diagnostics.Tools.Monitor.CollectionRules.Options.Actions;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace Microsoft.Diagnostics.Monitoring.TestCommon
{
internal sealed class CallbackActionFactory : ICollectionRuleActionFactory<BaseRecordOptions>
{
private readonly CallbackActionService _service;
public CallbackActionFactory(CallbackActionService service)
{
_service = service;
}
public ICollectionRuleAction Create(IProcessInfo processInfo, BaseRecordOptions options)
{
return new CallbackAction(_service);
}
}
internal sealed class CallbackAction : ICollectionRuleAction
{
public const string ActionName = nameof(CallbackAction);
private readonly CallbackActionService _service;
private readonly TaskCompletionSource _startCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
public Task Started => _startCompletionSource.Task;
public CallbackAction(CallbackActionService service)
{
_service = service;
}
public Task StartAsync(CollectionRuleMetadata collectionRuleMetadata, CancellationToken token)
{
return StartAsync(token); // We don't care about collectionRuleMetadata for testing (yet)
}
public Task StartAsync(CancellationToken token)
{
_startCompletionSource.TrySetResult();
return _service.NotifyListeners(token);
}
public Task<CollectionRuleActionResult> WaitForCompletionAsync(CancellationToken token)
{
return Task.FromResult(new CollectionRuleActionResult());
}
}
internal sealed class DelayedCallbackActionFactory : ICollectionRuleActionFactory<BaseRecordOptions>
{
private readonly CallbackActionService _service;
public DelayedCallbackActionFactory(CallbackActionService service)
{
_service = service;
}
public ICollectionRuleAction Create(IProcessInfo processInfo, BaseRecordOptions options)
{
return new DelayedCallbackAction(_service);
}
}
internal sealed class CallbackActionDescriptor : ICollectionRuleActionDescriptor
{
public string ActionName => CallbackAction.ActionName;
public Type OptionsType => typeof(BaseRecordOptions);
public Type FactoryType => typeof(CallbackActionFactory);
public void BindOptions(IConfigurationSection settingsSection, out object settings)
{
BaseRecordOptions options = new();
settingsSection.Bind(options);
settings = options;
}
}
internal sealed class DelayedCallbackAction : ICollectionRuleAction
{
public const string ActionName = nameof(DelayedCallbackAction);
private readonly CallbackActionService _service;
private readonly TaskCompletionSource _startCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
public Task Started => _startCompletionSource.Task;
public DelayedCallbackAction(CallbackActionService service)
{
_service = service;
}
public Task StartAsync(CollectionRuleMetadata collectionRuleMetadata, CancellationToken token)
{
return StartAsync(token); // We don't care about collectionRuleMetadata for testing (yet)
}
public Task StartAsync(CancellationToken token)
{
_startCompletionSource.TrySetResult();
return _service.NotifyListeners(token);
}
public Task<CollectionRuleActionResult> WaitForCompletionAsync(CancellationToken token)
{
var currentTime = _service.TimeProvider.GetUtcNow();
while (_service.TimeProvider.GetUtcNow() == currentTime)
{
// waiting for clock to be ticked (simulated time)
token.ThrowIfCancellationRequested();
}
return Task.FromResult(new CollectionRuleActionResult());
}
}
internal sealed class DelayedCallbackActionDescriptor : ICollectionRuleActionDescriptor
{
public string ActionName => DelayedCallbackAction.ActionName;
public Type OptionsType => typeof(BaseRecordOptions);
public Type FactoryType => typeof(DelayedCallbackActionFactory);
public void BindOptions(IConfigurationSection settingsSection, out object settings)
{
BaseRecordOptions options = new();
settingsSection.Bind(options);
settings = options;
}
}
internal sealed class CallbackActionService
{
public TimeProvider TimeProvider { get; }
private readonly List<CompletionEntry> _entries = new();
private readonly SemaphoreSlim _entriesSemaphore = new(1);
private readonly List<DateTime> _executionTimestamps = new();
private readonly ITestOutputHelper _outputHelper;
private int _nextId = 1;
public CallbackActionService(ITestOutputHelper outputHelper, TimeProvider timeProvider = null)
{
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
TimeProvider = timeProvider ?? TimeProvider.System;
}
public async Task NotifyListeners(CancellationToken token)
{
await _entriesSemaphore.WaitAsync(token);
try
{
lock (_executionTimestamps)
{
_executionTimestamps.Add(TimeProvider.GetUtcNow().UtcDateTime);
}
_outputHelper.WriteLine("[Callback] Completing {0} source(s).", _entries.Count);
foreach (var entry in _entries)
{
entry.Complete();
}
_entries.Clear();
}
finally
{
_entriesSemaphore.Release();
}
}
/// <summary>
/// Registers a callback with the Callback action that will complete when
/// the Callback action is invoked.
/// </summary>
/// <returns>
/// A <see cref="Task{Task}"/> that completes when the callback has finished
/// being registered. The inner <see cref="Task"/> will complete when the callback
/// is invoked.
/// </returns>
/// <remarks>
/// Await this method to wait for the callback to be registered; await the inner
/// task to wait for the callback to be invoked. The <paramref name="token"/> parameter
/// only cancels registration if the registration has not completed; it does not cancel
/// the inner task that represents the callback invocation.
/// </remarks>
public async Task<Task> StartWaitForCallbackAsync(CancellationToken token)
{
int id = _nextId++;
string name = $"Callback{id}";
CompletionEntry entry = new(_outputHelper, name);
await _entriesSemaphore.WaitAsync(token);
try
{
_outputHelper.WriteLine("[Test] Registering {0}.", name);
_entries.Add(entry);
}
finally
{
_entriesSemaphore.Release();
}
return entry.Task;
}
public IReadOnlyCollection<DateTime> ExecutionTimestamps
{
get
{
lock (_executionTimestamps)
{
return _executionTimestamps.AsReadOnly();
}
}
}
private sealed class CompletionEntry
{
private readonly TaskCompletionSource<object> _completionSource =
new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly string _name;
private readonly ITestOutputHelper _outputHelper;
public CompletionEntry(ITestOutputHelper outputHelper, string name)
{
_name = name;
_outputHelper = outputHelper;
}
public void Complete()
{
_outputHelper.WriteLine("[Callback] Begin completing {0}.", _name);
if (!_completionSource.TrySetResult(null))
{
_outputHelper.WriteLine("[Callback] Unable to complete {0}.", _name);
}
_outputHelper.WriteLine("[Callback] End completing {0}.", _name);
}
public Task Task => _completionSource.Task;
}
}
}