Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace System.Diagnostics.Metrics
{
Expand Down Expand Up @@ -88,6 +89,17 @@ 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);
Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking about it and this seems OK - I didn't come up with another place where the MeterListener static constructor would be a problem. In the future if we did discover any more it might be worthwhile to move the runtime metrics initialization out of the static constructor and instead do it as a lazy initialization within the MeterListener instance constructor.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we can consider moving the initialization later out of the static constructor.


List<MeterListener>? allListeners = null;
lock (Instrument.SyncObject)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public void Dispose()
}
}

// Publish is called from Instrument.Publish
// GetAllListeners is called from Instrument.Publish inside Instrument.SyncObject lock.
internal static List<MeterListener>? GetAllListeners() => s_allStartedListeners.Count == 0 ? null : new List<MeterListener>(s_allStartedListeners);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down