Priority: P1
Related API contract: Provide a stable error hierarchy and safe, class-specific metadata
Describe the issue
Wreq::Client owns a reusable native client but does not expose close or closed?. Callers cannot reject new work or cancel binding-owned operations during shutdown; cleanup depends on object lifetime and native drops.
Ruby resource-owning APIs generally provide explicit, idempotent cleanup and a block form that uses ensure internally.
Proposed Ruby API
client = Wreq::Client.new
client.get("https://example.com")
client.close
client.closed? # => true
Wreq::Client.open(timeout: 5) do |http|
http.get("https://example.com")
end # closes even if the block raises
Wreq::Client.open may be a class method implemented in Ruby around new and close. close should be safe to call more than once.
Lifecycle semantics to define
- Closing atomically stops accepting new requests and releases the Ruby wrapper's reference to its native client.
- Requests started after close raise a stable wreq-ruby error, for example
Wreq::ClientClosedError < Wreq::Error.
- In-flight operations owned by the binding should be cancelled promptly where the native operation supports cancellation. Document whether
close waits for their cleanup; a non-blocking default is reasonable.
- Closing one client must not close resources owned by another client clone or session unless they intentionally share ownership.
closed? must be thread-safe.
Rust wreq does not currently expose a public method that synchronously destroys an entire shared connection pool. Therefore close should not promise that every file descriptor is gone before active request clones unwind. Releasing idle connections immediately is desirable only if the upstream client can guarantee it. The public contract should focus on logical shutdown, cancellation, and ownership rather than an unverifiable pool-destruction guarantee.
This request is about deterministic ownership and cleanup. It does not replace the existing fiber interruptibility issue; explicit cancellation of an individual in-flight request can remain separate.
Process changes are a separate ownership boundary. close should compose with, but is not a substitute for, the fork-safety contract.
Acceptance criteria
Client#close exists, is thread-safe, and is idempotent.
Client#closed? accurately reports lifecycle state.
Client.open(**options) { |client| ... } always closes in an ensure path and returns the block result.
- New requests after close fail before network I/O.
- The wrapper releases its native client reference, and the exact cleanup/cancellation behavior for concurrent and in-flight requests is documented and tested.
- The API does not claim synchronous pool destruction unless that behavior is guaranteed by Rust wreq.
Ruby ecosystem precedent
Net::HTTP exposes start, finish, and started?, including block-scoped sessions.
- HTTPX sessions expose
Session#close and use an ensure path for wrapped sessions.
- http.rb supports block-scoped persistent clients.
- Faraday exposes
Connection#close.
- Excon exposes connection reset for persistent sockets.
wreq-python implements close by cancelling a client-scoped token, preventing new work and cancelling operations without claiming a synchronous Rust pool teardown.
Relevant wreq-ruby source
Priority: P1
Related API contract: Provide a stable error hierarchy and safe, class-specific metadata
Describe the issue
Wreq::Clientowns a reusable native client but does not exposecloseorclosed?. Callers cannot reject new work or cancel binding-owned operations during shutdown; cleanup depends on object lifetime and native drops.Ruby resource-owning APIs generally provide explicit, idempotent cleanup and a block form that uses
ensureinternally.Proposed Ruby API
Wreq::Client.openmay be a class method implemented in Ruby aroundnewandclose.closeshould be safe to call more than once.Lifecycle semantics to define
Wreq::ClientClosedError < Wreq::Error.closewaits for their cleanup; a non-blocking default is reasonable.closed?must be thread-safe.Rust wreq does not currently expose a public method that synchronously destroys an entire shared connection pool. Therefore
closeshould not promise that every file descriptor is gone before active request clones unwind. Releasing idle connections immediately is desirable only if the upstream client can guarantee it. The public contract should focus on logical shutdown, cancellation, and ownership rather than an unverifiable pool-destruction guarantee.This request is about deterministic ownership and cleanup. It does not replace the existing fiber interruptibility issue; explicit cancellation of an individual in-flight request can remain separate.
Process changes are a separate ownership boundary.
closeshould compose with, but is not a substitute for, the fork-safety contract.Acceptance criteria
Client#closeexists, is thread-safe, and is idempotent.Client#closed?accurately reports lifecycle state.Client.open(**options) { |client| ... }always closes in anensurepath and returns the block result.Ruby ecosystem precedent
Net::HTTPexposesstart,finish, andstarted?, including block-scoped sessions.Session#closeand use anensurepath for wrapped sessions.Connection#close.wreq-pythonimplementscloseby cancelling a client-scoped token, preventing new work and cancelling operations without claiming a synchronous Rust pool teardown.Relevant wreq-ruby source
src/client.rslib/wreq_ruby/client.rb