oracle / oracle/dotnet-db-samples

Exception when concurrently getting Metrics

Open
#477 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C#
Stars
434
Forks
191
Avg merge
1d 15h
Merged PRs (30d)
1

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduction program and the stack trace through MeterListener.RecordObservableInstruments(), OracleMetricsInstrument.GetMeasurements(), and OracleMetricsCollection.GetNumberOfActiveConnections(). Run two concurrent calls after opening an OracleConnection to reproduce the failure. Done means concurrent metric collection no longer throws the duplicate-key exception.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.