Conversation
Agent-Logs-Url: https://github.com/mycroes/Sally7/sessions/0c551dde-09a9-4e0b-91aa-dd1a7de675df Co-authored-by: mycroes <122017+mycroes@users.noreply.github.com>
Agent-Logs-Url: https://github.com/mycroes/Sally7/sessions/5d42deb2-9b80-482e-98e9-cdfe1950b01c Co-authored-by: mycroes <122017+mycroes@users.noreply.github.com>
|
@gfoidl Actually had CoPilot do this for me, so didn't need a computer to commit it 😆 |
There was a problem hiding this comment.
Pull request overview
This PR changes how ArrayPool<ReadWriteErrorCode> buffers are managed in S7Connection.ReadAsync(IDataItem[], CancellationToken) and WriteAsync(IDataItem[], CancellationToken), returning the rented buffer only when the operation completes without throwing.
Changes:
- Removed
try/finallyblocks around pooledresultsbuffers inReadAsync(...)andWriteAsync(...). - Moved
ArrayPool<ReadWriteErrorCode>.Shared.Return(results)to occur only afterReadWriteErrorHelpers.ThrowIfHasErrors(...)completes successfully.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var results = ArrayPool<ReadWriteErrorCode>.Shared.Rent(dataItems.Length); | ||
| try | ||
| { | ||
| await ReadAsync(dataItems, results, cancellationToken).ConfigureAwait(false); | ||
| await ReadAsync(dataItems, results, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| ReadWriteErrorHelpers.ThrowIfHasErrors("Read", dataItems, results.AsSpan(0, dataItems.Length)); | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<ReadWriteErrorCode>.Shared.Return(results); | ||
| } | ||
| ReadWriteErrorHelpers.ThrowIfHasErrors("Read", dataItems, results.AsSpan(0, dataItems.Length)); | ||
| ArrayPool<ReadWriteErrorCode>.Shared.Return(results); |
There was a problem hiding this comment.
ArrayPool<ReadWriteErrorCode>.Shared.Rent(...) is no longer paired with a Return(...) on exception paths (including the common path where ThrowIfHasErrors throws for per-item failures). This defeats pooling under failure conditions and can cause sustained allocations/GC pressure (and potentially LOH allocations for large dataItems.Length) when a PLC is offline or returns item errors repeatedly.
Consider restoring a try/finally that always returns the buffer, or if the intent is specifically to avoid returning on transport/cancellation exceptions, then catch only the expected AggregateException from ThrowIfHasErrors (return the buffer, then rethrow) while still letting other exceptions skip return if you really need that behavior.
| var results = ArrayPool<ReadWriteErrorCode>.Shared.Rent(dataItems.Length); | ||
| try | ||
| { | ||
| await WriteAsync(dataItems, results, cancellationToken).ConfigureAwait(false); | ||
| await WriteAsync(dataItems, results, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| ReadWriteErrorHelpers.ThrowIfHasErrors("Write", dataItems, results.AsSpan(0, dataItems.Length)); | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<ReadWriteErrorCode>.Shared.Return(results); | ||
| } | ||
| ReadWriteErrorHelpers.ThrowIfHasErrors("Write", dataItems, results.AsSpan(0, dataItems.Length)); | ||
| ArrayPool<ReadWriteErrorCode>.Shared.Return(results); |
There was a problem hiding this comment.
Same pooling issue as in ReadAsync: if WriteAsync(...) throws (including ThrowIfHasErrors throwing AggregateException for per-item failures), the rented ArrayPool buffer is never returned. This can significantly increase allocation rate and memory pressure in repeated error scenarios.
Recommendation: ensure the rented buffer is returned for at least the expected error case (AggregateException from ThrowIfHasErrors) and ideally via try/finally to keep pooling effective and consistent with the existing ArrayPool<byte> usage earlier in this file.
ArrayPoolbuffers rented inReadAsync/WriteAsyncwere previously returned unconditionally viafinally. The intent is to skip the return on exception paths (letting the GC handle it) while still returning on success.Changes
try/finallyblocks inReadAsync(IDataItem[], CancellationToken)andWriteAsync(IDataItem[], CancellationToken)ArrayPool<ReadWriteErrorCode>.Shared.Return(results)is now placed afterThrowIfHasErrors— only reached when no exception is thrown