From 488d35c0aaa4e952a101559decce936cb7fdf024 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 21 Aug 2026 14:20:28 +0200 Subject: [PATCH 1/2] chore: replace remaining ConfigureAwait(false) with NoContext in library code Closes out the last unresolved review comment from #561: library code uses the NoContext() extension for async continuations, but eight await sites still called ConfigureAwait(false) directly. Converts them in StoreFunctions, BaseTracer, ChannelExtensions, and the Sqlite/SqlServer projectors (whose GetConnection awaits had no configuration at all), and adds an IAsyncDisposable overload to TaskExtensions so `await using` can follow the same convention. Co-Authored-By: Claude Fable 5 --- .../Eventuous.Persistence/Diagnostics/Tracing/BaseTracer.cs | 4 ++-- .../src/Eventuous.Persistence/EventStore/StoreFunctions.cs | 2 +- src/Core/src/Eventuous.Shared/Tools/TaskExtensions.cs | 3 +++ .../Eventuous.Subscriptions/Channels/ChannelExtensions.cs | 2 +- .../Eventuous.SqlServer/Projections/SqlServerProjector.cs | 6 +++--- .../src/Eventuous.Sqlite/Projections/SqliteProjector.cs | 6 +++--- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/BaseTracer.cs b/src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/BaseTracer.cs index d1a320a6a..8f899bc25 100644 --- a/src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/BaseTracer.cs +++ b/src/Core/src/Eventuous.Persistence/Diagnostics/Tracing/BaseTracer.cs @@ -59,12 +59,12 @@ protected async IAsyncEnumerable TraceEnumerable( var enumerator = source.GetAsyncEnumerator(cancellationToken); - await using (enumerator.ConfigureAwait(false)) { + await using (enumerator.NoContext()) { while (true) { bool moved; try { - moved = await enumerator.MoveNextAsync().ConfigureAwait(false); + moved = await enumerator.MoveNextAsync().NoContext(); } catch (Exception e) { activity?.SetActivityStatus(ActivityStatus.Error(e)); measure.SetError(); diff --git a/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs b/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs index 18a5f430f..8334eaa57 100644 --- a/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs +++ b/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs @@ -140,7 +140,7 @@ CancellationToken cancellationToken try { var result = new List(); - await foreach (var evt in eventReader.ReadEventsBackwards(stream, start, count, cancellationToken).ConfigureAwait(false)) { + await foreach (var evt in eventReader.ReadEventsBackwards(stream, start, count, cancellationToken).NoContext(cancellationToken)) { result.Add(evt); } diff --git a/src/Core/src/Eventuous.Shared/Tools/TaskExtensions.cs b/src/Core/src/Eventuous.Shared/Tools/TaskExtensions.cs index 67d5d3870..cb600ff6b 100644 --- a/src/Core/src/Eventuous.Shared/Tools/TaskExtensions.cs +++ b/src/Core/src/Eventuous.Shared/Tools/TaskExtensions.cs @@ -25,6 +25,9 @@ static class TaskExtensions { public static ConfiguredCancelableAsyncEnumerable NoContext(this IAsyncEnumerable source, CancellationToken cancellationToken) => source.WithCancellation(cancellationToken).ConfigureAwait(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredAsyncDisposable NoContext(this IAsyncDisposable disposable) => disposable.ConfigureAwait(false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Task WhenAll(this IEnumerable tasks) => Task.WhenAll(tasks); diff --git a/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs index da029678f..b45c7ecf4 100644 --- a/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Channels/ChannelExtensions.cs @@ -114,7 +114,7 @@ [EnumeratorCancellation] CancellationToken cancellationToken // Propagate possible failure of the channel. if (source.Completion.IsCompleted) - await source.Completion.ConfigureAwait(false); + await source.Completion.NoContext(); } finally { timerCts.Dispose(); } } } diff --git a/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs b/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs index e3362edd2..5a11a8b3d 100644 --- a/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs +++ b/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs @@ -34,10 +34,10 @@ protected void On(ProjectToSqlServerAsync handler) where T : class => base.On(async ctx => await Handle(ctx, handler).NoContext()); async Task Handle(MessageConsumeContext context, ProjectToSqlServerAsync handler) where T : class { - await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken); + await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); - var cmd = await handler(connection, context).ConfigureAwait(false); - await cmd.ExecuteNonQueryAsync(context.CancellationToken).ConfigureAwait(false); + var cmd = await handler(connection, context).NoContext(); + await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); } protected static SqlCommand Project(SqlConnection connection, string commandText, params SqlParameter[] parameters) { diff --git a/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs b/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs index aefc35430..68ec60ec6 100644 --- a/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs +++ b/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs @@ -34,10 +34,10 @@ protected void On(ProjectToSqliteAsync handler) where T : class => base.On(async ctx => await Handle(ctx, handler).NoContext()); async Task Handle(MessageConsumeContext context, ProjectToSqliteAsync handler) where T : class { - await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken); + await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); - var cmd = await handler(connection, context).ConfigureAwait(false); - await cmd.ExecuteNonQueryAsync(context.CancellationToken).ConfigureAwait(false); + var cmd = await handler(connection, context).NoContext(); + await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); } protected static SqliteCommand Project(SqliteConnection connection, string commandText, params SqliteParameter[] parameters) { From be9205110567fba6f0ee89cf36157629dd223b3d Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Fri, 21 Aug 2026 15:11:38 +0200 Subject: [PATCH 2/2] chore: configure the implicit await using disposal with NoContext too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `await using var` always awaits the implicit DisposeAsync() unconfigured — the declaration form cannot be combined with NoContext(), only the block form can. Splits acquisition from disposal in the projectors and the paged-read enumerator so the disposal await also opts out of context capture, matching BaseTracer. Co-Authored-By: Claude Fable 5 --- .../EventStore/StoreFunctions.cs | 28 ++++++++++--------- .../Projections/SqlServerProjector.cs | 8 ++++-- .../Projections/SqliteProjector.cs | 8 ++++-- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs b/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs index 8334eaa57..462bb4643 100644 --- a/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs +++ b/src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs @@ -218,24 +218,26 @@ [EnumeratorCancellation] CancellationToken cancellationToken var yielded = 0; long lastRevision = 0; - await using var enumerator = eventReader.ReadEvents(streamName, position, pageSize, cancellationToken).GetAsyncEnumerator(cancellationToken); + var enumerator = eventReader.ReadEvents(streamName, position, pageSize, cancellationToken).GetAsyncEnumerator(cancellationToken); - while (true) { - bool moved; + await using (enumerator.NoContext()) { + while (true) { + bool moved; - try { - moved = await enumerator.MoveNextAsync().NoContext(); - } catch (StreamNotFound) when (!failIfNotFound) { - yield break; - } + try { + moved = await enumerator.MoveNextAsync().NoContext(); + } catch (StreamNotFound) when (!failIfNotFound) { + yield break; + } - if (!moved) break; + if (!moved) break; - var evt = enumerator.Current; - yielded++; - lastRevision = evt.Revision; + var evt = enumerator.Current; + yielded++; + lastRevision = evt.Revision; - yield return evt; + yield return evt; + } } if (yielded < pageSize) yield break; diff --git a/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs b/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs index 5a11a8b3d..2e12439e5 100644 --- a/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs +++ b/src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs @@ -34,10 +34,12 @@ protected void On(ProjectToSqlServerAsync handler) where T : class => base.On(async ctx => await Handle(ctx, handler).NoContext()); async Task Handle(MessageConsumeContext context, ProjectToSqlServerAsync handler) where T : class { - await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); + var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); - var cmd = await handler(connection, context).NoContext(); - await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); + await using (connection.NoContext()) { + var cmd = await handler(connection, context).NoContext(); + await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); + } } protected static SqlCommand Project(SqlConnection connection, string commandText, params SqlParameter[] parameters) { diff --git a/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs b/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs index 68ec60ec6..18fe144d9 100644 --- a/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs +++ b/src/Sqlite/src/Eventuous.Sqlite/Projections/SqliteProjector.cs @@ -34,10 +34,12 @@ protected void On(ProjectToSqliteAsync handler) where T : class => base.On(async ctx => await Handle(ctx, handler).NoContext()); async Task Handle(MessageConsumeContext context, ProjectToSqliteAsync handler) where T : class { - await using var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); + var connection = await ConnectionFactory.GetConnection(_connectionString, context.CancellationToken).NoContext(); - var cmd = await handler(connection, context).NoContext(); - await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); + await using (connection.NoContext()) { + var cmd = await handler(connection, context).NoContext(); + await cmd.ExecuteNonQueryAsync(context.CancellationToken).NoContext(); + } } protected static SqliteCommand Project(SqliteConnection connection, string commandText, params SqliteParameter[] parameters) {