-
-
Notifications
You must be signed in to change notification settings - Fork 21
refactor(engine): Improve context scope cleanup with ConcurrentBag and IAsyncDisposable (#1477) #1793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(engine): Improve context scope cleanup with ConcurrentBag and IAsyncDisposable (#1477) #1793
Changes from 1 commit
894a24b
a29f238
9589281
2b9732c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,14 @@ | ||||||||||||||||||||
| using System.Collections.Concurrent; | ||||||||||||||||||||
| using Microsoft.Extensions.DependencyInjection; | ||||||||||||||||||||
| using ModularPipelines.Context; | ||||||||||||||||||||
| using ModularPipelines.Interfaces; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| namespace ModularPipelines.Engine; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| internal class ModuleContextProvider : IPipelineContextProvider, IScopeDisposer | ||||||||||||||||||||
| internal class ModuleContextProvider : IPipelineContextProvider, IScopeDisposer, IAsyncDisposable | ||||||||||||||||||||
| { | ||||||||||||||||||||
| private readonly IServiceProvider _serviceProvider; | ||||||||||||||||||||
| private readonly List<IServiceScope> _scopes = new(); | ||||||||||||||||||||
| private readonly ConcurrentBag<IServiceScope> _scopes = new(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public ModuleContextProvider(IServiceProvider serviceProvider) | ||||||||||||||||||||
| { | ||||||||||||||||||||
|
|
@@ -27,4 +28,19 @@ public IEnumerable<IServiceScope> GetScopes() | |||||||||||||||||||
| { | ||||||||||||||||||||
| return _scopes; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public async ValueTask DisposeAsync() | ||||||||||||||||||||
| { | ||||||||||||||||||||
| foreach (var scope in _scopes) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| if (scope is IAsyncDisposable asyncDisposable) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| await asyncDisposable.DisposeAsync().ConfigureAwait(false); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| else | ||||||||||||||||||||
| { | ||||||||||||||||||||
| scope.Dispose(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
||||||||||||||||||||
| if (scope is IAsyncDisposable asyncDisposable) | |
| { | |
| await asyncDisposable.DisposeAsync().ConfigureAwait(false); | |
| } | |
| else | |
| { | |
| scope.Dispose(); | |
| } | |
| await ((IAsyncDisposable)scope).DisposeAsync().ConfigureAwait(false); |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DisposeAsync implementation has a critical concurrency issue. While DisposeAsync iterates over the ConcurrentBag, GetModuleContext can still be adding new scopes to it. This creates a race condition where:
- Scopes added during disposal may not be disposed
- The iteration over ConcurrentBag is not thread-safe when concurrent modifications occur
Additionally, this implementation is missing several important elements of proper async disposal:
- No mechanism to prevent concurrent disposal calls
- Missing GC.SuppressFinalize(this) to suppress finalization
- No coordination to prevent new scopes from being added during disposal
Consider implementing a proper disposal pattern with a disposed flag and synchronization, or ensure that disposal only occurs when no concurrent calls to GetModuleContext are possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IScopeDisposer interface provides a default implementation of IDisposable.Dispose() that synchronously disposes scopes. However, with this class now also implementing IAsyncDisposable, there's a potential conflict:
Additionally, the IScopeDisposer.Dispose implementation doesn't coordinate with DisposeAsync, creating potential race conditions. Consider whether both disposal patterns are needed, or implement proper coordination between them with a disposed flag.