Skip to content

Commit

Permalink
Simplify initialization of RuntimeMetrics
Browse files Browse the repository at this point in the history
- Trigger the RuntimeMetrics initialization only when actually needed in the MeterListener constructor.
- Delete the lock-ordering workaround and wrong comment introduced in dotnet#105259. Trigger the RuntimeMetrics initialization only when actually needed should make the lock-ordering workarond unnecessary.
  • Loading branch information
jkotas committed Jul 26, 2024
1 parent e7f3ff1 commit b6434e3
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 40 deletions.
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

0 comments on commit b6434e3

Please sign in to comment.