Sync Guzzle 8.3 and reuse async handlers across coroutines

Merged
BI
2d ago
+3649-588

This updates the fork to upstream Guzzle 8.3.0 and lets a shared default client send asynchronous requests from concurrent coroutines. Each coroutine borrows a cURL multi handler while it has work; once idle, that handler returns to a bounded pool so later requests can reuse its connections.

Coroutine transports

The pool keeps up to 64 idle multi handlers by default. max_idle_multi_handlers changes that limit, and zero disables retention. It does not limit active requests. The separate upstream max_idle_handles option still controls idle easy handles within each handler.

Synchronous requests retain the existing fast path. Explicit multi handlers and clients with connection caps retain their ownership checks and shared-handler behavior. Existing Guzzle APIs and the global middleware used by framework integrations stay intact.

Cleanup tracks native work for each busy handler, rather than relying on a response promise remaining pending. This releases transfers whose promises were settled directly, finishes cancellation when removal callbacks create more work, and reports cleanup failures through the owning task queue. Closed handlers are discarded instead of being handed to another coroutine.

Upstream updates

The merge includes the released 8.3.0 changes and preserves upstream ancestry. Response streams now use upstream's implementations for incremental gzip/deflate decoding, chunked bodies, declared content lengths and transport reads. The fork's superseded incremental-stream implementation is removed.

This also includes upstream's stream-header and per-request deadline fixes, raw-deflate support, rejection of corrupt or trailing compressed data, and the Promises 3.1 State API. The fork continues to include the configurable easy-handle retention from guzzle/guzzle#3935.

Tooling and dependencies

Static tools now run on PHP 8.4 with Swoole, without Composer platform exceptions. Tool versions and upstream formatting are preserved. A small PHPStan stub corrects Swoole coroutine return types; it does not provide executable runtime substitutes.

This requires the release from hypervel/guzzle-promises#2. The PR targets the new 8.3 branch and prepares the matching fork release.

Validation

PHP-CS-Fixer, PHPStan and the affected Guzzle tests pass. Regression coverage includes concurrent clients, idle pool limits and reuse, closed handlers, queued ticks, directly settled transfers and cleanup failures. The candidate was also tested through copied Composer path dependencies against Hypervel's HTTP suite and the affected Sentry, Telescope, AI, MCP, AWS and Pusher integrations.


Summary by cubic

Merges upstream Guzzle 8.3.0 into the fork and lets a shared default client send asynchronous requests from concurrent coroutines.

Coroutine transports

  • Each coroutine borrows a cURL multi handler while it has work; idle handlers return to a bounded pool (default 64) so later requests can reuse connections. max_idle_multi_handlers sets the limit; zero disables retention, and the limit does not constrain active requests.
  • Synchronous requests keep the existing CurlHandler path, and explicit or connection-capped multi handlers keep their ownership checks.
  • Cleanup tracks native work per busy handler instead of relying on pending promises, releasing directly settled transfers and work created by cancellation callbacks; closed handlers are discarded.

Upstream sync and tooling

  • Adopts upstream's chunked, gzip/deflate, declared-length, and transport stream decoding, plus deadline, raw-deflate, and numeric-host fixes; the fork's superseded incremental stream is removed.
  • Requires hypervel/guzzle-promises ^3.1 and raises guzzlehttp/psr7 to ^3.2.
  • Static tools now run on PHP 8.4 with Swoole; a narrow PHPStan stub replaces the executable Swoole analysis shims, and CI analysis gets an explicit 512 MB memory limit.

Summary by CodeRabbit

  • New Features

    • Asynchronous cURL requests can now use separate handlers for concurrent coroutines, with idle handlers reused from a pool. The default pool retains up to 64 handlers; this limit can be configured.
    • Streamed responses now decode chunked and compressed bodies as data arrives and stop at the declared content length.
  • Bug Fixes

    • Improved handling of malformed, truncated, or unsupported response framing and compressed data.
    • Fixed edge cases involving long delays, oversized numeric hosts, and cancellation of unfinished transfers.