Skip to content

Exception when concurrently getting Metrics #477

@lschloetterer

Description

@lschloetterer

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
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions