-
Notifications
You must be signed in to change notification settings - Fork 293
Description
This is the same as #341 (I can't re-open that one, though). Now I have a root cause and a fix ready to go.
Repro Steps
Compile and run this console app on .NET Core referencing the latest ApplicationInsights (base) NuGet (actually it repros as far back as 2.4.0):
using System;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
namespace AINullListenerRepro
{
class Program
{
static void Main(string[] args)
{
var t1 = Task.Run(Race1);
var t2 = Task.Run(Race2);
Console.WriteLine("Running...");
Console.ReadKey();
}
static async Task Race1()
{
while (true)
{
using (var channel = new InMemoryChannel())
{
await Task.Yield();
}
}
}
static async Task Race2()
{
while (true)
{
using (var telemetryConfiguration = TelemetryConfiguration.Active)
{
await Task.Yield();
}
}
}
}
}Actual Behavior
After a few seconds:
NullReferenceException in DiagnosticsEventListener.OnEventWritten.
The listener field is null.
Expected Behavior
No exception.
Version Info
SDK Version : 2.4.0 to 2.9.1
.NET Version : .NET Core 2.2 (although I think it repros on other platforms too)
How Application was onboarded with SDK: VisualStudio
OS : Windows 10
Hosting Info (IIS/Azure WebApps/ etc) : N/A
This issue has been plaguing my unit tests for Snapshot Debugger for that last week or so. It's timing dependent.
Explanation for the repro:
- Race1 just creates and disposes an InMemoryChannel over and over. That has the effect of reporting "No Telemetry Items passed to Enqueue" to the CoreEventSource over and over.
- Race2 creates and disposes the TelemetryConfiguration.Active. A lot happens there, but the important thing for the repro is that it adds the DiagnosticsTelemtryModule which establishes the DiagnosticsEventListener.
The two threads race with each other. At some point, the DiagnosticsEventListener constructor begins. It first calls the base class; note that the listener and logLevel fields are not yet set -- they're still null and zero. During the base class constructor, the OnEventSourceCreated virtual method is called. This is unusual; a quirk of EventListener. We detect that the event source is the CoreEventSource and enable it with AllKeyword and a logLevel of zero - LogAlways (that's a bug actually because it doesn't honor the logLevel that was passed in the constructor, but it's not this bug). Now that the event source is enabled, the other thread gets a chance to run -- to write an event. DiagnosticsEventListener's constructor still hasn't completed, but it gets a callback on OnEventWritten and gets all the way to the last line where listener is still null. NRE.