feat(TcpSocket): support multiple DataPackageAdapter#542
Conversation
Reviewer's GuideThis PR adds support for multiple DataPackageAdapter instances on an ITcpSocketClient by introducing Add/Remove adapter methods with caching and callback management, refactors the DataPackageAdapter implementation to use constructor injection and simplify handler setup, updates unit tests to cover the new APIs and adjust adapter construction, and refreshes license headers across extension files. Sequence diagram for adding a DataPackageAdapter to ITcpSocketClientsequenceDiagram
participant Client as ITcpSocketClient
participant Extensions as ITcpSocketClientExtensions
participant Adapter as IDataPackageAdapter
participant Callback as Callback
Extensions->>Client: AddDataPackageAdapter(Adapter, Callback)
Extensions->>Adapter: set ReceivedCallBack = Callback
Extensions->>Client: subscribe ReceivedCallBack
Client->>Adapter: HandlerAsync(buffer) on data received
Adapter->>Callback: Callback(processed data)
Sequence diagram for removing a DataPackageAdapter from ITcpSocketClientsequenceDiagram
participant Client as ITcpSocketClient
participant Extensions as ITcpSocketClientExtensions
participant Adapter as IDataPackageAdapter
participant Callback as Callback
Extensions->>Client: RemoveDataPackageAdapter(Callback)
Extensions->>Client: unsubscribe ReceivedCallBack
Extensions->>Adapter: remove from cache
Class diagram for DataPackageAdapter and IDataPackageAdapter changesclassDiagram
class IDataPackageAdapter {
+Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack
+ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token)
}
class DataPackageAdapter {
+DataPackageAdapter(IDataPackageHandler? DataPackageHandler = null)
+Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack
+ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token)
}
IDataPackageAdapter <|.. DataPackageAdapter
class IDataPackageHandler {
+Func<ReadOnlyMemory<byte>, ValueTask>? ReceivedCallBack
+ValueTask HandlerAsync(ReadOnlyMemory<byte> data, CancellationToken token)
}
DataPackageAdapter --> IDataPackageHandler: optional constructor param
Class diagram for ITcpSocketClientExtensions adapter managementclassDiagram
class ITcpSocketClientExtensions {
+void AddDataPackageAdapter(ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
+void RemoveDataPackageAdapter(ITcpSocketClient client, Func<ReadOnlyMemory<byte>, ValueTask> callback)
+void SetDataPackageAdapter(ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
+void SetDataPackageAdapter<TEntity>(ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
+void SetDataPackageAdapter<TEntity>(ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
+void SetDataPackageAdapter(ITcpSocketClient client, IDataPackageHandler handler, Func<ReadOnlyMemory<byte>, ValueTask> callback)
+void SetDataPackageAdapter<TEntity>(ITcpSocketClient client, IDataPackageHandler handler, IDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
+void SetDataPackageAdapter<TEntity>(ITcpSocketClient client, IDataPackageHandler handler, Func<TEntity?, Task> callback)
}
class ITcpSocketClient {
+event Func<ReadOnlyMemory<byte>, ValueTask> ReceivedCallBack
}
ITcpSocketClientExtensions --> ITcpSocketClient: manages adapters
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes and found some issues that need to be addressed.
Blocking issues:
- Variable 'cb' is used before its declaration. (link)
General comments:
- The static _cache dictionary in Add/RemoveDataPackageAdapter is neither thread-safe nor cleaned up when clients are disposed; consider using a ConcurrentDictionary and removing client entries when no adapters remain to prevent memory leaks and race conditions.
- The DataPackageAdapter class uses a primary-constructor syntax for the IDataPackageHandler parameter but doesn’t explicitly store it; verify that DataPackageHandler is correctly backed by a field since the interface no longer exposes it and primary constructors on regular classes may not be supported in all C# versions.
- The new tests wait for callbacks but don’t assert the received buffer contents or confirm that RemoveDataPackageAdapter unsubscribes callbacks; add assertions to verify both adapter callbacks run as expected and that removal stops further invocations.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The static _cache dictionary in Add/RemoveDataPackageAdapter is neither thread-safe nor cleaned up when clients are disposed; consider using a ConcurrentDictionary and removing client entries when no adapters remain to prevent memory leaks and race conditions.
- The DataPackageAdapter class uses a primary-constructor syntax for the IDataPackageHandler parameter but doesn’t explicitly store it; verify that DataPackageHandler is correctly backed by a field since the interface no longer exposes it and primary constructors on regular classes may not be supported in all C# versions.
- The new tests wait for callbacks but don’t assert the received buffer contents or confirm that RemoveDataPackageAdapter unsubscribes callbacks; add assertions to verify both adapter callbacks run as expected and that removal stops further invocations.
## Individual Comments
### Comment 1
<location> `src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs:54` </location>
<code_context>
return client.ConnectAsync(endPoint, token);
}
+ private static readonly Dictionary<ITcpSocketClient, List<(IDataPackageAdapter Adapter, Func<ReadOnlyMemory<byte>, ValueTask> Callback)>> _cache = [];
+
+ /// <summary>
</code_context>
<issue_to_address>
Consider thread safety for the static _cache dictionary.
Since _cache is modified and read by multiple extension methods, this can cause race conditions in multi-threaded use. Use a thread-safe collection like ConcurrentDictionary or implement locking to prevent concurrent access issues.
</issue_to_address>
### Comment 2
<location> `src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs:66` </location>
<code_context>
+ {
+ if (_cache.TryGetValue(client, out var list))
+ {
+ list.Add((adapter, cb));
+ }
+ else
</code_context>
<issue_to_address>
Variable 'cb' is used before its declaration.
'cb' is referenced in list.Add and _cache.Add before it is declared as an async local function, which will cause a compilation error. Please declare 'cb' before using it.
</issue_to_address>
### Comment 3
<location> `src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs:64` </location>
<code_context>
+ /// <param name="callback"></param>
+ public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func<ReadOnlyMemory<byte>, ValueTask> callback)
+ {
+ if (_cache.TryGetValue(client, out var list))
+ {
+ var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList();
+ foreach (var c in items)
+ {
</code_context>
<issue_to_address>
Callback removal logic may not match added callbacks.
Since the callback added is a wrapper function, direct comparison with the original callback may fail. Store and compare the actual delegate instance used in ReceivedCallBack to ensure correct removal.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return client.ConnectAsync(endPoint, token); | ||
| } | ||
|
|
||
| private static readonly Dictionary<ITcpSocketClient, List<(IDataPackageAdapter Adapter, Func<ReadOnlyMemory<byte>, ValueTask> Callback)>> _cache = []; |
There was a problem hiding this comment.
issue (bug_risk): Consider thread safety for the static _cache dictionary.
Since _cache is modified and read by multiple extension methods, this can cause race conditions in multi-threaded use. Use a thread-safe collection like ConcurrentDictionary or implement locking to prevent concurrent access issues.
| { | ||
| if (_cache.TryGetValue(client, out var list)) | ||
| { | ||
| list.Add((adapter, cb)); |
There was a problem hiding this comment.
issue (bug_risk): Variable 'cb' is used before its declaration.
'cb' is referenced in list.Add and _cache.Add before it is declared as an async local function, which will cause a compilation error. Please declare 'cb' before using it.
| if (_cache.TryGetValue(client, out var list)) | ||
| { | ||
| list.Add((adapter, cb)); | ||
| } | ||
| else | ||
| { | ||
| _cache.Add(client, [(adapter, cb)]); | ||
| } | ||
|
|
||
| client.ReceivedCallBack += cb; |
There was a problem hiding this comment.
issue (bug_risk): Callback removal logic may not match added callbacks.
Since the callback added is a wrapper function, direct comparison with the original callback may fail. Store and compare the actual delegate instance used in ReceivedCallBack to ensure correct removal.
Link issues
fixes #541
Summary By Copilot
Regression?
Risk
Verification
Packaging changes reviewed?
☑️ Self Check before Merge
Summary by Sourcery
Support multiple data package adapters on TCP socket clients by adding AddDataPackageAdapter and RemoveDataPackageAdapter extension methods, overloading SetDataPackageAdapter with handler and generic converter variants, refactoring DataPackageAdapter constructor, standardizing license headers, and updating unit tests.
New Features:
Enhancements:
Tests: