Skip to content

Commit 465aa61

Browse files
pshao25Pan Shao
and
Pan Shao
authored
Add scope of waitForCompletion in LRO (#29033)
* Add scope of waitForCompletion in LRO * Change flag to string * Resolve comments * Resolve comments * Add tests * Resolve comments * Fix tests * Fix test * Fix test * Resolve comments * Resolve comments * Make scope name not nullable. * Resolve comments * Remove useless method * Resolve comment Co-authored-by: Pan Shao <pashao@microsoft.com>
1 parent 7a49112 commit 465aa61

File tree

4 files changed

+173
-29
lines changed

4 files changed

+173
-29
lines changed

sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs

+31-15
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ namespace Azure.Core
1515
internal abstract class OperationInternalBase
1616
{
1717
private readonly ClientDiagnostics _diagnostics;
18-
private readonly string _updateStatusScopeName;
1918
private readonly IReadOnlyDictionary<string, string>? _scopeAttributes;
2019
private readonly DelayStrategy? _fallbackStrategy;
2120
private readonly AsyncLockWithValue<Response> _responseLock;
2221

22+
private readonly string _waitForCompletionResponseScopeName;
23+
protected readonly string _updateStatusScopeName;
24+
protected readonly string _waitForCompletionScopeName;
25+
2326
protected OperationInternalBase(Response rawResponse)
2427
{
2528
_diagnostics = new ClientDiagnostics(ClientOptions.Default);
2629
_updateStatusScopeName = string.Empty;
30+
_waitForCompletionResponseScopeName = string.Empty;
31+
_waitForCompletionScopeName = string.Empty;
2732
_scopeAttributes = default;
2833
_fallbackStrategy = default;
2934
_responseLock = new AsyncLockWithValue<Response>(rawResponse);
@@ -32,7 +37,9 @@ protected OperationInternalBase(Response rawResponse)
3237
protected OperationInternalBase(ClientDiagnostics clientDiagnostics, string operationTypeName, IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null, DelayStrategy? fallbackStrategy = null)
3338
{
3439
_diagnostics = clientDiagnostics;
35-
_updateStatusScopeName = $"{operationTypeName}.UpdateStatus";
40+
_updateStatusScopeName = $"{operationTypeName}.{nameof(UpdateStatus)}";
41+
_waitForCompletionResponseScopeName = $"{operationTypeName}.{nameof(WaitForCompletionResponse)}";
42+
_waitForCompletionScopeName = $"{operationTypeName}.WaitForCompletion";
3643
_scopeAttributes = scopeAttributes?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
3744
_fallbackStrategy = fallbackStrategy;
3845
_responseLock = new AsyncLockWithValue<Response>();
@@ -114,7 +121,7 @@ public Response UpdateStatus(CancellationToken cancellationToken) =>
114121
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
115122
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
116123
public async ValueTask<Response> WaitForCompletionResponseAsync(CancellationToken cancellationToken)
117-
=> await WaitForCompletionResponseAsync(async: true, null, cancellationToken).ConfigureAwait(false);
124+
=> await WaitForCompletionResponseAsync(async: true, null, _waitForCompletionResponseScopeName, cancellationToken).ConfigureAwait(false);
118125

119126
/// <summary>
120127
/// Periodically calls <see cref="UpdateStatusAsync(CancellationToken)"/> until the long-running operation completes. The interval
@@ -135,7 +142,7 @@ public async ValueTask<Response> WaitForCompletionResponseAsync(CancellationToke
135142
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
136143
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
137144
public async ValueTask<Response> WaitForCompletionResponseAsync(TimeSpan pollingInterval, CancellationToken cancellationToken)
138-
=> await WaitForCompletionResponseAsync(async: true, pollingInterval, cancellationToken).ConfigureAwait(false);
145+
=> await WaitForCompletionResponseAsync(async: true, pollingInterval, _waitForCompletionResponseScopeName, cancellationToken).ConfigureAwait(false);
139146

140147
/// <summary>
141148
/// Periodically calls <see cref="UpdateStatus(CancellationToken)"/> until the long-running operation completes.
@@ -155,7 +162,7 @@ public async ValueTask<Response> WaitForCompletionResponseAsync(TimeSpan polling
155162
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
156163
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
157164
public Response WaitForCompletionResponse(CancellationToken cancellationToken)
158-
=> WaitForCompletionResponseAsync(async: false, null, cancellationToken).EnsureCompleted();
165+
=> WaitForCompletionResponseAsync(async: false, null, _waitForCompletionResponseScopeName, cancellationToken).EnsureCompleted();
159166

160167
/// <summary>
161168
/// Periodically calls <see cref="UpdateStatus(CancellationToken)"/> until the long-running operation completes. The interval
@@ -176,9 +183,9 @@ public Response WaitForCompletionResponse(CancellationToken cancellationToken)
176183
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
177184
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
178185
public Response WaitForCompletionResponse(TimeSpan pollingInterval, CancellationToken cancellationToken)
179-
=> WaitForCompletionResponseAsync(async: false, pollingInterval, cancellationToken).EnsureCompleted();
186+
=> WaitForCompletionResponseAsync(async: false, pollingInterval, _waitForCompletionResponseScopeName, cancellationToken).EnsureCompleted();
180187

181-
private async ValueTask<Response> WaitForCompletionResponseAsync(bool async, TimeSpan? pollingInterval, CancellationToken cancellationToken)
188+
protected async ValueTask<Response> WaitForCompletionResponseAsync(bool async, TimeSpan? pollingInterval, string scopeName, CancellationToken cancellationToken)
182189
{
183190
// If _responseLock has the value, lockOrValue will contain that value, and no lock is acquired.
184191
// If _responseLock doesn't have the value, GetLockOrValueAsync will acquire the lock that will be released when lockOrValue is disposed
@@ -188,20 +195,29 @@ private async ValueTask<Response> WaitForCompletionResponseAsync(bool async, Tim
188195
return lockOrValue.Value;
189196
}
190197

191-
var poller = new OperationPoller(_fallbackStrategy);
192-
var response = async
193-
? await poller.WaitForCompletionResponseAsync(this, pollingInterval, cancellationToken).ConfigureAwait(false)
194-
: poller.WaitForCompletionResponse(this, pollingInterval, cancellationToken);
198+
using var scope = CreateScope(scopeName);
199+
try
200+
{
201+
var poller = new OperationPoller(_fallbackStrategy);
202+
var response = async
203+
? await poller.WaitForCompletionResponseAsync(this, pollingInterval, cancellationToken).ConfigureAwait(false)
204+
: poller.WaitForCompletionResponse(this, pollingInterval, cancellationToken);
195205

196-
lockOrValue.SetValue(response);
197-
return response;
206+
lockOrValue.SetValue(response);
207+
return response;
208+
}
209+
catch (Exception e)
210+
{
211+
scope.Failed(e);
212+
throw;
213+
}
198214
}
199215

200216
protected abstract ValueTask<Response> UpdateStatusAsync(bool async, CancellationToken cancellationToken);
201217

202-
protected DiagnosticScope CreateScope()
218+
protected DiagnosticScope CreateScope(string scopeName)
203219
{
204-
DiagnosticScope scope = _diagnostics.CreateScope(_updateStatusScopeName);
220+
DiagnosticScope scope = _diagnostics.CreateScope(scopeName);
205221

206222
if (_scopeAttributes != null)
207223
{

sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs

+8-14
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,7 @@ public T Value
174174
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
175175
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
176176
public async ValueTask<Response<T>> WaitForCompletionAsync(CancellationToken cancellationToken)
177-
{
178-
var rawResponse = await WaitForCompletionResponseAsync(cancellationToken).ConfigureAwait(false);
179-
return Response.FromValue(Value, rawResponse);
180-
}
177+
=> await WaitForCompletionAsync(async: true, null, cancellationToken).ConfigureAwait(false);
181178

182179
/// <summary>
183180
/// Periodically calls <see cref="OperationInternalBase.UpdateStatusAsync(CancellationToken)"/> until the long-running operation completes. The interval
@@ -198,10 +195,7 @@ public async ValueTask<Response<T>> WaitForCompletionAsync(CancellationToken can
198195
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
199196
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
200197
public async ValueTask<Response<T>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken)
201-
{
202-
var rawResponse = await WaitForCompletionResponseAsync(pollingInterval, cancellationToken).ConfigureAwait(false);
203-
return Response.FromValue(Value, rawResponse);
204-
}
198+
=> await WaitForCompletionAsync(async: true, pollingInterval, cancellationToken).ConfigureAwait(false);
205199

206200
/// <summary>
207201
/// Periodically calls <see cref="OperationInternalBase.UpdateStatus(CancellationToken)"/> until the long-running operation completes.
@@ -220,10 +214,7 @@ public async ValueTask<Response<T>> WaitForCompletionAsync(TimeSpan pollingInter
220214
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
221215
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
222216
public Response<T> WaitForCompletion(CancellationToken cancellationToken)
223-
{
224-
var rawResponse = WaitForCompletionResponse(cancellationToken);
225-
return Response.FromValue(Value, rawResponse);
226-
}
217+
=> WaitForCompletionAsync(async: false, null, cancellationToken).EnsureCompleted();
227218

228219
/// <summary>
229220
/// Periodically calls <see cref="OperationInternalBase.UpdateStatus(CancellationToken)"/> until the long-running operation completes. The interval
@@ -244,8 +235,11 @@ public Response<T> WaitForCompletion(CancellationToken cancellationToken)
244235
/// <returns>The last HTTP response received from the server, including the final result of the long-running operation.</returns>
245236
/// <exception cref="RequestFailedException">Thrown if there's been any issues during the connection, or if the operation has completed with failures.</exception>
246237
public Response<T> WaitForCompletion(TimeSpan pollingInterval, CancellationToken cancellationToken)
238+
=> WaitForCompletionAsync(async: false, pollingInterval, cancellationToken).EnsureCompleted();
239+
240+
private async ValueTask<Response<T>> WaitForCompletionAsync(bool async, TimeSpan? pollingInterval, CancellationToken cancellationToken)
247241
{
248-
var rawResponse = WaitForCompletionResponse(pollingInterval, cancellationToken);
242+
var rawResponse = await WaitForCompletionResponseAsync(async, pollingInterval, _waitForCompletionScopeName, cancellationToken).ConfigureAwait(false);
249243
return Response.FromValue(Value, rawResponse);
250244
}
251245

@@ -260,7 +254,7 @@ protected override async ValueTask<Response> UpdateStatusAsync(bool async, Cance
260254
return GetResponseFromState(asyncLock.Value);
261255
}
262256

263-
using var scope = CreateScope();
257+
using var scope = CreateScope(_updateStatusScopeName);
264258
try
265259
{
266260
var state = await _operation.UpdateStateAsync(async, cancellationToken).ConfigureAwait(false);

sdk/core/Azure.Core/tests/OperationInternalOfTTests.cs

+85
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,19 @@ public async Task UpdateStatusCreatesDiagnosticScope([Values(true, false)] bool
148148
testListener.AssertScope($"{expectedTypeName}.UpdateStatus", expectedAttributes);
149149
}
150150

151+
[Test]
152+
public async Task UpdateStatusNotCreateDiagnosticScope([Values(true, false)] bool async)
153+
{
154+
using ClientDiagnosticListener testListener = new(DiagnosticNamespace);
155+
156+
var operationInternal = OperationInternal<int>.Succeeded(InitialResponse, 1);
157+
_ = async
158+
? await operationInternal.UpdateStatusAsync(CancellationToken.None)
159+
: operationInternal.UpdateStatus(CancellationToken.None);
160+
161+
CollectionAssert.IsEmpty(testListener.Scopes);
162+
}
163+
151164
[Test]
152165
public async Task UpdateStatusSetsFailedScopeWhenOperationFails([Values(true, false)] bool async)
153166
{
@@ -205,6 +218,78 @@ public async Task UpdateStatusPassesTheCancellationTokenToUpdateState([Values(tr
205218
Assert.AreEqual(originalToken, passedToken);
206219
}
207220

221+
[Test]
222+
public async Task WaitForCompletionResponseCreatesDiagnosticScope([Values(true, false)] bool async, [Values(null, "CustomTypeName")] string operationTypeName, [Values(true, false)] bool suppressNestedClientActivities)
223+
{
224+
using ClientDiagnosticListener testListener = new(DiagnosticNamespace);
225+
226+
string expectedTypeName = operationTypeName ?? nameof(TestOperation);
227+
KeyValuePair<string, string>[] expectedAttributes = { new("key1", "value1"), new("key2", "value2") };
228+
var operationInternal = new OperationInternal<int>(new(new TestClientOptions(), suppressNestedClientActivities), TestOperation.Succeeded(1), InitialResponse, operationTypeName, expectedAttributes);
229+
230+
_ = async
231+
? await operationInternal.WaitForCompletionResponseAsync(CancellationToken.None)
232+
: operationInternal.WaitForCompletionResponse(CancellationToken.None);
233+
234+
testListener.AssertScope($"{expectedTypeName}.WaitForCompletionResponse", expectedAttributes);
235+
#if NET5_0_OR_GREATER
236+
if (suppressNestedClientActivities)
237+
{
238+
testListener.AssertAndRemoveScope($"{expectedTypeName}.WaitForCompletionResponse", expectedAttributes);
239+
CollectionAssert.IsEmpty(testListener.Scopes);
240+
}
241+
#endif
242+
}
243+
244+
[Test]
245+
public async Task WaitForCompletionCreatesDiagnosticScope([Values(true, false)] bool async, [Values(null, "CustomTypeName")] string operationTypeName, [Values(true, false)] bool suppressNestedClientActivities)
246+
{
247+
using ClientDiagnosticListener testListener = new(DiagnosticNamespace);
248+
249+
string expectedTypeName = operationTypeName ?? nameof(TestOperation);
250+
KeyValuePair<string, string>[] expectedAttributes = { new("key1", "value1"), new("key2", "value2") };
251+
var operationInternal = new OperationInternal<int>(new(new TestClientOptions(), suppressNestedClientActivities), TestOperation.Succeeded(1), InitialResponse, operationTypeName, expectedAttributes);
252+
253+
_ = async
254+
? await operationInternal.WaitForCompletionAsync(CancellationToken.None)
255+
: operationInternal.WaitForCompletion(CancellationToken.None);
256+
257+
testListener.AssertScope($"{expectedTypeName}.WaitForCompletion", expectedAttributes);
258+
#if NET5_0_OR_GREATER
259+
if (suppressNestedClientActivities)
260+
{
261+
testListener.AssertAndRemoveScope($"{expectedTypeName}.WaitForCompletion", expectedAttributes);
262+
CollectionAssert.IsEmpty(testListener.Scopes);
263+
}
264+
#endif
265+
}
266+
267+
[Test]
268+
public async Task WaitForCompletionResponseNotCreateDiagnosticScope([Values(true, false)] bool async)
269+
{
270+
using ClientDiagnosticListener testListener = new(DiagnosticNamespace);
271+
272+
var operationInternal = OperationInternal<int>.Succeeded(InitialResponse, 1);
273+
_ = async
274+
? await operationInternal.WaitForCompletionResponseAsync(CancellationToken.None)
275+
: operationInternal.WaitForCompletionResponse(CancellationToken.None);
276+
277+
CollectionAssert.IsEmpty(testListener.Scopes);
278+
}
279+
280+
[Test]
281+
public async Task WaitForCompletionNotCreateDiagnosticScope([Values(true, false)] bool async)
282+
{
283+
using ClientDiagnosticListener testListener = new(DiagnosticNamespace);
284+
285+
var operationInternal = OperationInternal<int>.Succeeded(InitialResponse, 1);
286+
_ = async
287+
? await operationInternal.WaitForCompletionAsync(CancellationToken.None)
288+
: operationInternal.WaitForCompletion(CancellationToken.None);
289+
290+
CollectionAssert.IsEmpty(testListener.Scopes);
291+
}
292+
208293
[Test]
209294
public async Task WaitForCompletionCallsUntilOperationCompletes([Values(true, false)] bool useDefaultPollingInterval)
210295
{

0 commit comments

Comments
 (0)