Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add .ConfigureAwait(false) to all async method invocations. #2177

Closed
wants to merge 1 commit into from

Conversation

tippmar-nr
Copy link
Member

Updates all async method invocations to add .ConfigureAwait(false) which may help prevent deadlocks in certain cases - in particular, we've seen reports of the agent apparently hanging on startup in some instances which may be related to our use of HttpClient.SendAsync(). Changes are, at worst, harmless and are based on guidance for "library" code from Stephen Toub.

Note that unit and integration tests were not updated, as those are "application" code which (generally) shouldn't use .ConfigureAwait(false).

@codecov-commenter
Copy link

Codecov Report

Attention: 1 lines in your changes are missing coverage. Please review.

Comparison is base (445f7c2) 81.05% compared to head (143b785) 81.04%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2177      +/-   ##
==========================================
- Coverage   81.05%   81.04%   -0.02%     
==========================================
  Files         396      396              
  Lines       24723    24723              
  Branches     2997     2997              
==========================================
- Hits        20040    20036       -4     
- Misses       3895     3898       +3     
- Partials      788      789       +1     
Files Coverage Δ
src/Agent/NewRelic/Agent/Core/Agent.cs 71.24% <ø> (ø)
.../Agent/Core/DataTransport/Client/HttpClientBase.cs 75.00% <100.00%> (ø)
...ent/Core/DataTransport/Client/HttpClientWrapper.cs 35.71% <ø> (ø)
...ic/Agent/Core/DataTransport/Client/HttpResponse.cs 70.00% <ø> (ø)
...ic/Agent/Core/DataTransport/Client/NRHttpClient.cs 73.33% <ø> (ø)
...elic/Agent/Core/DataTransport/HttpCollectorWire.cs 88.13% <100.00%> (ø)
...c/Agent/Core/DataTransport/DataStreamingService.cs 81.60% <0.00%> (-0.66%) ⬇️

@@ -62,9 +62,9 @@ public string SendData(string method, ConnectionInfo connectionInfo, string seri
foreach (var header in _requestHeadersMap)
request.Headers.Add(header.Key, header.Value);

using var response = httpClient.SendAsync(request).GetAwaiter().GetResult();
using var response = httpClient.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nrcventura pointed out (from the doc I linked in the description) that .ConfigureAwait(false).GetAwaiter().GetResult() does exactly nothing different than just .GetAwaiter().GetResult() -- and that was the one area I was really hoping we'd be able to gain some improvement around.

I will note, however, that the .SendAsync() call here is actually calling into NRHttpClient.SendAsync() where the added .ConfigureAwait(false) is actually useful. So, while this change may not do anything beneficial here, it also does no harm.

@@ -322,10 +322,10 @@ public async Task TryInjectBrowserScriptAsync(string contentType, string request
if (rumBytes == null)
{
transaction.LogFinest("Skipping RUM Injection: No script was available.");
await baseStream.WriteAsync(buffer, 0, buffer.Length);
await baseStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this change is safe or not, because it may rely on data from the synchronization context.

@@ -25,7 +25,7 @@ public static async Task InjectBrowserScriptAsync(byte[] buffer, Stream baseStre
{
// not found, can't inject anything
transaction?.LogFinest("Skipping RUM Injection: No suitable location found to inject script.");
await baseStream.WriteAsync(buffer, 0, buffer.Length);
await baseStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this stream requires data on the synchronization context.

@@ -65,7 +65,7 @@ protected void TestConnection()
wc.DownloadString(testAddress);
}
#else
_lazyHttpClient.Value.GetAsync(testAddress).GetAwaiter().GetResult();
_lazyHttpClient.Value.GetAsync(testAddress).ConfigureAwait(false).GetAwaiter().GetResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage of ConfigureAwait does nothing.

@@ -39,7 +39,7 @@ public async Task Invoke(HttpContext context)
{
_agent.Logger.Log(Agent.Extensions.Logging.Level.Finest, "Skipping instrumenting incoming OPTIONS request.");

await _next(context);
await _next(context).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is safe to do. AspNetCore does not have an synchronization context by default, but if one was configured, I don't know if there would be problems with other middleware in the pipeline.

@@ -77,7 +77,7 @@ public override void Write(byte[] buffer, int offset, int count)
// Set a flag on the context to indicate we're in the middle of injecting - prevents multiple recursions when response compression is in use
StartInjecting();
_agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, buffer, _baseStream)
.GetAwaiter().GetResult();
.ConfigureAwait(false).GetAwaiter().GetResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ConfigureAwait call does nothing.

@@ -102,7 +102,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
{
// Set a flag on the context to indicate we're in the middle of injecting - prevents multiple recursions when response compression is in use
StartInjecting();
await _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, buffer.ToArray(), _baseStream);
await _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, buffer.ToArray(), _baseStream).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this would cause problems if a synchronization was added by someone.

@@ -39,7 +39,7 @@ public async Task Invoke(HttpContext context)
{
_agent.Logger.Log(Agent.Extensions.Logging.Level.Finest, "Not instrumenting incoming OPTIONS request.");

await _next(context);
await _next(context).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the WrapPipelineMiddleware for older aspnetcore versions, I don't know if this could cause problems if someone added a synchronization context.

@@ -54,7 +54,7 @@ public async Task Send(ConsumeContext context, IPipe<ConsumeContext> next)

var segment = transaction.StartMessageBrokerSegment(mc, MessageBrokerDestinationType.Queue, MessageBrokerAction.Consume, MessageBrokerVendorName, queueData.QueueName);

await next.Send(context);
await next.Send(context).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this would cause problems for other filters in the pipeline if there is a synchronization context.

@@ -55,7 +55,7 @@ public async Task Send(ConsumeContext context, IPipe<ConsumeContext> next)

var segment = transaction.StartMessageBrokerSegment(mc, MessageBrokerDestinationType.Queue, MessageBrokerAction.Consume, MessageBrokerVendorName, queueData.QueueName);

await next.Send(context);
await next.Send(context).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as the non-legacy filter.

@@ -56,7 +56,7 @@ public override async Task Invoke(IOwinContext context)

try
{
await _next.Invoke(context);
await _next.Invoke(context).ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this would cause problems with other middelwares if a synchronization is present.

Copy link
Member

@nrcventura nrcventura left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there is some increased risk (or at least unknowns) related to these changes. We should either address the unknowns, or reduce the scope of the changes to just the code that we use for sending data to New Relic.

@tippmar-nr tippmar-nr closed this Jan 8, 2024
@tippmar-nr tippmar-nr deleted the chore/configure-await-false branch January 8, 2024 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants