Skip to content

Add deterministic client lifecycle management #120

Description

@ZilvinasKucinskas

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    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