Skip to content

Add connection reuse and pooling#3

Merged
loks0n merged 10 commits into
mainfrom
connection-reuse-and-pooling
Jun 9, 2026
Merged

Add connection reuse and pooling#3
loks0n merged 10 commits into
mainfrom
connection-reuse-and-pooling

Conversation

@loks0n

@loks0n loks0n commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds opt-in connection reuse to both adapters and a connection pool, on top of a small interface refactor that makes them compose cleanly.

Interface split

  • New Utopia\Psr18\StreamingClientInterface — the streaming counterpart to PSR-18's ClientInterface (stream(), renamed from streamRequest() since it streams the response, not the request).
  • Adapter now composes ClientInterface + StreamingClientInterface + the withX config.
  • Utopia\Client now implements Adapter — which is what lets a fully-configured Client be used as a pooled resource.

Connection reuse — withConnectionReuse()

Promoted to the Adapter interface; off by default. Each adapter maps it to its own transport:

  • cURL — persists one CurlHandle, curl_reset()-ing it between requests. curl_reset() clears per-request options but preserves the connection cache, so the socket/TLS session is reused and no stale per-request option can leak.
  • Swoole — keeps a kept-alive coroutine client, cached per origin (a SwooleClient is bound to its origin at construction). Swoole re-checks the socket before each request and reconnects if dropped.

The flag is authoritative in both directions (keep_alive / CURLOPT_FORBID_REUSE). The Swoole adapter also normalises requestBody/uploadFiles every request — swoole never clears requestBody and only clears uploadFiles on a successful response, so without this a reused client could send a stale body (e.g. a bodyless GET after a POST).

Pooling — Utopia\Client\Pool

Borrows a client from a utopia-php/pools pool per request and reclaims it afterwards, sharing a bounded set of connections across concurrent callers. Implements ClientInterface + StreamingClientInterface (not Adapter — pooling load-balances across connections, so per-connection withX doesn't apply; configure the connections in init).

Validation

All behaviour was verified end-to-end against a keep-alive test server (reuse on/off, the authoritative override, swoole stale-socket auto-reconnect, and the POST→GET no-stale-body case). The reuse internals were also reviewed against the swoole 6.2.1 and libcurl 8.20.0 source.

  • 167 tests pass (added PoolTest and a Client reuse-forwarding test)
  • PHPStan level 10 clean, Pint clean

Docs

  • New docs/pooling.md
  • docs/configuration.md — Connection reuse section
  • README + CHANGELOG updated

Note

No permanent reuse regression test yet — php -S can't keep-alive, so locking down reuse/stale-state in CI needs a small keep-alive server added to the harness. Happy to follow up.

🤖 Generated with Claude Code

loks0n and others added 10 commits June 9, 2026 14:07
Split the transport contract into three layers: PSR-18's ClientInterface,
a new Utopia\Psr18\StreamingClientInterface for the streaming counterpart
(stream(), renamed from streamRequest), and Adapter composing both plus the
withX config. Utopia\Client now implements Adapter.

Add withConnectionReuse() to the Adapter interface, keeping the underlying
connection alive across requests to the same origin: the cURL adapter persists
and resets one handle (connection cache preserved), and the Swoole adapter keeps
a kept-alive coroutine client. The flag is authoritative over keep_alive /
CURLOPT_FORBID_REUSE in both directions. The Swoole adapter normalises
requestBody/uploadFiles each request so a reused client never leaks a prior
body or files.

Add Utopia\Client\Pool, which borrows a reused client from a utopia-php/pools
pool per request and reclaims it afterwards, sharing a bounded set of
connections across concurrent callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
utopia-php/pools pulls in utopia-php/telemetry, which requires the
ext-opentelemetry and ext-protobuf extensions; without them composer
install fails before tests run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Loading ext-opentelemetry alongside swoole segfaults the PHP 8.4 test
process. Only the no-op telemetry path is used at runtime, so ignore the
platform requirement at install time instead of enabling the extensions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the utopia-php/pools dependency tree loaded, running the swoole
coroutine tests in the same process as the rest segfaults the runtime on
PHP 8.4. Split them into their own PHPUnit invocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Utopia\Client implements Adapter, whose withX helpers return static.
Declaring them as self on Client is a fatal incompatibility on PHP 8.4
(8.5 permits self for a final class). Return static to match the interface
on both versions.

Also revert the diagnostic CI step and the swoole test-suite split that
were added while misdiagnosing this as a runtime crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The failure that prompted its removal was an unrelated PHP 8.4 variance
fatal in Client, now fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@loks0n loks0n merged commit 4f9ccd6 into main Jun 9, 2026
2 checks passed
utopia-php-monorepo Bot pushed a commit that referenced this pull request Jun 10, 2026
One phpunit (^12) pinned in the root composer.json; packages no longer
declare it. Package test scripts call `phpunit`, resolved from the
root vendor/bin which bin/monorepo puts on PATH; mirror workflows get
it via setup-php's tools input.

Migrates the phpunit 9 era leftovers: cli and platform phpunit.xml
schemas, cli's @dataProvider annotation to an attribute. cli's mirror
test matrix moves to 8.3-8.5 (phpunit 12 needs >=8.3). bin/monorepo
absorb now strips phpunit and rewrites test scripts for incoming
libraries.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant