Sans-I/O TDS core (2/N): route NetworkTransport through PacketBuffer - #191
Draft
Saurabh Singh (saurabh500) wants to merge 2 commits into
Draft
Conversation
Adopt the sans-I/O PacketBuffer as the single source of truth for packet buffering and scalar/byte decode in the production read path. Extend PacketBuffer with first-class pending-bytes carryover (begin_refill, refill_window, validate_packet_length, record_pending, strip_header, change_packet_size, reset_to_length) so NetworkTransport delegates fully, and delete the duplicated TdsReadBuffer. Add zero-byte EOF guards to the socket-read loops to prevent a spin loop when the peer stops sending mid-packet. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f8f138c-afa7-4926-8581-5727e54cb199
Cover the empty-input and truncated-header edge cases that previously spun the header/payload refill loops forever. Each now asserts ConnectionClosed is surfaced deterministically, so the sans-I/O parser inversion in later layers cannot silently reintroduce the infinite loop. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8f8f138c-afa7-4926-8581-5727e54cb199
Saurabh Singh (saurabh500)
marked this pull request as ready for review
August 10, 2026 06:38
Copilot started reviewing on behalf of
Saurabh Singh (saurabh500)
August 10, 2026 06:38
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Routes production TDS reads through the shared sans-I/O PacketBuffer, consolidating buffering and fixing zero-byte read loops.
Changes:
- Adds pending-byte carryover, packet validation, resizing, and tests to
PacketBuffer. - Replaces
TdsReadBufferinNetworkTransport. - Returns
ConnectionClosedwhen packet reads reach EOF.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
mssql-tds/src/io/packet_reader.rs |
Uses refill APIs and handles EOF. |
mssql-tds/src/io/packet_buffer.rs |
Adds production buffering and framing support. |
mssql-tds/src/connection/transport/network_transport.rs |
Delegates production reads to PacketBuffer. |
mssql-tds/src/connection/transport/buffers.rs |
Removes duplicated read buffer. |
mssql-tds/src/connection/transport.rs |
Removes the deleted buffer module. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Saurabh Singh (saurabh500)
marked this pull request as draft
August 10, 2026 06:49
Saurabh Singh (saurabh500)
marked this pull request as ready for review
August 10, 2026 07:39
Saurabh Singh (saurabh500)
marked this pull request as draft
August 10, 2026 13:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Layer 2 of the sans-I/O restructure of
mssql-tds. Stacks on #189 — base isdev/saurabh/sans-io-tds-core, notmain.Adopts the Layer-1
PacketBufferas the single source of truth for packet buffering and scalar/byte decode in the production read path (NetworkTransport), and deletes the duplicatedTdsReadBuffer(buffers.rs, 482 lines).Net: +417 / −737 lines.
Behavior change — correctness fix (reviewer note)
A zero-byte socket read now returns
ConnectionClosedon both socket-read loops. This is a production correctness fix, not merely a test-hang fix. Previously, if a peer closed the connection mid-packet,read()returningOk(0)was treated as a successful (empty) read, so the refill loop spun a perpetually-ready future at 100% CPU forever — on real connections, not only under test. Both loops now detectbytes_read == 0and returnError::ConnectionClosed. The symptom that surfaced it was an infinitecargo btesthang, but the underlying defect affected production peers that drop mid-packet. Reviewers should treat this as a distinct bug fix on its own merits.Approach — Option A (extend
PacketBuffer, delegate fully)Rather than layering pending-bytes bookkeeping in the transport around a dumb buffer (Option B), I extended
PacketBufferwith first-class multi-packet pending-bytes carryover soNetworkTransportdelegates all buffering and decode:begin_refill()— compact readable bytes to front, relocate carried pending bytes with the same bounds guard production had.refill_window(base, already)— wide window to end of buffer so one socket read can pull multiple TDS packets (coalescing).validate_packet_length(base)— rejects< 8,> max_packet_size, or header-claims-overflow (preserves the production overflow guard).record_pending/strip_header/change_packet_size(resize tosize*2only on change — preserves the reset_reader resize fix) /reset_to_length(preserves pending).NetworkTransportnow has no hand-rolled scalar decode or position bookkeeping — every reader isif !has(N) { read_tds_packet().await? } take_X().TdsReadBufferis gone.Test coverage note (deleted
buffers.rstests)Deleting
buffers.rsremoved its 16TdsReadBufferunit tests; 8 new directPacketBuffertests were added (net −8 passing, failing set unchanged). This is a consolidation, not a silent loss:begin_refill_*), surplus tracking (record_pending_tracks_surplus), packet-length guards (validate_packet_length_guards), header strip (strip_header_exposes_payload), and reset-preserves-pending (reset_to_length_preserves_pending).consume_bytes/get_slice/do_we_have_enough_data/get_remaining_byte_count) that are nowPacketBuffer::{take_*, has, available, skip, copy_out}. These are exercised indirectly by network_transport (17/17) and packet_reader (23/23). DirectPacketBufferunit tests fortake_*/skip/copy_outroundtrips and the over-consume bounds guard are restored in Layer 3, which makes those methods the sync-core API (adding them here would force an L3 restack).Notes
TestPacketBuilder::build()writespayload_length(not total length) into the TDS header, so its header-length field can't be trusted as authoritative. ProductionNetworkTransportwrites correct total lengths and uses the header field authoritatively viavalidate_packet_length+record_pending. Both sharePacketBufferbut call different methods by design.PacketBuffer::with_packet_sizeis un-gated (was#[cfg(test)]) so production can construct it.Validation
cargo bfmt— cleancargo bclippy(-D warnings) — cleancargo btest(nextest + coverage) — no regressions. Verified by diffing the failing-test set against a clean baseline of the base branch: 359 failed on both, identical sets, zero regressions. The 359 are pre-existing/environmental (no live SQL Server, missing cert fixtures intests/test_certificates/, odbc debug-assert tests) and fail the same way ondev/saurabh/sans-io-tds-core.cargo build --manifest-path mssql-py-core/Cargo.toml— builds clean (public surface unchanged).Draft until the stack lands.