Skip to content

Commit 11c0241

Browse files
authored
fix: Eliminate intermittent deadlock on agent startup. (#2183) (#2184)
1 parent 445f7c2 commit 11c0241

File tree

7 files changed

+145
-11
lines changed

7 files changed

+145
-11
lines changed

src/Agent/NewRelic/Agent/Core/AgentShim.cs

+5-10
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ static void Initialize()
2020
AgentInitializer.InitializeAgent();
2121
}
2222

23-
#if NETSTANDARD2_0
24-
static AgentShim()
25-
{
26-
Initialize();
27-
}
28-
#else
2923
private static bool _initialized = false;
3024
private static object _initLock = new object();
3125

@@ -49,6 +43,7 @@ static bool TryInitialize(string method)
4943
private static HashSet<string> _deferInitializationOnTheseMethods = new HashSet<string>
5044
{
5145
"System.Net.Http.HttpClient.SendAsync",
46+
"System.Net.Http.SocketsHttpHandler.SendAsync",
5247
"System.Net.HttpWebRequest.SerializeHeaders",
5348
"System.Net.HttpWebRequest.GetResponse"
5449
};
@@ -93,7 +88,6 @@ static bool DeferInitialization(string method)
9388
{
9489
return DeferInitializationOnTheseMethods.Contains(method);
9590
}
96-
#endif
9791

9892
/// <summary>
9993
/// Creates a tracer (if appropriate) and returns a delegate for the tracer's finish method.
@@ -112,12 +106,10 @@ public static Action<object, Exception> GetFinishTracerDelegate(
112106
object[] args,
113107
ulong functionId)
114108
{
115-
#if NETFRAMEWORK
116109
if (!_initialized)
117110
{
118111
if (!TryInitialize($"{typeName}.{methodName}")) return NoOpFinishTracer;
119112
}
120-
#endif
121113

122114
var tracer = GetTracer(
123115
tracerFactoryName,
@@ -325,7 +317,10 @@ public void FinishTracer(object returnValue, Exception exception)
325317

326318
/// <summary>
327319
/// Used to stop re-entry into the agent via AgentShim entry points when the call stack contains agent code already.
328-
/// The profiler stops reentry of GetTracer/FinishTracer twice in the same call stack, but it doesn't stop the agent from spinning up a background thread and then entering the agent from that thread. To resolve this, anytime a new thread is spun up (via any mechanism including async, Timer, Thread, ThreadPool, etc.) the work inside it needs to be wrapped in using (new IgnoreWork()) as a way of telling AgentShim to not re-enter.
320+
/// The profiler stops reentry of GetTracer/FinishTracer twice in the same call stack, but it doesn't stop the agent
321+
/// from spinning up a background thread and then entering the agent from that thread. To resolve this, anytime a
322+
/// new thread is spun up (via any mechanism including async, Timer, Thread, ThreadPool, etc.) the work inside it
323+
/// needs to be wrapped in using (new IgnoreWork()) as a way of telling AgentShim to not re-enter.
329324
/// </summary>
330325
public class IgnoreWork : IDisposable
331326
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.8.14">
10+
<PrivateAssets>all</PrivateAssets>
11+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12+
</PackageReference>
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\SharedApplications\Common\SharedApplicationHelpers\SharedApplicationHelpers.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Diagnostics;
6+
using NewRelic.Api.Agent;
7+
8+
namespace ConsoleInstrumentationStartup;
9+
10+
static class Program
11+
{
12+
[Transaction]
13+
static void Main()
14+
{
15+
Console.WriteLine($"{DateTime.Now} Main enter");
16+
17+
var stopWatch = new Stopwatch();
18+
stopWatch.Start();
19+
20+
NewRelic.Api.Agent.NewRelic.SetTransactionName("category", "name");
21+
22+
stopWatch.Stop();
23+
24+
Console.WriteLine($"{DateTime.Now} Main exit");
25+
}
26+
}

tests/Agent/IntegrationTests/IntegrationTestHelpers/NewRelicConfigModifier.cs

+6
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ public void SetHost(string host)
6464
host);
6565
}
6666

67+
public void SetHostPort(int port)
68+
{
69+
CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "service" }, "port",
70+
port.ToString());
71+
}
72+
6773
public void SetRequestTimeout(TimeSpan duration)
6874
{
6975
CommonUtils.ModifyOrCreateXmlAttributeInNewRelicConfig(_configFilePath, new[] { "configuration", "service" }, "requestTimeout",

tests/Agent/IntegrationTests/IntegrationTests.sln

+8-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreBasicWebApiApplic
134134
EndProject
135135
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedApplicationHelpers", "SharedApplications\Common\SharedApplicationHelpers\SharedApplicationHelpers.csproj", "{85A4B5C1-1248-4DE2-AE97-B96B6FA3AE09}"
136136
EndProject
137-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicAspNetCoreRazorApplication", "Applications\BasicAspNetCoreRazorApplication\BasicAspNetCoreRazorApplication.csproj", "{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF}"
137+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicAspNetCoreRazorApplication", "Applications\BasicAspNetCoreRazorApplication\BasicAspNetCoreRazorApplication.csproj", "{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF}"
138+
EndProject
139+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleInstrumentationStartup", "Applications\ConsoleInstrumentationStartup\ConsoleInstrumentationStartup.csproj", "{31DB04AF-2ED3-4379-98D7-7D02F38864F9}"
138140
EndProject
139141
Global
140142
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -358,6 +360,10 @@ Global
358360
{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
359361
{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
360362
{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF}.Release|Any CPU.Build.0 = Release|Any CPU
363+
{31DB04AF-2ED3-4379-98D7-7D02F38864F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
364+
{31DB04AF-2ED3-4379-98D7-7D02F38864F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
365+
{31DB04AF-2ED3-4379-98D7-7D02F38864F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
366+
{31DB04AF-2ED3-4379-98D7-7D02F38864F9}.Release|Any CPU.Build.0 = Release|Any CPU
361367
EndGlobalSection
362368
GlobalSection(SolutionProperties) = preSolution
363369
HideSolutionNode = FALSE
@@ -411,6 +417,7 @@ Global
411417
{69F04BE2-6859-45BB-97E7-E5ACDEE53503} = {F0F6F2CE-8AE8-49E1-8EE9-A44B451EFC29}
412418
{85A4B5C1-1248-4DE2-AE97-B96B6FA3AE09} = {30CF078E-E531-441E-83AB-24AB9B1C179F}
413419
{8F7EBBC3-B22F-43AC-978E-2CD8AD7C02CF} = {F0F6F2CE-8AE8-49E1-8EE9-A44B451EFC29}
420+
{31DB04AF-2ED3-4379-98D7-7D02F38864F9} = {F0F6F2CE-8AE8-49E1-8EE9-A44B451EFC29}
414421
EndGlobalSection
415422
GlobalSection(ExtensibilityGlobals) = postSolution
416423
SolutionGuid = {3830ABDF-4AEA-4D91-83A2-13F091D1DF5F}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Linq;
6+
using NewRelic.Agent.IntegrationTestHelpers;
7+
using NewRelic.Testing.Assertions;
8+
using Xunit;
9+
using Xunit.Abstractions;
10+
11+
namespace NewRelic.Agent.IntegrationTests.AgentFeatures
12+
{
13+
/// <summary>
14+
/// Tests that the agent doesn't deadlock at startup
15+
/// </summary>
16+
[NetCoreTest]
17+
public class InstrumentationStartupDeadlockTests : NewRelicIntegrationTest<RemoteServiceFixtures.ConsoleInstrumentationStartupFixtureCore>
18+
{
19+
private readonly RemoteServiceFixtures.ConsoleInstrumentationStartupFixtureCore _fixture;
20+
21+
public InstrumentationStartupDeadlockTests(RemoteServiceFixtures.ConsoleInstrumentationStartupFixtureCore fixture, ITestOutputHelper output) : base(fixture)
22+
{
23+
_fixture = fixture;
24+
_fixture.TestLogger = output;
25+
26+
_fixture.Actions
27+
(
28+
setupConfiguration: () =>
29+
{
30+
var configPath = fixture.DestinationNewRelicConfigFilePath;
31+
var configModifier = new NewRelicConfigModifier(configPath);
32+
33+
configModifier.SetHostPort(9999); // use a bogus port to generate an exception during HttpClient.SendAsync() on connect
34+
configModifier.SetSendDataOnExit();
35+
36+
configModifier.ForceTransactionTraces();
37+
configModifier.SetLogLevel("finest");
38+
configModifier.DisableEventListenerSamplers(); // Required for .NET 8 to pass.
39+
},
40+
exerciseApplication: () =>
41+
{
42+
}
43+
);
44+
45+
_fixture.Initialize();
46+
}
47+
48+
[Fact]
49+
public void Test()
50+
{
51+
var expectedLogLineRegexes = new[] { AgentLogBase.ShutdownLogLineRegex };
52+
53+
Assertions.LogLinesExist(expectedLogLineRegexes, _fixture.AgentLog.GetFileLines());
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
5+
using NewRelic.Agent.IntegrationTestHelpers.RemoteServiceFixtures;
6+
using System;
7+
using System.IO;
8+
using System.Reflection;
9+
10+
namespace NewRelic.Agent.IntegrationTests.RemoteServiceFixtures
11+
{
12+
public class ConsoleInstrumentationStartupFixtureCore : RemoteApplicationFixture
13+
{
14+
private static readonly string ApplicationDirectoryName = @"ConsoleInstrumentationStartup";
15+
private static readonly string ExecutableName = $"{ApplicationDirectoryName}.exe";
16+
17+
public ConsoleInstrumentationStartupFixtureCore()
18+
: base(new RemoteConsoleApplication(ApplicationDirectoryName, ExecutableName, ApplicationType.Bounded, true, true)
19+
.SetTimeout(TimeSpan.FromMinutes(2)))
20+
{
21+
22+
}
23+
24+
}
25+
}

0 commit comments

Comments
 (0)