From b1c546ce957936d2fab39fc9c8396966fb96df31 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 18 Mar 2026 12:16:57 +0000 Subject: [PATCH 1/7] CSHARP-5935: Command activities may be skipped when using pooled connection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was looking at flaky tests, and found MongoClient_should_create_activities_when_tracing_enabled was failing occasionally. Turns out it was an actual bug. Junie (Opus 4.6) says: ### Root Cause `_shouldTrace` in `CommandEventHelper` was set **once** at connection construction time via `MongoTelemetry.ActivitySource.HasListeners()`. Since connections are pooled and reused, if a connection was created before an `ActivityListener` was registered (or by a test with tracing disabled), `_shouldTrace` remained `false` permanently for that connection — command activities were never created even when a listener was later active. --- .../Core/Connections/CommandEventHelper.cs | 10 +++++----- .../LibmongocryptTests.cs | 3 ++- .../OpenTelemetryTests.cs | 8 ++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index 049d5630bd8..5784b0b08c7 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -41,7 +41,7 @@ internal class CommandEventHelper private readonly bool _shouldTrackState; private readonly bool _shouldTrackFailed; private readonly bool _shouldTrackSucceeded; - private readonly bool _shouldTrace; + private readonly bool _tracingDisabled; private readonly int _queryTextMaxLength; private Activity _currentCommandActivity; @@ -54,10 +54,10 @@ public CommandEventHelper(EventLogger eventLogger, Tracin _shouldTrackFailed = _eventLogger.IsEventTracked(); _shouldTrackStarted = _eventLogger.IsEventTracked(); - _shouldTrace = tracingOptions?.Disabled != true && MongoTelemetry.ActivitySource.HasListeners(); + _tracingDisabled = tracingOptions?.Disabled == true; _queryTextMaxLength = tracingOptions?.QueryTextMaxLength ?? 0; - _shouldTrackState = _shouldTrackSucceeded || _shouldTrackFailed || _shouldTrace; + _shouldTrackState = _shouldTrackSucceeded || _shouldTrackFailed || !_tracingDisabled; _shouldProcessRequestMessages = _shouldTrackStarted || _shouldTrackState; if (_shouldTrackState) @@ -223,7 +223,7 @@ public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? public void ConnectionFailed(ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - if (!_shouldTrackFailed && !_shouldTrace) + if (!_shouldTrackFailed && _tracingDisabled) { return; } @@ -746,7 +746,7 @@ private void TrackCommandState( ShouldRedactReply = shouldRedactCommand }; - if (_shouldTrace && !shouldRedactCommand && !skipLogging) + if (!_tracingDisabled && MongoTelemetry.ActivitySource.HasListeners() && !shouldRedactCommand && !skipLogging) { _currentCommandActivity = MongoTelemetry.StartCommandActivity( commandName, diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs index 037b9e3b2b8..b9f28a78f4c 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs @@ -59,7 +59,8 @@ public void Explicit_encryption_with_libmongocrypt_package_works() kmsProviders.Add("local", localKey); var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); - var keyVaultMongoClient = new MongoClient(); + var connectionString = Environment.GetEnvironmentVariable("MONGODB_URI"); + var keyVaultMongoClient = string.IsNullOrWhiteSpace(connectionString) ? new MongoClient() : new MongoClient(connectionString); var clientEncryptionSettings = new ClientEncryptionOptions( keyVaultMongoClient, keyVaultNamespace, diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs index b69d08d7650..b4a4ded411b 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs @@ -13,9 +13,11 @@ * limitations under the License. */ +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using FluentAssertions; using MongoDB.Bson; using MongoDB.Driver.Core.Configuration; @@ -43,6 +45,8 @@ public void MongoClient_should_create_activities_when_tracing_enabled() collection.InsertOne(new BsonDocument("name", "test")); collection.Find(Builders.Filter.Empty).FirstOrDefault(); collection.DeleteOne(Builders.Filter.Eq("name", "test")); + + SpinWait.SpinUntil(() => capturedActivities.Count >= 6, millisecondsTimeout: 10000); } finally { @@ -84,9 +88,9 @@ public void MongoClient_should_not_create_activities_when_tracing_disabled() capturedActivities.Should().BeEmpty(); } - private static ActivityListener CreateActivityListener(out List capturedActivities) + private static ActivityListener CreateActivityListener(out IReadOnlyCollection capturedActivities) { - var activities = new List(); + var activities = new ConcurrentBag(); var listener = new ActivityListener { ShouldListenTo = source => source.Name == MongoTelemetry.ActivitySourceName, From fc9acaa06b8da7bcfe26f5015a36e9cd8bdfc1fe Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 18 Mar 2026 12:44:26 +0000 Subject: [PATCH 2/7] Updates from copilot feedback. --- src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs | 2 +- .../MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index 5784b0b08c7..c49c8c1f0f1 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -223,7 +223,7 @@ public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? public void ConnectionFailed(ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - if (!_shouldTrackFailed && _tracingDisabled) + if (!_shouldTrackFailed && (_tracingDisabled || !MongoTelemetry.ActivitySource.HasListeners())) { return; } diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs index b9f28a78f4c..6c461bc6a7e 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs @@ -59,8 +59,7 @@ public void Explicit_encryption_with_libmongocrypt_package_works() kmsProviders.Add("local", localKey); var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); - var connectionString = Environment.GetEnvironmentVariable("MONGODB_URI"); - var keyVaultMongoClient = string.IsNullOrWhiteSpace(connectionString) ? new MongoClient() : new MongoClient(connectionString); + var keyVaultMongoClient = new MongoClient(InfrastructureUtilities.MongoUri); var clientEncryptionSettings = new ClientEncryptionOptions( keyVaultMongoClient, keyVaultNamespace, From 287ed716629a5622db0b0830d7043d93d52155fd Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 19 Mar 2026 13:47:45 +0000 Subject: [PATCH 3/7] Summary I've successfully refactored the code to fix the bug where activity listeners were being checked when the connection was created (at which point none may be set), but not checked again when the connection was pulled from the pool. Key Changes: Constructor (CommandEventHelper.cs:60): Removed the listener check from _shouldTrackState initialization. Now _shouldTrackState only depends on whether event tracking is needed (_shouldTrackSucceeded || _shouldTrackFailed), not on tracing with listeners. Property Getters (CommandEventHelper.cs:71-94): Updated all property getters (ShouldCallBeforeSending, ShouldCallAfterSending, etc.) to dynamically check MongoTelemetry.ActivitySource.HasListeners() at execution time, ensuring that activity tracing is detected when connections are obtained from the pool. Lazy State Initialization (CommandEventHelper.cs:37, 741-745): Changed _state from readonly to allow lazy initialization. The state dictionary is now only created when needed - either for event tracking or when tracing with listeners is detected at runtime. Null Safety (CommandEventHelper.cs:132, 165, 192, 216, 233): Added null checks before accessing _state in all methods to handle cases where state tracking hasn't been initialized. TrackCommandState (CommandEventHelper.cs:734-770): Updated to check for listeners at execution time and lazily initialize the state dictionary only when tracing is actually needed. Test Update (CommandEventHelperTests.cs:122-124): Updated the test to reflect the new behavior where _shouldTrackState only reflects event tracking, not tracing with listeners (which is now checked dynamically). Benefits: Bug Fixed: Activity listeners are now checked when connections are obtained from the pool, not just when they're created No Unnecessary State Tracking: State tracking is only enabled when actually needed (either for events or when listeners are registered), avoiding the performance overhead of always tracking state Backward Compatible: The behavior is the same for all scenarios, just more efficient The changes ensure that the driver properly detects activity listeners whenever a connection is used, whether it's newly created or retrieved from the pool, while avoiding unnecessary state tracking when no listeners are registered. --- .../Core/Connections/CommandEventHelper.cs | 91 +++++++++++-------- .../Connections/CommandEventHelperTests.cs | 4 +- .../LibmongocryptTests.cs | 2 +- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index c49c8c1f0f1..fee180856e5 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -34,7 +34,7 @@ namespace MongoDB.Driver.Core.Connections internal class CommandEventHelper { private readonly EventLogger _eventLogger; - private readonly ConcurrentDictionary _state; + private ConcurrentDictionary _state; private readonly bool _shouldProcessRequestMessages; private readonly bool _shouldTrackStarted; @@ -57,7 +57,7 @@ public CommandEventHelper(EventLogger eventLogger, Tracin _tracingDisabled = tracingOptions?.Disabled == true; _queryTextMaxLength = tracingOptions?.QueryTextMaxLength ?? 0; - _shouldTrackState = _shouldTrackSucceeded || _shouldTrackFailed || !_tracingDisabled; + _shouldTrackState = _shouldTrackSucceeded || _shouldTrackFailed; _shouldProcessRequestMessages = _shouldTrackStarted || _shouldTrackState; if (_shouldTrackState) @@ -68,30 +68,18 @@ public CommandEventHelper(EventLogger eventLogger, Tracin } } - public bool ShouldCallBeforeSending - { - get { return _shouldProcessRequestMessages; } - } + public bool ShouldCallBeforeSending => _shouldProcessRequestMessages || ShouldTraceWithActivityListener(); - public bool ShouldCallAfterSending - { - get { return _shouldTrackState; } - } + public bool ShouldCallAfterSending => _shouldTrackState || ShouldTraceWithActivityListener(); - public bool ShouldCallErrorSending - { - get { return _shouldTrackState; } - } + public bool ShouldCallErrorSending => _shouldTrackState || ShouldTraceWithActivityListener(); - public bool ShouldCallAfterReceiving - { - get { return _shouldTrackState; } - } + public bool ShouldCallAfterReceiving => _shouldTrackState || ShouldTraceWithActivityListener(); - public bool ShouldCallErrorReceiving - { - get { return _shouldTrackState; } - } + public bool ShouldCallErrorReceiving => _shouldTrackState || ShouldTraceWithActivityListener(); + + private bool ShouldTraceWithActivityListener() + => !_tracingDisabled && MongoTelemetry.ActivitySource.HasListeners(); public void CompleteFailedCommandActivity(Exception exception) { @@ -129,8 +117,12 @@ public void BeforeSending( public void AfterSending(RequestMessage message, ConnectionId connectionId, ObjectId? serviceId, bool skipLogging) { - CommandState state; - if (_state.TryGetValue(message.RequestId, out state) && + if (_state == null) + { + return; + } + + if (_state.TryGetValue(message.RequestId, out var state) && state.ExpectedResponseType == ExpectedResponseType.None) { state.Stopwatch.Stop(); @@ -157,12 +149,16 @@ public void AfterSending(RequestMessage message, ConnectionId connectionId, Obje public void ErrorSending(RequestMessage message, ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - CommandState state; - if (_state.TryRemove(message.RequestId, out state)) + CompleteCommandActivityWithException(exception); + + if (_state == null) { - state.Stopwatch.Stop(); + return; + } - CompleteCommandActivityWithException(exception); + if (_state.TryRemove(message.RequestId, out var state)) + { + state.Stopwatch.Stop(); _eventLogger.LogAndPublish(new CommandFailedEvent( state.CommandName, @@ -179,8 +175,12 @@ public void ErrorSending(RequestMessage message, ConnectionId connectionId, Obje public void AfterReceiving(ResponseMessage message, IByteBuffer buffer, ConnectionId connectionId, ObjectId? serviceId, MessageEncoderSettings encoderSettings, bool skipLogging) { - CommandState state; - if (!_state.TryRemove(message.ResponseTo, out state)) + if (_state == null) + { + return; + } + + if (!_state.TryRemove(message.ResponseTo, out var state)) { // this indicates a bug in the sending portion... return; @@ -198,8 +198,14 @@ public void AfterReceiving(ResponseMessage message, IByteBuffer buffer, Connecti public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - CommandState state; - if (!_state.TryRemove(responseTo, out state)) + CompleteCommandActivityWithException(exception); + + if (_state == null) + { + return; + } + + if (!_state.TryRemove(responseTo, out var state)) { // this indicates a bug in the sending portion... return; @@ -207,8 +213,6 @@ public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? state.Stopwatch.Stop(); - CompleteCommandActivityWithException(exception); - _eventLogger.LogAndPublish(new CommandFailedEvent( state.CommandName, state.QueryNamespace.DatabaseNamespace, @@ -223,13 +227,18 @@ public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? public void ConnectionFailed(ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - if (!_shouldTrackFailed && (_tracingDisabled || !MongoTelemetry.ActivitySource.HasListeners())) + if (!_shouldTrackFailed && !ShouldTraceWithActivityListener()) { return; } CompleteCommandActivityWithException(exception); + if (_state == null) + { + return; + } + var requestIds = _state.Keys; foreach (var requestId in requestIds) { @@ -731,11 +740,19 @@ private void TrackCommandState( BsonDocument sessionId, long? transactionNumber) { - if (!_shouldTrackState) + var shouldTraceCommand = ShouldTraceWithActivityListener() && !shouldRedactCommand && !skipLogging; + + if (!_shouldTrackState && !shouldTraceCommand) { return; } + if (!_shouldTrackState && shouldTraceCommand) + { + // Lazy initialize state dictionary when tracing is needed but event tracking is not + _state ??= new ConcurrentDictionary(); + } + var commandState = new CommandState { CommandName = commandName, @@ -746,7 +763,7 @@ private void TrackCommandState( ShouldRedactReply = shouldRedactCommand }; - if (!_tracingDisabled && MongoTelemetry.ActivitySource.HasListeners() && !shouldRedactCommand && !skipLogging) + if (shouldTraceCommand) { _currentCommandActivity = MongoTelemetry.StartCommandActivity( commandName, diff --git a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs index ded46b101af..ec71a075c10 100644 --- a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs @@ -119,7 +119,9 @@ public void ShouldTrackState_should_be_correct_with_activity_listener( var tracingOptions = traceCommands ? new TracingOptions() : new TracingOptions { Disabled = true }; var commandHelper = new CommandEventHelper(eventLogger, tracingOptions); - commandHelper._shouldTrackState().Should().Be(logCommands || captureCommandSucceeded || captureCommandFailed || traceCommands); + // With the new implementation, _shouldTrackState only reflects event tracking, + // not tracing with listeners (which is checked dynamically at execution time) + commandHelper._shouldTrackState().Should().Be(logCommands || captureCommandSucceeded || captureCommandFailed); } finally { diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs index 6c461bc6a7e..037b9e3b2b8 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/LibmongocryptTests.cs @@ -59,7 +59,7 @@ public void Explicit_encryption_with_libmongocrypt_package_works() kmsProviders.Add("local", localKey); var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); - var keyVaultMongoClient = new MongoClient(InfrastructureUtilities.MongoUri); + var keyVaultMongoClient = new MongoClient(); var clientEncryptionSettings = new ClientEncryptionOptions( keyVaultMongoClient, keyVaultNamespace, From ec24e94fcdfc1c1b7a49272cbbc4aef235c19e5e Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 26 Mar 2026 14:14:43 +0000 Subject: [PATCH 4/7] Updates based on review feedback. --- .../Core/Connections/CommandEventHelper.cs | 22 +++-------- .../Connections/CommandEventHelperTests.cs | 38 ++++++++++++------- .../OpenTelemetryTests.cs | 3 -- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index fee180856e5..82a66482e98 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -149,13 +149,13 @@ public void AfterSending(RequestMessage message, ConnectionId connectionId, Obje public void ErrorSending(RequestMessage message, ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - CompleteCommandActivityWithException(exception); - if (_state == null) { return; } + CompleteFailedCommandActivity(exception); + if (_state.TryRemove(message.RequestId, out var state)) { state.Stopwatch.Stop(); @@ -198,13 +198,13 @@ public void AfterReceiving(ResponseMessage message, IByteBuffer buffer, Connecti public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - CompleteCommandActivityWithException(exception); - if (_state == null) { return; } + CompleteFailedCommandActivity(exception); + if (!_state.TryRemove(responseTo, out var state)) { // this indicates a bug in the sending portion... @@ -232,13 +232,13 @@ public void ConnectionFailed(ConnectionId connectionId, ObjectId? serviceId, Exc return; } - CompleteCommandActivityWithException(exception); - if (_state == null) { return; } + CompleteFailedCommandActivity(exception); + var requestIds = _state.Keys; foreach (var requestId in requestIds) { @@ -787,16 +787,6 @@ private void CompleteCommandActivityWithSuccess(BsonDocument reply = null) } } - private void CompleteCommandActivityWithException(Exception exception) - { - if (_currentCommandActivity is not null) - { - MongoTelemetry.RecordException(_currentCommandActivity, exception); - _currentCommandActivity.Dispose(); - _currentCommandActivity = null; - } - } - private void HandleCommandFailure( CommandState state, BsonDocument reply, diff --git a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs index ec71a075c10..3cf0adc266f 100644 --- a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs @@ -58,8 +58,7 @@ public void ShouldRedactCommand_should_return_expected_result(string commandJson public void ShouldTrackState_should_be_correct( [Values(false, true)] bool logCommands, [Values(false, true)] bool captureCommandSucceeded, - [Values(false, true)] bool captureCommandFailed, - [Values(false, true)] bool traceCommands) + [Values(false, true)] bool captureCommandFailed) { var mockLogger = new Mock>(); mockLogger.Setup(m => m.IsEnabled(LogLevel.Debug)).Returns(logCommands); @@ -76,7 +75,7 @@ public void ShouldTrackState_should_be_correct( } var eventLogger = new EventLogger(eventCapturer, mockLogger.Object); - var tracingOptions = traceCommands ? new TracingOptions() : new TracingOptions { Disabled = true }; + var tracingOptions = new TracingOptions { Disabled = true }; var commandHelper = new CommandEventHelper(eventLogger, tracingOptions); // No ActivityListener, so tracing doesn't contribute to _shouldTrackState @@ -85,7 +84,7 @@ public void ShouldTrackState_should_be_correct( [Theory] [ParameterAttributeData] - public void ShouldTrackState_should_be_correct_with_activity_listener( + public void Callbacks_turn_on_when_listener_is_added_even_if_no_events( [Values(false, true)] bool logCommands, [Values(false, true)] bool captureCommandSucceeded, [Values(false, true)] bool captureCommandFailed, @@ -94,13 +93,6 @@ public void ShouldTrackState_should_be_correct_with_activity_listener( ActivityListener listener = null; try { - listener = new ActivityListener - { - ShouldListenTo = source => source.Name == "MongoDB.Driver", - Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData - }; - ActivitySource.AddActivityListener(listener); - var mockLogger = new Mock>(); mockLogger.Setup(m => m.IsEnabled(LogLevel.Debug)).Returns(logCommands); @@ -119,9 +111,27 @@ public void ShouldTrackState_should_be_correct_with_activity_listener( var tracingOptions = traceCommands ? new TracingOptions() : new TracingOptions { Disabled = true }; var commandHelper = new CommandEventHelper(eventLogger, tracingOptions); - // With the new implementation, _shouldTrackState only reflects event tracking, - // not tracing with listeners (which is checked dynamically at execution time) - commandHelper._shouldTrackState().Should().Be(logCommands || captureCommandSucceeded || captureCommandFailed); + // When there are no listeners, these only return true if logging is enabled or an event is registered, + // regardless of whether tracing is enabled. + commandHelper.ShouldCallBeforeSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands); + commandHelper.ShouldCallAfterSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands); + commandHelper.ShouldCallErrorSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands); + commandHelper.ShouldCallAfterReceiving.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands); + commandHelper.ShouldCallErrorReceiving.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands); + + listener = new ActivityListener + { + ShouldListenTo = source => source.Name == "MongoDB.Driver", + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData + }; + ActivitySource.AddActivityListener(listener); + + // With listeners registered, these always return true when unless everything is disabled. + commandHelper.ShouldCallBeforeSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); + commandHelper.ShouldCallAfterSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); + commandHelper.ShouldCallErrorSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); + commandHelper.ShouldCallAfterReceiving.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); + commandHelper.ShouldCallErrorReceiving.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); } finally { diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs index b4a4ded411b..7e12f7146be 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using FluentAssertions; using MongoDB.Bson; using MongoDB.Driver.Core.Configuration; @@ -45,8 +44,6 @@ public void MongoClient_should_create_activities_when_tracing_enabled() collection.InsertOne(new BsonDocument("name", "test")); collection.Find(Builders.Filter.Empty).FirstOrDefault(); collection.DeleteOne(Builders.Filter.Eq("name", "test")); - - SpinWait.SpinUntil(() => capturedActivities.Count >= 6, millisecondsTimeout: 10000); } finally { From c10588c604bf665633abdd946983bb2b86ac30e7 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Thu, 2 Apr 2026 14:33:06 +0100 Subject: [PATCH 5/7] Minor fixes. --- .../Core/Connections/CommandEventHelperTests.cs | 2 +- .../MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs index 3cf0adc266f..c28f2e7a337 100644 --- a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs @@ -126,7 +126,7 @@ public void Callbacks_turn_on_when_listener_is_added_even_if_no_events( }; ActivitySource.AddActivityListener(listener); - // With listeners registered, these always return true when unless everything is disabled. + // With listeners registered, these always return true unless everything is disabled. commandHelper.ShouldCallBeforeSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); commandHelper.ShouldCallAfterSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); commandHelper.ShouldCallErrorSending.Should().Be(captureCommandSucceeded || captureCommandFailed || logCommands || traceCommands); diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs index 7e12f7146be..6a9b29b0d2f 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs @@ -13,7 +13,6 @@ * limitations under the License. */ -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -87,7 +86,7 @@ public void MongoClient_should_not_create_activities_when_tracing_disabled() private static ActivityListener CreateActivityListener(out IReadOnlyCollection capturedActivities) { - var activities = new ConcurrentBag(); + var activities = new List(); var listener = new ActivityListener { ShouldListenTo = source => source.Name == MongoTelemetry.ActivitySourceName, From 33ed4595023a7c6b74fcff5c70773d059897fb02 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 8 Apr 2026 11:37:01 +0100 Subject: [PATCH 6/7] Updates after comments from Boris. --- .../Core/Connections/CommandEventHelper.cs | 28 ++++++++----------- .../Connections/CommandEventHelperTests.cs | 8 +++--- .../OpenTelemetryTests.cs | 1 - 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index 82a66482e98..8b29c7d58dc 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -36,9 +36,9 @@ internal class CommandEventHelper private readonly EventLogger _eventLogger; private ConcurrentDictionary _state; - private readonly bool _shouldProcessRequestMessages; + private readonly bool _eventsNeedBeforeSending; private readonly bool _shouldTrackStarted; - private readonly bool _shouldTrackState; + private readonly bool _eventsNeedState; private readonly bool _shouldTrackFailed; private readonly bool _shouldTrackSucceeded; private readonly bool _tracingDisabled; @@ -57,10 +57,10 @@ public CommandEventHelper(EventLogger eventLogger, Tracin _tracingDisabled = tracingOptions?.Disabled == true; _queryTextMaxLength = tracingOptions?.QueryTextMaxLength ?? 0; - _shouldTrackState = _shouldTrackSucceeded || _shouldTrackFailed; - _shouldProcessRequestMessages = _shouldTrackStarted || _shouldTrackState; + _eventsNeedState = _shouldTrackSucceeded || _shouldTrackFailed; + _eventsNeedBeforeSending = _shouldTrackStarted || _eventsNeedState; - if (_shouldTrackState) + if (_eventsNeedState) { // we only need to track state if we have to raise // a succeeded or failed event or for tracing @@ -68,15 +68,15 @@ public CommandEventHelper(EventLogger eventLogger, Tracin } } - public bool ShouldCallBeforeSending => _shouldProcessRequestMessages || ShouldTraceWithActivityListener(); + public bool ShouldCallBeforeSending => _eventsNeedBeforeSending || ShouldTraceWithActivityListener(); - public bool ShouldCallAfterSending => _shouldTrackState || ShouldTraceWithActivityListener(); + public bool ShouldCallAfterSending => _eventsNeedState || ShouldTraceWithActivityListener(); - public bool ShouldCallErrorSending => _shouldTrackState || ShouldTraceWithActivityListener(); + public bool ShouldCallErrorSending => _eventsNeedState || ShouldTraceWithActivityListener(); - public bool ShouldCallAfterReceiving => _shouldTrackState || ShouldTraceWithActivityListener(); + public bool ShouldCallAfterReceiving => _eventsNeedState || ShouldTraceWithActivityListener(); - public bool ShouldCallErrorReceiving => _shouldTrackState || ShouldTraceWithActivityListener(); + public bool ShouldCallErrorReceiving => _eventsNeedState || ShouldTraceWithActivityListener(); private bool ShouldTraceWithActivityListener() => !_tracingDisabled && MongoTelemetry.ActivitySource.HasListeners(); @@ -742,16 +742,12 @@ private void TrackCommandState( { var shouldTraceCommand = ShouldTraceWithActivityListener() && !shouldRedactCommand && !skipLogging; - if (!_shouldTrackState && !shouldTraceCommand) + if (!_eventsNeedState && !shouldTraceCommand) { return; } - if (!_shouldTrackState && shouldTraceCommand) - { - // Lazy initialize state dictionary when tracing is needed but event tracking is not - _state ??= new ConcurrentDictionary(); - } + _state ??= new ConcurrentDictionary(); var commandState = new CommandState { diff --git a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs index c28f2e7a337..720c258f0f2 100644 --- a/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs +++ b/tests/MongoDB.Driver.Tests/Core/Connections/CommandEventHelperTests.cs @@ -78,8 +78,8 @@ public void ShouldTrackState_should_be_correct( var tracingOptions = new TracingOptions { Disabled = true }; var commandHelper = new CommandEventHelper(eventLogger, tracingOptions); - // No ActivityListener, so tracing doesn't contribute to _shouldTrackState - commandHelper._shouldTrackState().Should().Be(logCommands || captureCommandSucceeded || captureCommandFailed); + // No ActivityListener, so tracing doesn't contribute to _eventsNeedState + commandHelper._eventsNeedState().Should().Be(logCommands || captureCommandSucceeded || captureCommandFailed); } [Theory] @@ -142,8 +142,8 @@ public void Callbacks_turn_on_when_listener_is_added_even_if_no_events( internal static class CommandEventHelperReflector { - public static bool _shouldTrackState(this CommandEventHelper commandEventHelper) => - (bool)Reflector.GetFieldValue(commandEventHelper, nameof(_shouldTrackState)); + public static bool _eventsNeedState(this CommandEventHelper commandEventHelper) => + (bool)Reflector.GetFieldValue(commandEventHelper, nameof(_eventsNeedState)); public static bool ShouldRedactCommand(BsonDocument command) => diff --git a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs index 6a9b29b0d2f..a0d99c5fc63 100644 --- a/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs +++ b/tests/SmokeTests/MongoDB.Driver.SmokeTests.Sdk/OpenTelemetryTests.cs @@ -26,7 +26,6 @@ namespace MongoDB.Driver.SmokeTests.Sdk [Trait("Category", "Integration")] public sealed class OpenTelemetryTests { - [Fact] public void MongoClient_should_create_activities_when_tracing_enabled() { From 32cd3215acdec7a720238788eefe5fc32953969a Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Mon, 13 Apr 2026 14:57:30 +0100 Subject: [PATCH 7/7] Added an explicit API for ShouldCallConnectionFailed to make it look like the other methods, even though it is not really needed. --- .../Core/Connections/BinaryConnection.cs | 6 +++++- .../Core/Connections/CommandEventHelper.cs | 12 ++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/BinaryConnection.cs b/src/MongoDB.Driver/Core/Connections/BinaryConnection.cs index 43a5723dab1..3afd74bfc53 100644 --- a/src/MongoDB.Driver/Core/Connections/BinaryConnection.cs +++ b/src/MongoDB.Driver/Core/Connections/BinaryConnection.cs @@ -155,7 +155,11 @@ private void ConnectionFailed(Exception exception) { _failedEventHasBeenRaised = true; _eventLogger.LogAndPublish(new ConnectionFailedEvent(_connectionId, exception)); - _commandEventHelper.ConnectionFailed(_connectionId, _description?.ServiceId, exception, IsInitializing); + + if (_commandEventHelper.ShouldCallConnectionFailed) + { + _commandEventHelper.ConnectionFailed(_connectionId, _description?.ServiceId, exception, IsInitializing); + } } } diff --git a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs index 8b29c7d58dc..f24ec0e3060 100644 --- a/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs +++ b/src/MongoDB.Driver/Core/Connections/CommandEventHelper.cs @@ -78,6 +78,8 @@ public CommandEventHelper(EventLogger eventLogger, Tracin public bool ShouldCallErrorReceiving => _eventsNeedState || ShouldTraceWithActivityListener(); + public bool ShouldCallConnectionFailed => (_shouldTrackFailed || ShouldTraceWithActivityListener()) && _state != null; + private bool ShouldTraceWithActivityListener() => !_tracingDisabled && MongoTelemetry.ActivitySource.HasListeners(); @@ -227,16 +229,6 @@ public void ErrorReceiving(int responseTo, ConnectionId connectionId, ObjectId? public void ConnectionFailed(ConnectionId connectionId, ObjectId? serviceId, Exception exception, bool skipLogging) { - if (!_shouldTrackFailed && !ShouldTraceWithActivityListener()) - { - return; - } - - if (_state == null) - { - return; - } - CompleteFailedCommandActivity(exception); var requestIds = _state.Keys;