Repository navigation
Release idle database sessions and improve coroutine HTTP streaming - #653
Conversation
Group the public implementation into seven review checkpoints with explicit implementer and reviewer responsibilities. Allow dependencies to move forward while requiring complete files, meaningful tests and signoff before advancing. Preserve copy-first porting and permit coherent whole-file commits with detailed rationale and validation. Build incremental HTTP streaming against the current framework handler boundaries and existing tests. Remove the unrelated transport-worktree prerequisite while preserving cancellation, resource ownership and performance acceptance requirements. Validation: reviewed both plan updates and ran git diff --check. Documentation-only change; no source tests or benchmarks were run.
Keep coroutine-owned concrete connections stable while allowing idle physical sessions to return to the pool before long external waits. Add releaseIdleConnections and scoped session pinning, preserving retained builders, sticky routing, query state, transactions, cursors and event callbacks. Keep config-first extensions and endpoints with different logical identities under whole-connection ownership. Select read endpoints per physical generation, preserve selected configuration metadata, settle leases exactly once, and clear hidden PDO holders promptly on disconnect and failed replacement. Preserve the current owner during terminal callbacks and forget lost resources before rollback callbacks can reconnect. Cover ownership, read/write routing, custom drivers, session configuration, failure recovery and teardown with regression tests. Document extension and pinning behavior and add an independently runnable database lifecycle benchmark. Benchmarks remain required before opening the framework PR; reviewed checkpoints may proceed before the idle measurement window. Validation of the complete checkpoint: formatting and source/type PHPStan passed; 40,181 framework tests, 632 Testbench tests and 6 package-consumer tests passed with expected service skips.
Group pending, committed and current transaction records in one non-copyable DatabaseTransactionState. Child coroutines acquire independent transaction ownership instead of sharing parent records and running parent callbacks prematurely or twice. Preserve transaction callback extension points while retrieving related state together. Keep the intentional Testbench setup-to-test transfer explicit through manager copy methods and a tests-only non-coroutine context setter, so normal child context propagation cannot inherit transactions. Add regressions for interleaved parent and child callbacks and explicit lifecycle transfer with nested transactions. Validation of the complete checkpoint passed formatting, source/type PHPStan, 40,181 framework tests, 632 Testbench tests and 6 package-consumer tests with expected service skips.
Retrieve scoped container instances, auth defaults, Inertia state, static instances and Blade context values once instead of repeating has/get lookups on request paths. Preserve null-as-absent behavior, false and zero scoped values, identity-based scoped removal and lazy fallback evaluation. In particular, Blade must not read an uninitialized fallback property when coroutine context already supplies the path. Existing behavioral coverage verifies the supported values and scoped resolution semantics. The complete checkpoint passed formatting, source/type PHPStan, 40,181 framework tests, 632 Testbench tests and 6 package-consumer tests with expected service skips.
Use a pull-driven cURL transport for streamed requests inside hooked coroutines. Each active response owns its transfer and applies backpressure while the caller consumes buffered bytes. Named connections retain at most three idle transports, matched by proxy ownership and replaced in least recently used order. Keep custom clients and handlers, PHP-stream fallback, destination policies, middleware and supported Guzzle 7/8 behavior intact. Separate the response-header deadline from idle read timeouts, preserve received responses on timeout and callback failure, and retain native transfer statistics. Settle transfers on completion, close, cancellation and abandonment without keeping request callbacks in worker-owned caches. Add opt-in cancellation for iterable response production when a client connection closes. Track every active producer, including pipelined HTTP/1 responses, and compose cancellation with application close callbacks only in supported server modes. Preserve cancellation through Guzzle and routing exception conversion. Document the existing quiet HTTP/2 stream-reset limit and expose fluent event-stream typing without narrowing public signatures. Validation: full composer fix passed, including 40,237 framework tests, 632 Testbench tests, six dogfood tests, PHPStan and formatting. Focused minimum-version Guzzle 7 coverage passed 75 cases; the original Guzzle 8 dependencies were restored. Controlled performance benchmarks remain a separate prerequisite before opening the framework PR.
Specify pull-driven streaming, bounded exact-signature idle reuse, timeout phases, Guzzle compatibility and transfer cleanup in the active port plan. Keep disconnect registration scoped to response production and distinguish whole-connection cancellation from the missing native HTTP/2 reset event. Record the verified stable-runtime limitation accurately and retain the pre-PR performance acceptance requirements. The framework changes remain independent of the later AI package implementation.
Declare fileExists() and directoryExists() on the shared contract while preserving the file-or-directory behavior of exists(). Existing adapters and pooled/scoped implementations already provide both operations; update the in-memory fixture and remove the obsolete read-through analysis suppression. Move Sentry instrumentation to the base filesystem decorator so plain and cloud contract implementations receive the same tracing as adapters. Regenerate the Storage facade annotations and document the required methods for custom implementations. Validation: filesystem cache fixtures, Sentry storage integration and facade tests pass; formatting, full PHPStan and the full framework suite pass.
Add ObjectType::getProperties() so schema consumers can inspect the original Type objects without serializing them or bypassing protected state. Preserve property names, object identity and empty-object behavior. Correct property-array annotations across the factory, contract and static API to include integer keys produced by PHP numeric-string normalization. Document the accessor without changing schema serialization. Validation: focused object-schema tests, formatting, full PHPStan and the full framework suite pass.
Add ClassMetadataCache::usesTrait() using the existing reflection cache. Resolve direct, nested and inherited traits once per queried class, then answer membership checks from that complete set. Keep worker-lifetime keys bounded by classes rather than arbitrary class-and-trait lookup pairs, so negative lookups do not grow the cache. Clear the new metadata through the existing flushState() test-cleanup registration and avoid introducing a reverse dependency on Support helpers. Validation: direct, nested, inherited, absent-trait and cache-reset coverage passes, together with formatting, full PHPStan and the full framework suite.
Watch resources/skills by default. Map native events for exact roots, their ancestors and recursive descendant directories so directory arrivals can trigger reloads even when native events omit newly created files. Exact-file watches use their parent as the arrival root and do not match nested directories. On Linux, retain explicit missing-root operands and add shallow ancestors through the first existing directory. Preserve canonical and literal mappings, glob filtering, sibling exclusions and the existing process grouping; do not broaden Darwin operands. Document conservative directory-arrival restarts and Linux fswatch limitations for initially absent roots and directory moves or renames. Recommend existing polling drivers for those workflows instead of adding rescans, latency tuning or process recycling. Record the resulting watcher requirements in the active plan. Validation: scripted mapping and real directory-arrival/subsequent-edit tests pass. Formatting, full PHPStan and all 183 Watcher tests pass after review corrections; the preceding full framework suite also passes. Native macOS missing-root behavior has not been verified.
Remove redundant repository flushes around APP_ENV masking. Environment adapters already read the live globals; replacing the repository discarded the immutable writer ownership needed to forget values loaded by earlier application bootstraps, leaking state into later tests. Have WithEnv capture its originating repository and raw prior value so cleanup survives scoped environment helpers replacing the active repository. Restore nested class and method attributes in reverse order while preserving external-value protection and leaving other lifecycle callbacks unchanged. Extend existing tests for scoped-helper composition, class/method attributes sharing a key in both environment-loading modes, and standalone application cleanup. The previously failing application-then-migration test sequence now passes. Validation: formatting and full PHPStan pass; the framework suite passes with 40,246 tests, package-mode Testbench with 635 tests, and dogfood with 6 tests. Existing service-dependent skips remain.
Keep CURLMOPT_MAXCONNECTS set to one on each streaming multi handle, but remove the dead failure branch around this fixed supported option and its unused exception import. Connection-cache ownership and limits are unchanged. Validation: formatting, full PHPStan and the full framework suite pass, including the HTTP streaming coverage.
Return the current execution's idle database sessions to their pools before real framework HTTP requests. Run after application request callbacks and fake selection, before destination resolution and transport I/O, so redirects and retries also release sessions borrowed by callbacks. Make the resolver operation static and delegate to it directly from the database manager. This avoids resolving database services or opening a connection merely to release sessions. Existing transactions, cursors, scoped foreign-key suppression and explicit pins retain their sessions. Document automatic release, lazy reacquisition, named/default session pins, manual release before coroutine fan-out and caller-supplied Guzzle clients. Explain the session-dependent compatibility cases in the porting guide. Verify real requests against a silent loopback origin with a one-slot pool, sibling database access, callbacks, retries, redirects and pin preservation. Formatting, both full PHPStan checks, the full framework suite, Testbench package mode and dogfood pass. Performance acceptance remains separate.
Pause native receives at BufferStream's high-water mark instead of after every body chunk. Resume only after draining the buffer, clearing the shared pause flag before resuming because cURL may synchronously fill and pause the buffer again. Reads continue returning available bytes immediately. Defer trailer callbacks when a transfer completes before response exposure. Deliver their original Guzzle arguments after successful header processing and before statistics, preserving response-bearing errors and cancellation. Restore the original trailer callback during pre-header retries and clear pending arguments during cleanup. Keep post-exposure completion on Guzzle's existing callback path. Extend streaming tests for bounded buffering, small and streamed trailers, header rejection, callback failure, retry and cancellation. Add synchronized reproductions for the native select readiness defect, concurrent PHP-stream header retention and Guzzle's shared fallback deadline. Keep the approved dependency-version skips and upstream-fix TODOs, without runtime workarounds. Remove deprecated no-op cURL close calls from the affected tests. Focused callback and streaming checks pass with Guzzle 7 and 8. Formatting, both full PHPStan checks, 40,255 framework tests, 635 Testbench package tests and six dogfood tests pass, with configured skips. The first full run hit the known Swoole #6280 manager shutdown race; the unchanged suite passed on rerun. High-water performance acceptance remains pending controlled benchmarks and the separate native select dependency decision.
Extend the database lifecycle harness with independently sized pools, optional checkout/hold/request latency measurements and a real HTTP wait. The HTTP scenario exercises automatic release without a manual release call; transaction mode provides the pinned comparison. Keep instrumentation optional so CPU and allocation measurements avoid its sample-array cost. Add a separate verified-TLS loopback origin and HTTP transport harness for cold and warm requests, paced streams, slow consumers, named transport reuse, buffered requests and bytewise baseline readers. Report first-event latency, throughput, client CPU, actual concurrency, native connection identities, heap, RSS, descriptors and garbage collection. Keep connection identities cumulative across samples and explain measurement bookkeeping costs. Document alternating paired runs with identical dependencies and runtime settings, an idle machine, raw reports outside the repository and no timing thresholds in CI. Keep simulated waits distinct from integrated HTTP results. Update the active port plan for default database release, callback ordering, dependency regressions and the remaining performance acceptance work. Harness syntax and command-line help checks pass, as do repository formatting, static analysis and the framework/Testbench/dogfood suites. These commits do not claim completion of the remaining controlled benchmarks or PR readiness.
Raise the lazy named streaming retention bound from three to 32. Bursts above the old bound repeatedly discarded reusable TLS connections, adding substantial setup cost. Preserve the existing ownership, exact proxy-signature matching and least-recently-used eviction; active requests remain uncapped and unnamed streams retain no idle transports. Extend the existing direct/proxy integration test through the configured retention bound, preserving credential and cookie isolation assertions and verifying reuse and eviction. Document the bound in the HTTP client guide and its measured descriptor cost in the benchmark guide. Correct the streaming buffering description and record the separate Guzzle buffered-retention dependency decision in the active plan. Validation: formatting and both full PHPStan configurations pass. The HTTP suite passes with 1,053 tests and 3,143 assertions, retaining 11 existing skips. Verified-TLS runs with the actual default reused all 32 warm connections without further connections and returned descriptors to baseline after cleanup. A concurrency-128 stability run retained the same bounded idle resources; the known Swoole select timing defect remains a separate native dependency.
Add a synchronized real-client regression for two bursts of four buffered requests through one named connection. Guzzle retains only three idle easy handles, so the second burst unnecessarily opens a fifth connection. Keep the owner-requested skip until configurable retention is available and adopted; no dependency workaround is introduced. Track both Guzzle-dependent skips and the post-fix Swoole native-select regression and performance checks in docs/todo.md. Update the active plan to allow the framework PR before the native fix while withholding unqualified buffering performance claims. Validation: the final default-configuration test failed its connection-count assertion in 20 of 20 runs, then passed in 20 of 20 runs with only a temporary vendor retention increase from three to four. The complete test file passed with that change and with the skip restored. Vendor was restored byte-for-byte. Formatting and full static analysis passed; the HTTP suite passed with 1,054 tests and 12 intentional or existing skips.
Exclude the imported, unported AI SDK documentation and the AI implementation plan from this framework change. Preserve both on feature/ai for the separate package port. Runtime code, framework documentation, regression tests and dependency follow-ups remain included.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
|
|
@cubic-dev-ai review |
@binaryfire cubic can't start this review because your workspace has reached its free monthly review limit. cubic has reviewed 124,545 of the 120,000 allowed lines of code this month. Reviews resume on 10 October 2026 (in 3 days). Paid plans include much higher monthly review limits. Upgrade now to resume reviews. To help optimise your usage, you can tune cubic to get the most out of your usage limits:
|
PR Summary by QodoRelease idle database sessions and stream coroutine HTTP responses
AI Description
Diagram
High-Level Assessment
Files changed (104)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can route each severity your way: inline, summary, both, or drop |
Include the effective driver when deciding whether selectable read or write records can share a logical connection across session leases. Different drivers need their own connection class and grammar even when the database and table prefix match. Resolve the current logical PDO after publishing ConnectionEstablished. A listener may reconnect during publication; returning the earlier PDO would otherwise overwrite its replacement. Cover both write and read resolution, including a replacement read PDO that remains lazy until the outer resolver resumes. Extend the identity tests so each case varies only its selected role, and verify that listener reconnects retain and return one pool slot. Clarify that connections and their builders belong to the coroutine that resolved them and must not be passed to another coroutine. Validated with formatting, full static analysis, and the database and database integration suites. The reconnect regressions fail against the previous implementation and pass with this change.
Keep a weak reference to the native handle in the receive callback. Guzzle 7 can discard a handle without clearing its callbacks, so a strong capture retained the handle, request and native resources until cyclic garbage collection. The active transfer continues to own the handle; the callback only dereferences it when pausing at the buffer limit. Extend the abandoned-response test to check native handle lifetime with cyclic collection disabled. Observe the real factory through a small decorator so the test itself does not retain callback-bearing options. The regression fails on Guzzle 7 before the change and passes on both supported Guzzle majors afterwards. Document that native cURL authentication negotiation can delay response exposure until body bytes arrive or the transfer completes, and that the request timeout covers this wait. A successful status alone cannot identify the final response during POST authentication negotiation. Validated with formatting, full static analysis, the HTTP client suite, and the streaming-handler tests on Guzzle 7.15.2 and Guzzle 8.2.
This PR lets requests return idle database sessions to the pool while waiting on an external service, and makes streamed HTTP responses deliver data incrementally. It also fixes several resource-lifetime and isolation problems exposed by those paths.
The motivating case is a request that reads from the database, waits several seconds for an API, then writes a result. That could be an AI response, a webhook, a payment service, or a file service. Holding a database connection during the wait limits concurrency even when the database itself has little work to do.
Release idle database sessions before HTTP requests
Previously, a coroutine kept its borrowed database connection until it finished. The HTTP client now returns idle database sessions before sending an outgoing request. The next query borrows a session automatically.
The PHP connection object remains owned by the request. Existing builders, query logs, callbacks and sticky read routing stay valid while the underlying PDO can return to the pool. This reuses existing database connections; it does not establish a new database connection for each request.
Release happens after request callbacks and before destination resolution or network I/O, including redirects and retries. Faked requests do not release connections, and the release check does not resolve database services or open a connection.
Active queries, transactions, open cursors and scoped foreign-key suppression keep their sessions automatically. Ordinary queries require no application changes. For other external work, or before starting child coroutines, release explicitly:
Children own their own database connections and cannot release a connection held by their parent. Callers supplying a complete Guzzle client through
setClient()also own the release boundary.Code that deliberately depends on one physical session across an external call can pin it:
DB::withPinnedSession()provides the same scope for the default connection. This is needed for temporary tables, session locks, retained raw PDOs/statements, or manual session changes spanning a release. Transactions already pin automatically. Session configurators still apply the current execution's settings before a borrowed PDO is used.Custom drivers registered with
DB::extend()retain their complete driver object until execution ends. The same applies to selectable endpoints with different drivers, database names or table prefixes. PDO drivers registered throughConnection::resolverFor()can participate in early release when their logical database identity agrees.Database measurements
These are local, pool-constrained workloads, not predictions of general SQL throughput. Measurements used an idle machine, PHP 8.4.25, Swoole 6.2.2, libcurl 8.5.0, file-backed SQLite, identical dependencies, CLI OPcache enabled, JIT disabled and GC enabled. Tables report medians from ten alternating runs; p95 columns are medians of each run's p95.
The integrated workload runs 128 requests at concurrency 32 with four database slots. Each request executes two queries separated by a real, verified-TLS HTTP call whose server waits 50 ms before returning headers. There is no explicit release call in the application path.
Automatic release produced 4.35× throughput here. Throughput ranges were 74.4–75.1 requests/s before and 314.5–333.9 after. All borrowed slots were returned. Checkout time includes acquisition bookkeeping as well as waiting for a slot.
A separate experiment replaces the HTTP call with a 50 ms simulated external wait to isolate database retention. It improves from 78.1 to 598.1 requests/s, approximately 7.7×. That is a different workload from the integrated result above.
The integrated CPU increase includes repeated HTTP/TLS setup. A connection-count diagnostic observed 11–12 HTTP connections before versus 119 after, for 128 requests: releasing database slots allows larger HTTP bursts, which exceed Guzzle's three-idle-handle default for buffered requests. Configurable buffered retention is a separate dependency enhancement, tracked with a reproducible test. The streaming retention change below does not fix that buffered path.
The database ownership cost was measured separately, without HTTP or early release:
An extra 10 µs per request at 10,000 requests/s uses 0.1 CPU core, or 10% of one core. The added work is constructing and cleaning up caller-owned connection state. Reusing that state across requests would break retained builders and other caller-owned objects.
For HTTP requests that never use the database, before/after CPU and throughput ranges overlap. A separate control-adjusted probe measured the empty-context release check at about 0.18 µs. There is no database initialization on that path.
Select read endpoints with their matching metadata
Read/write connection construction could select one read endpoint for the PDO and another for its configuration metadata. The factory now selects once and uses that record for both.
Explicit
::readpools also selected a read record when the worker constructed the pool, keeping that choice for the worker lifetime. Selection now happens when a physical connection is created. Read records must agree on effective pool options, so pool behavior does not depend on whichever record was chosen first.Release database resources promptly and safely
Disconnecting or replacing a PDO could leave a hidden reference in a discarded connection object's grammar cycle. The cleanup paths now clear those holders explicitly, including failed preparation and SQLite pool bootstrap.
Lease settlement also preserves ownership during lifecycle callbacks and clears lost resources before rollback callbacks can reconnect. Tests cover retained builders, read/write routing, nested pins, custom drivers, failed acquisition, reconnects and coroutine cleanup.
Preserve reconnects made by connection listeners
A
ConnectionEstablishedlistener can reconnect before the original PDO resolver returns. Returning the earlier PDO would overwrite the replacement session. The resolver now returns the logical connection's current PDO after notifying listeners. Tests cover both write and read resolution, including a replacement read connection that remains lazy until the outer resolver resumes.Keep transaction callbacks isolated between coroutines
Copying coroutine context could share mutable transaction records with a child. The child could then alter its parent's records or run callbacks belonging to the parent.
Pending, committed and current transaction records now live together in a non-copyable
DatabaseTransactionState. Testbench explicitly transfers the same state across setup, test and teardown; normal child coroutines do not inherit transaction ownership.Deliver HTTP streams incrementally
The PHP stream-wrapper path can batch body data instead of exposing each available chunk. Hooked coroutine requests now use a pull-driven cURL streaming body. Reads return available bytes immediately, and a bounded buffer pauses network reads when the consumer falls behind. There is no background producer coroutine.
Each active response owns its transfer. Completion, close, cancellation, errors and abandoned consumption release it. Named connections reuse idle transports without retaining request callbacks, credentials or cookies. Proxy tunnel reuse requires the same ownership signature.
For streaming requests,
timeoutbounds the header phase. When cURL negotiates authentication, this includes waiting for the first body bytes or transfer completion, since even a successful status can be an intermediate response.read_timeoutbounds idle gaps during headers and body reads; its default is 60 seconds, and zero disables that idle limit. Long streams can continue beyond the header timeout. Custom handlers/clients retain their existing ownership, and the PHP-stream fallback remains available for supported options that require it.Bounded connection reuse
Named streaming connections retain up to 32 idle transports per worker, created only when needed. This does not limit active requests. Larger bursts can still open additional connections, which are discarded when the idle cache is full.
Ten alternating paired measurements used 256 verified-TLS requests at concurrency 32, unpaced 4 KiB responses, and the same transport with only the retention bound changed:
On this Linux build, each retained transport accounts for a socket and both ends of a wakeup pipe. The higher bound trades idle resources for less connection setup. Descriptor counts returned to their starting values after cleanup. Runs using the final default also verified reuse at concurrency 32 and bounded retention at concurrency 128.
Swoole's native
curl_multi_select()currently has a separate readiness defect that can delay a streamed event or completion by up to one second. It appeared in paced measurements. The regression is captured, and post-fix measurements are tracked indocs/todo.md; this PR adds no polling or shorter-wait workaround. No general performance claim is made for the buffering change while that runtime issue remains.Release discarded streaming handles without cyclic collection
Guzzle 7 can discard a cURL handle without clearing its callbacks. A receive callback holding that same handle creates a cycle that retains native and request resources until garbage collection. The callback now holds a weak reference; the active transfer remains the owner. The abandoned-response test verifies that both the response body and native handle are released with cyclic collection disabled.
Preserve streaming callback order and cancellation
A small response can finish before the handler exposes its headers. Its trailer callback must still run after
on_headers, rather than before it. Completion now preserves that order and delivers the original Guzzle callback arguments.Pre-header retries retain the original callbacks. Callback failures retain their response where applicable, and coroutine cancellation passes through Guzzle and routing exception conversion unchanged. Statistics are reported once at the handler-return boundary.
Cancel opted-in response producers when their client disconnects
An iterable response can now cancel its producer when the client connection closes:
Cancellation applies only while producing that response. Application close callbacks still run, and unrelated requests or later termination work are unaffected. The connection map retains every active producer, including pipelined HTTP/1 responses. Tests verify that cancellation interrupts a silent upstream wait and releases the transfer.
Existing Swoole releases do not notify PHP when one HTTP/2 stream is reset while its connection remains open. A quiet producer detects that case on a later failed write or operation completion/timeout. Whole-connection closes are handled now; the separate stream-cancel event is tracked as a future runtime integration.
Avoid repeated coroutine-context lookups
Container scoped resolution, auth defaults, Inertia state, static instances and Blade rendering now retrieve context values once instead of checking and retrieving them separately. Null-as-absent behavior, false/zero values and lazy fallbacks are preserved. Blade does not touch an uninitialized fallback property when context already contains the value.
Expose file and directory checks through the filesystem contract
Filesystemnow declaresfileExists()anddirectoryExists(). Concrete adapters already support these checks; contract consumers and custom implementations can now rely on them without capability probes.exists()still accepts either a file or a directory.Custom contract implementations must provide both methods. The Storage facade and porting documentation reflect the contract.
Trace existence checks on all Sentry filesystem decorators
Existence-check instrumentation moves to the base filesystem decorator. Plain and cloud contract implementations now receive the same tracing as adapters, without duplicate instrumentation methods on the adapter decorator.
Expose object property schemas without serialization
ObjectType::getProperties()returns the original namedTypeobjects. Consumers can inspect or reuse them without serializing the schema or bypassing protected state.Property-array annotations also accept integer keys produced by PHP's normalization of numeric strings. Schema serialization is unchanged.
Cache recursive trait membership
ClassMetadataCache::usesTrait()resolves direct, nested and inherited traits once per class:The cache stores class metadata rather than every queried class/trait pair, so negative lookups do not keep adding entries. Existing static cleanup clears it between tests.
Watch directory arrivals and skill paths
Fswatch event mapping now recognizes watched roots, their ancestors and recursive descendant directories. Linux watches include the shallow ancestors needed to notice an initially missing root. Glob filtering and sibling exclusions remain intact, without broadening macOS watch operands.
resources/skills/**is included in the default watch paths. The docs explain custom skill paths, conservative reloads on directory arrivals, and Linux fswatch event loss around missing roots and directory moves. Polling drivers remain the appropriate option for those workflows; no rescanning or process-recycling workaround is added.Restore Testbench environment values through their original repository
Flushing the dotenv repository while masking
APP_ENVdiscarded the writer ownership needed to remove values loaded by an earlier application. Scoped environment helpers could also replace the repository before aWithEnvrestorer ran.The redundant flushes are removed.
WithEnvcaptures the repository that owns its value and restores the raw prior value through that repository. Nested class/method restorers run in reverse order, preserving external environment values and preventing leakage between tests.Verification and reproducibility
Formatting, source/type static analysis, the framework suite, Testbench package mode and the package-consumer suite pass locally. Focused streaming checks also cover the supported Guzzle 7 and 8 branches. Tests exercise real loopback I/O, database pool contention, cancellation, isolation and cleanup; performance thresholds are not part of CI.
tests/Benchmarks/Databaseandtests/Benchmarks/HttpTransportinclude standalone harnesses and reproduction instructions. They report actual concurrency, latency, CPU, connection reuse, pool checkout/hold times, memory, descriptors and GC. Known dependency regressions have explicit tests and tracked follow-ups rather than production workarounds.Note
Add database session leases, coroutine cURL streaming, and response cancellation
ConnectionLeaseso each coroutine keeps a stable logicalPdoConnectionwhile the pool borrows and returns physical PDO sessions. Idle sessions are released automatically around outbound HTTP requests via new middleware in PendingRequest.php, andConnection::withPinnedSession()keeps a session attached for temporary tables or advisory locks. Leases activate only when no connection extension is configured and all read/write endpoints resolve to the same logical database identity.CurlStreamingHandlerwith a newCurlStreamingBodyPSR-7 stream: supported stream requests use coroutine-native cURL chunking with backpressure instead of the buffered fallback sink middleware.IterableStreamedResponseviacancelOnDisconnect().ResponseCancellationin src/server/src/ResponseCancellation.php registers producer coroutines and cancels them when the connection closes; supported in coroutine base mode and selected process dispatch modes.FswatchDrivertarget resolution to watch shallow ancestor directories so root-arrival events and missing watch roots are observed without recursing from the project root.fileExistsanddirectoryExiststo theFilesystemcontract; moves the Sentry wrappers fromFilesystemAdapterDecoratortoFilesystemDecorator, andexists()now covers both files and directories.InvalidArgumentExceptioninDatabasePool::readPoolConfig; stream requests with restricted destination policies are rejected only when native streaming is unsupported; customFilesystemimplementations must addfileExists/directoryExiststo satisfy the widened contract in Filesystem.php.Macroscope summarized 6d1f614.