When MeterListener.RecordObservableInstruments() is called concurrently from multiple threads, an Exception is thrown.
ArgumentException: An item with the same key has already been added. Key: OracleMetricsRepro[23212,1]
at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException[T](T key)
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at Oracle.ManagedDataAccess.Client.OracleMetricsCollection.GetNumberOfActiveConnections()
at Oracle.ManagedDataAccess.Client.OracleMetricsInstrument.GetMeasurements()
at System.Diagnostics.Metrics.ObservableInstrument`1.Observe(Object callback)
at System.Diagnostics.Metrics.ObservableGauge`1.Observe()
at System.Diagnostics.Metrics.ObservableInstrument`1.Observe(MeterListener listener)
at System.Diagnostics.Metrics.MeterListener.RecordObservableInstruments()
This behavior can be reproduced with this simple program (might need to be started multiple times, it doesn't happen every time):
using Oracle.ManagedDataAccess.Client;
using System.Diagnostics.Metrics;
internal class Program
{
private static MeterListener listener;
public static async Task Main(string[] args)
{
listener = new MeterListener();
listener.SetMeasurementEventCallback<long>((inst, value, tags, state) =>
{
Console.WriteLine(value);
});
listener.InstrumentPublished = (instrument, l) =>
{
if (instrument.Meter.Name == "Oracle.ManagedDataAccess.Core" &&
instrument.Name == "odp.number_of_active_connections")
{
l.EnableMeasurementEvents(instrument);
}
};
listener.Start();
var conn = new OracleConnection("TODO");
await conn.OpenAsync(); // open connection so that metrics are available
await Task.WhenAll(DummyAsync(), DummyAsync());
}
public static async Task DummyAsync()
{
await Task.Delay(10); // add delay so that RecordObservableInstruments is not called syncronously
listener.RecordObservableInstruments(); // make ODP.NET emit the metric value
}
}
When MeterListener.RecordObservableInstruments() is called concurrently from multiple threads, an Exception is thrown.
This behavior can be reproduced with this simple program (might need to be started multiple times, it doesn't happen every time):