Skip to content

Validate BodySender construction and closed-state operations #113

Description

@ZilvinasKucinskas

Priority: P0

Related API contract: Provide a stable error hierarchy and safe, class-specific metadata

Describe the issue

Wreq::BodySender.new currently passes a zero capacity to Tokio's bounded channel, which panics and terminates the Ruby process. Other invalid capacities are silently replaced with the default capacity of 8. After close, push silently returns nil and there is no closed? predicate.

Invalid Ruby input must be reported as a Ruby exception before entering a Rust API that can panic. The sender should also follow the ordinary Ruby lifecycle of a writable stream.

Reproduction

Tested with wreq 1.2.4:

require "wreq"

Wreq::BodySender.new(0)
# Rust panic: mpsc bounded channel requires buffer > 0
# The Ruby process terminates.

Other invalid values do not fail:

sender = Wreq::BodySender.new("not an integer") # accepted; capacity becomes 8
sender.close
sender.push("data")                              # => nil; data is discarded
sender.respond_to?(:closed?)                     # => false

Proposed Ruby behavior

Keep the existing positional constructor, but validate it exactly:

sender = Wreq::BodySender.new(8)
sender.closed? # => false

sender.close
sender.closed? # => true
sender.push("data")
# IOError: closed body sender
  • Omitted capacity continues to default to 8.
  • Zero and negative integers raise ArgumentError.
  • Non-integer values raise TypeError; they must not silently select the default.
  • close is idempotent.
  • Closing the producer closes only the sending side. If the receiver has not yet been attached to a request, queued bytes and the receiver remain available so the eventual request can drain them to EOF.
  • closed? reports whether the sender can accept more bytes.
  • push after close, or after the receiver has been consumed by a request, raises IOError (or a documented Wreq::BodyError < Wreq::Error) rather than dropping data.

Acceptance criteria

  • No Ruby value passed to BodySender.new can cause a Rust panic or native process termination.
  • Invalid capacity errors are raised synchronously and identify the argument.
  • closed? is available and changes to true after close or terminal receiver consumption.
  • A post-close push raises instead of returning success.
  • Existing backpressure for a valid bounded sender remains intact.
  • Tests include zero, negative, float, string, omitted, valid, double-close, and post-close push cases.
  • Tests cover queuing data, closing the producer, and then attaching it to a request; all queued bytes must reach the wire before EOF.
  • The panic regression is tested in a subprocess so the suite proves that Ruby exits normally.

Ruby ecosystem precedent

Ruby's SizedQueue is the closest standard bounded-channel API: SizedQueue.new(0) raises ArgumentError, non-numeric input raises TypeError, and operations on closed resources report their state instead of silently accepting data. SizedQueue.new(1.9) does accept and truncate the Float to a capacity of 1; this proposal is deliberately stricter because silently truncating a native channel capacity is surprising. BodySender should require a positive Integer. Ruby IO objects expose closed? and raise IOError for writes to a closed stream.

Relevant wreq-ruby source

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions