Skip to content

Cache canceled read results in SlicPipeReader#4766

Merged
pepone merged 7 commits into
icerpc:mainfrom
pepone:fix-audit-30
Jun 17, 2026
Merged

Cache canceled read results in SlicPipeReader#4766
pepone merged 7 commits into
icerpc:mainfrom
pepone:fix-audit-30

Conversation

@pepone

@pepone pepone commented Jun 12, 2026

Copy link
Copy Markdown
Member

Fixes icerpc/icerpc-csharp-audit#30.

ReadAsync and TryRead returned canceled read results without caching them in _readResult. AdvanceTo computes the consumed and examined offsets against _readResult.Buffer, so advancing a canceled read with positions from the returned buffer computed the offsets against a stale buffer — or the default read result when the first read is canceled: GetOffset either throws ArgumentOutOfRangeException or produces garbage offsets that corrupt the window accounting (_examined/_windowSize) fed to the StreamWindowUpdate frames. The repository's own conformance tests advance canceled results, but only in arrangements where the stale offsets happen to be benign, which is why this stayed latent.

Canceled read results are now cached like regular ones, so AdvanceTo operates on the buffer the application actually received.

Tests

  • Canceled_read_with_buffered_data_then_resume_reading (ReadAsync and TryRead variants): cancels a read that returns buffered data, advances with positions from the returned buffer (examine all, consume nothing), and verifies reading resumes correctly through the end of the stream. Both variants fail without the fix with ArgumentOutOfRangeException from the stale-buffer offset computation.
  • Canceled_read_advance_does_not_corrupt_window_accounting: wire-level test (raw duplex client) pinning the flow-control invariant that the sum of StreamWindowUpdate increments sent to the peer never exceeds the bytes written on the stream, across a cancel/advance/resume sequence. This one is invariant-pinning rather than regression-detecting: with the unfixed code its particular advance positions happen to produce benign offsets.
  • The ReadFrameAsync test helper copied and consumed the whole buffered sequence into a frame-sized array, which throws (and would drop trailing frames) when several frames are buffered back to back — single-frame arrangements never hit this. It now slices exactly one frame per call, which the wire-level test relies on to read consecutive window updates.

Full IceRpc.Tests suite passes (990 passed / 0 failed), dotnet build and dotnet format --verify-no-changes clean.

Fixes icerpc/icerpc-csharp-audit#30.

ReadAsync and TryRead returned canceled read results without caching
them in _readResult. AdvanceTo computes the consumed and examined
offsets against _readResult.Buffer, so advancing a canceled read with
positions from the returned buffer computed the offsets against a stale
buffer (or the default read result when the first read is canceled):
GetOffset either throws ArgumentOutOfRangeException or produces garbage
offsets that corrupt the window accounting fed to the StreamWindowUpdate
frames.

Canceled read results are now cached like regular ones, so AdvanceTo
operates on the buffer the application actually received.

Also fix the ReadFrameAsync test helper to consume only one frame's
bytes when several frames are buffered back to back.
Copilot AI review requested due to automatic review settings June 12, 2026 15:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Slic transport correctness bug where canceled PipeReader read results (ReadAsync/TryRead) were not cached in SlicPipeReader, causing AdvanceTo to compute offsets against a stale buffer and potentially corrupt flow-control window accounting (or throw).

Changes:

  • Cache canceled ReadResult values in SlicPipeReader.ReadAsync and .TryRead so AdvanceTo always computes offsets against the buffer actually returned to the application.
  • Add regression tests covering cancel/advance/resume scenarios, including a wire-level invariant test validating StreamWindowUpdate accounting.
  • Fix the ReadFrameAsync test helper to consume/copy exactly one frame when multiple frames are buffered back-to-back.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/IceRpc.Tests/Transports/Slic/SlicTransportTests.cs Adds new cancel/advance/resume tests and fixes ReadFrameAsync to read exactly one frame per call.
src/IceRpc/Transports/Slic/Internal/SlicPipeReader.cs Caches canceled read results in _readResult to keep AdvanceTo offset computations consistent and prevent window-accounting corruption.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@bernardnormier bernardnormier self-requested a review June 16, 2026 16:29
Comment thread src/IceRpc/Transports/Slic/Internal/SlicPipeReader.cs Outdated
Comment thread tests/IceRpc.Tests/Transports/Slic/SlicTransportTests.cs
Comment thread tests/IceRpc.Tests/Transports/Slic/SlicTransportTests.cs Outdated
Comment thread tests/IceRpc.Tests/Transports/Slic/SlicTransportTests.cs Outdated
@bernardnormier bernardnormier self-requested a review June 17, 2026 14:23
Comment thread src/IceRpc/Transports/Slic/Internal/SlicPipeReader.cs Outdated
@pepone pepone merged commit 0566a2d into icerpc:main Jun 17, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants