Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,6 @@ protected void Publish()
return;
}

// MeterListener has a static constructor that creates runtime metrics instruments.
// We need to ensure this static constructor is called before starting to publish the instrument.
// This is necessary because creating runtime metrics instruments will cause re-entry to the Publish method,
// potentially resulting in a deadlock due to the SyncObject lock.
// Sequence of the deadlock:
// 1. An application creates an early instrument (e.g., Counter) before the MeterListener static constructor is executed.
// 2. Instrument.Publish is called and enters the SyncObject lock.
// 3. Within the lock block, MeterListener is called, triggering its static constructor.
// 4. The static constructor creates runtime metrics instruments, causing re-entry to Instrument.Publish and leading to a deadlock.
RuntimeHelpers.RunClassConstructor(typeof(MeterListener).TypeHandle);

List<MeterListener>? allListeners = null;
lock (Instrument.SyncObject)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,17 @@ public sealed class MeterListener : IDisposable
private MeasurementCallback<double> _doubleMeasurementCallback = (instrument, measurement, tags, state) => { /* no-op */ };
private MeasurementCallback<decimal> _decimalMeasurementCallback = (instrument, measurement, tags, state) => { /* no-op */ };

static MeterListener()
/// <summary>
/// Creates a MeterListener object.
/// </summary>
public MeterListener()
{
#if NET9_0_OR_GREATER
// This ensures that the static Meter gets created before any listeners exist.
_ = RuntimeMetrics.IsEnabled();
RuntimeMetrics.EnsureInitialized();
#endif
}

/// <summary>
/// Creates a MeterListener object.
/// </summary>
public MeterListener() { }

/// <summary>
/// Callbacks to get notification when an instrument is published.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ internal static class RuntimeMetrics

private static readonly int s_maxGenerations = Math.Min(GC.GetGCMemoryInfo().GenerationInfo.Length, s_genNames.Length);

public static void EnsureInitialized()
{
// Dummy method to ensure that the static constructor have run and created the meters
}

static RuntimeMetrics()
{
AppDomain.CurrentDomain.FirstChanceException += (source, e) =>
Expand All @@ -33,6 +38,8 @@ static RuntimeMetrics()
};
}

#pragma warning disable CA1823 // suppress unused fields warning, as the fields are used to keep the meters alive

private static readonly ObservableCounter<long> s_gcCollections = s_meter.CreateObservableCounter(
"dotnet.gc.collections",
GetGarbageCollectionCounts,
Expand Down Expand Up @@ -156,28 +163,7 @@ static RuntimeMetrics()
unit: "s",
description: "CPU time used by the process.");

public static bool IsEnabled()
{
return s_gcCollections.Enabled
|| s_processWorkingSet.Enabled
|| s_gcHeapTotalAllocated.Enabled
|| s_gcLastCollectionMemoryCommitted.Enabled
|| s_gcLastCollectionHeapSize.Enabled
|| s_gcLastCollectionFragmentationSize.Enabled
|| s_gcPauseTime.Enabled
|| s_jitCompiledSize.Enabled
|| s_jitCompiledMethodCount.Enabled
|| s_jitCompilationTime.Enabled
|| s_monitorLockContention.Enabled
|| s_timerCount.Enabled
|| s_threadPoolThreadCount.Enabled
|| s_threadPoolCompletedWorkItems.Enabled
|| s_threadPoolQueueLength.Enabled
|| s_assembliesCount.Enabled
|| s_exceptions.Enabled
|| s_processCpuCount.Enabled
|| s_processCpuTime?.Enabled is true;
}
#pragma warning restore CA1823

private static IEnumerable<Measurement<long>> GetGarbageCollectionCounts()
{
Expand Down