Skip to content

Hypothesis fault-injection for the Rust pump's FD lifecycle (#74) - #244

Open
leynos wants to merge 2 commits into
mainfrom
python-pipeline-streams-tests
Open

Hypothesis fault-injection for the Rust pump's FD lifecycle (#74)#244
leynos wants to merge 2 commits into
mainfrom
python-pipeline-streams-tests

Conversation

@leynos

@leynos leynos commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

The Rust inter-stage pump takes over the raw pipe descriptors from asyncio for the duration of a transfer, across several partial-failure paths — FD extraction, reader-transport pause/resume, and blocking-mode switch/restore — with no isolated seam for fault injection (#74).

Seams

New module cuprum/_pipeline_stream_fds.py (extracting the FD lifecycle also lifts _pipeline_streams.py back under the 400-line health cap: 300 + 166):

  • _BlockingModeGuard — the FD-state object. engage switches the descriptor pair to blocking mode capturing prior state (rolling back a partial change on failure); restore returns them to that state.
  • _paused_reader — a context manager wrapping _pause_reader_transport so the resume cannot be skipped on any exit path (normal return, exception, or cancellation).

_run_rust_pump is refactored (via _pump_over_raw_fds) to drive these. Behaviour is preserved — the existing test_pipeline_stream_backend_selection.py suite (repointed to the new module) still passes, including the pause→drain→restore→resume ordering and the writer-toggle rollback tests.

Fault-injection tests

cuprum/unittests/test_pipeline_streams_fd_lifecycle.py covers the four hazards #74 names:

Hazard Test
Leaked blocking state round-trip property over initial modes, plus an injected toggle failure asserting no descriptor is left switched
Missing resume _paused_reader resumes exactly once on normal and exception exit; skips resume when the transport can't pause or pausing raises
Wrong fallback a blocking-toggle failure returns the Python-fallback signal (False) and still resumes the reader
Swallowed unexpected errors _surface_unexpected_pipe_failures raises the first non-pipe exception and suppresses BrokenPipeError/ConnectionResetError

Validation

Full gates green: make check-fmt, make lint (ruff, interrogate 100%, pylint 10.00/10 — both modules under the line cap), make test (762 passed / 47 skipped; Rust nextest 57/57). Wheel-manifest snapshot regenerated for the two new files.

Closes #74

🤖 Generated with Claude Code

Summary by Sourcery

Isolate the Rust pump’s raw file-descriptor lifecycle behind dedicated helpers and add targeted fault-injection tests for pause/resume and blocking-mode behaviour.

Enhancements:

  • Extract the FD extraction, pause/resume, and blocking-mode management logic from the pipeline streams module into a new _pipeline_stream_fds module with a _BlockingModeGuard and _paused_reader seam.
  • Refactor the Rust pump path to route FD handling through _pump_over_raw_fds, keeping existing behaviour while simplifying _run_rust_pump.

Tests:

  • Add hypothesis-based and unit tests validating the FD blocking-mode guard, reader pause/resume context manager, Rust pump fallback behaviour, and error-surfacing semantics for pipe-related failures.
  • Update backend-selection tests to use the new FD lifecycle module directly and to manage OS-level pipes without depending on internals of _pipeline_streams.

The Rust inter-stage pump hands the raw pipe descriptors to native code
under several partial-failure paths — FD extraction, reader-transport
pause/resume, and blocking-mode switch/restore — that had no isolated
seam for fault injection.

Introduce two seams in a new cuprum/_pipeline_stream_fds.py module (the
FD lifecycle also lifts _pipeline_streams back under the 400-line cap):

- _BlockingModeGuard: an FD-state object that switches the descriptor
  pair to blocking mode, capturing prior state, and restores it — with
  the partial-failure rollback preserved.
- _paused_reader: a context manager wrapping _pause_reader_transport so
  the resume can't be skipped on any exit (return, exception, cancel).

_run_rust_pump is refactored (via _pump_over_raw_fds) to drive these,
preserving behaviour; existing backend-selection tests are repointed to
the new module and still pass.

Add cuprum/unittests/test_pipeline_streams_fd_lifecycle.py with
Hypothesis fault-injection covering the four #74 hazards:
- leaked blocking state: a round-trip property plus an injected
  toggle-failure asserting no descriptor is left switched;
- missing resume: _paused_reader resumes once on both exits and skips
  resume when the transport can't pause;
- wrong fallback: a blocking-toggle failure returns the Python-fallback
  signal and still resumes the reader;
- swallowed unexpected errors: _surface_unexpected_pipe_failures raises
  the first non-pipe exception and suppresses broken-pipe/reset.

Regenerate the maturin wheel-manifest snapshot for the two new files.

Closes #74

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0ff1ddad-0806-46be-9370-a5bf2a471b17

📥 Commits

Reviewing files that changed from the base of the PR and between 302858c and a3f900b.

📒 Files selected for processing (6)
  • cuprum/_pipeline_stream_fds.py
  • cuprum/_pipeline_streams.py
  • cuprum/unittests/__snapshots__/test_maturin_build.ambr
  • cuprum/unittests/test_pipeline_stream_backend_selection.py
  • cuprum/unittests/test_pipeline_streams_fd_lifecycle.py
  • docs/cuprum-design.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch python-pipeline-streams-tests

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors the Rust pump FD lifecycle management into a dedicated module with reusable blocking/pause guards, updates the Rust-pump dispatch path to use these abstractions, and adds focused property- and fault-injection tests around FD blocking state, reader pause/resume, and error surfacing behavior.

Sequence diagram for _pump_over_raw_fds FD lifecycle and fallback

sequenceDiagram
    participant Pump as _pump_over_raw_fds
    participant Reader as asyncio_StreamReader
    participant Writer as asyncio_StreamWriter
    participant Guard as _BlockingModeGuard
    participant Rust as rust_pump_stream

    Pump->>Reader: _paused_reader(reader)
    activate Reader
    Pump->>Pump: _drain_reader_buffer(reader, writer)

    Pump->>Guard: _BlockingModeGuard.engage(reader_fd, writer_fd)
    alt [OSError from engage]
        Guard-->>Pump: OSError
        Pump-->>Pump: return False
    else [engage ok]
        Pump->>Rust: loop.run_in_executor(None, rust_pump_stream, reader_fd, writer_fd)
        Pump->>Guard: restore()
        Pump-->>Pump: return True
    end
    deactivate Reader
Loading

File-Level Changes

Change Details Files
Extract FD lifecycle helpers into a dedicated module and wire them into the Rust pump path.
  • Introduce _pipeline_stream_fds.py with helpers for extracting FDs from asyncio transports and pausing reader transports
  • Add _BlockingModeGuard to encapsulate switching pipe FDs into blocking mode and restoring prior state, including rollback on partial failure
  • Add _paused_reader context manager to ensure reader transports are always resumed when pausable
  • Refactor _pump_over_raw_fds to use the new abstractions when handing control to the Rust pump
  • Update _pipeline_streams to import and use the new FD lifecycle utilities instead of local implementations
cuprum/_pipeline_stream_fds.py
cuprum/_pipeline_streams.py
Adjust existing backend-selection tests to the new FD lifecycle seams and direct OS interactions.
  • Switch tests to import os directly instead of accessing it through _pipeline_streams
  • Update mocks and monkeypatches to target the new FD lifecycle module for pause, blocking, and restore behavior
  • Ensure ordering and rollback expectations (pause→drain→restore→resume and writer-toggle failure) are preserved under the refactor
cuprum/unittests/test_pipeline_stream_backend_selection.py
Add focused Hypothesis-based fault-injection tests around FD blocking and pause/resume behavior.
  • Add property tests ensuring _BlockingModeGuard round-trips arbitrary initial blocking modes and never leaks transient blocking state on toggle failure
  • Add tests validating _paused_reader’s behavior with normal, exceptional, and non-pausable transports, including skip-resume semantics on pause failure
  • Add tests verifying that a blocking-toggle failure in the Rust-pump path causes a Python fallback and still resumes the reader
  • Add tests for _surface_unexpected_pipe_failures to raise the first unexpected exception while suppressing BrokenPipeError/ConnectionResetError
cuprum/unittests/test_pipeline_streams_fd_lifecycle.py

Assessment against linked issues

Issue Objective Addressed Explanation
#74 Introduce an isolated FD-state object and reader-transport context manager to manage the Rust pump FD pause/blocking lifecycle for easier fault injection and verification.
#74 Add fault-injection tests (primarily Hypothesis-based) that verify FD blocking mode is restored correctly and no blocking state is leaked, including partial failures during blocking-mode toggling.
#74 Add tests (primarily Hypothesis-based) around the Rust dispatch path and pipe-error suppression logic to catch missing reader-resume calls, incorrect fallback behaviour, and swallowed unexpected errors.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-access[bot]

This comment was marked as outdated.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 904477a19b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cuprum/_pipeline_stream_fds.py
Comment thread cuprum/_pipeline_streams.py
codescene-access[bot]

This comment was marked as outdated.

The abstraction policy in AGENTS.md requires a new abstraction's scope and
reuse policy to be recorded in the project documentation, and
_pipeline_stream_fds appeared nowhere under docs/.

Add a "Raw descriptor lifecycle" subsection to design-doc 13.6, which
already covers the asyncio/Rust hand-off this module serves. It states
the partial-failure paths the module exists to contain, what
_BlockingModeGuard and _paused_reader each guarantee, the single consumer,
and the reuse policy: further descriptor-lifecycle concerns for this
hand-off belong here, but the seams are not a general-purpose descriptor
utility. cuprum-design.md is already indexed by docs/contents.md.

Also add the worked usage example AGENTS.md requires to
_pump_over_raw_fds, covering both the handled and fall-back outcomes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot 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.

No quality gates enabled for this code.

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.

Hypothesis fault-injection tests for _pipeline_streams dispatch/fd lifecycle (cuprum/_pipeline_streams.py)

2 participants