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
Priority: P0
Related API contract: Provide a stable error hierarchy and safe, class-specific metadata
Describe the issue
Wreq::BodySender.newcurrently 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. Afterclose,pushsilently returnsniland there is noclosed?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:
Other invalid values do not fail:
Proposed Ruby behavior
Keep the existing positional constructor, but validate it exactly:
ArgumentError.TypeError; they must not silently select the default.closeis idempotent.closed?reports whether the sender can accept more bytes.pushafterclose, or after the receiver has been consumed by a request, raisesIOError(or a documentedWreq::BodyError < Wreq::Error) rather than dropping data.Acceptance criteria
BodySender.newcan cause a Rust panic or native process termination.closed?is available and changes totrueaftercloseor terminal receiver consumption.pushraises instead of returning success.Ruby ecosystem precedent
Ruby's
SizedQueueis the closest standard bounded-channel API:SizedQueue.new(0)raisesArgumentError, non-numeric input raisesTypeError, 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 of1; this proposal is deliberately stricter because silently truncating a native channel capacity is surprising.BodySendershould require a positive Integer. RubyIOobjects exposeclosed?and raiseIOErrorfor writes to a closed stream.Relevant wreq-ruby source
src/client/body/stream.rslib/wreq_ruby/body.rb