Repository navigation
Give control-plane requests a timeout, and use it in Waterline #203
Description
Problem
A PHP SDK client, including Waterline in service mode, waits forever on a Server that accepts connections but doesn't answer.
- The SDK's Guzzle client is created with only
http_errors, and timeouts are set only for bounded or streaming requests. Ordinary control-plane calls (list, describe, start, signal) have no timeout, and the client has no option to set one. - The Python client defaults to a 60-second timeout, and the Rust client builder accepts one.
- Waterline builds its SDK client without a timeout, and its browser HTTP client has none either. A stalled Server leaves the page spinning and ties up a PHP worker. The run list has no Retry, unlike the dashboard.
Source-verified, not reproduced.
Acceptance criteria
- The PHP SDK client takes a request timeout with a sensible default, applied to every control-plane call. A timeout surfaces as a typed transport error.
- Waterline sets that timeout from configuration and maps it to its existing typed backend error. Its browser requests time out, and the run list offers Retry.
- A test against a Server stub that never responds shows the client and Waterline returning a timeout error within the configured bound.
Current SDK compatibility decision (2026-10-23)
PR209 now delegates owned no-cURL control requests to mature HTTP_Request2 2.8.0, with net_url2 2.2.3 and pear_exception 1.0.2 floors. PHP^8.1, ext-json, Guzzle7/8 and PSR-message1/2 remain unchanged; OpenSSL stays optional for HTTP-only consumers. The source resolves normally on a PHP8.1 Composer platform with OpenSSL disabled and Guzzle7/PSR-message1. Actual HTTP-only calls also pass with cURL/TLS functions disabled. Production advisory audit is clear. Final candidate fb244e708272267657ea741e9a68832f72ca008d / tree 3db9cf9221b23869da8688ca301f8d468ac513a3 proposes compatible3.4.0; publication and normal registry acceptance remain pending. HTTP-only/optional OpenSSL follows the existing published platform contract and maintainer compatibility decision.
The adapter keeps Guzzle's proxy selection, forwards HTTP through HTTP_PROXY and tunnels HTTPS through HTTPS_PROXY, honors NO_PROXY, and retains certificate/hostname verification and fresh authentication headers. Existing injected clients keep their own HTTP/TLS/proxy/timeouts. Worker/payload budgets remain independent. Mutations make one attempt.
Concrete transport decisions: the PHP stream path sends HTTP/1.1 with Connection: close; HTTP_Request2 owns HTTP serialization, headers, chunking, content decoding and body framing. Its incomplete-transfer warnings become typed transport failures, and the adapter requires its parser to reach a terminal chunk. CONNECT and the OpenSSL TLS upgrade share the original deadline; a delayed-CONNECT/stalled-TLS test rejects resetting that allowance. HTTPS-scheme proxies remain unsupported in this stream path. Normally installed SDK3.3.0/Guzzle7.16 and8.3 each passed6 proxy controls/94 assertions confirming the same native refusal and working HTTP forwarding, verified CONNECT and NO_PROXY (receipt); configured proxies are never bypassed on failure. cURL remains the existing Guzzle implementation.
Rejected alternatives and original evidence remain in history: NativeHttpClient checkpointd711688 exceeded a slowly arriving header deadline; Amp checkpoints29740b6/45b09d1 passed component tests but require OpenSSL; React/Requests explorationffb86d0 added unnecessary adapter/framing work. The earlier dependency choice is not a compatible release. No tag or timeout delivery is claimed by those checkpoints.
SDK3.4.0 is now published at 1bf25b5848f2031e72a3da38794b51db1d533546 / tree 3db9cf9221b23869da8688ca301f8d468ac513a3. Candidate/main CI39945233807/39946365023, Sonar/CodeQL, portal/live guidance and source-free normal installed SDK3.4.0/Server2.5.0 minimum-baseline smoke39946459071 pass. Fresh Guzzle7/PSR1 and Guzzle8/PSR2 each pass68 real HTTP controls/683 assertions, with HTTP-only/optional OpenSSL preserved. Installed published Server2.11.0 namespace and all three original killed-node recovery controls pass. Exact package/image/worker/run identities, outcomes and retained failures. Scoped Sonar dispositions and both fixed complexity findings are recorded above; no global profile change.
Waterline service API acceptance: the Waterline owner's normal-package kernel/real-HTTP receipt qualifies the existing typed HTTP502 mapping: Waterline2.13.0/SDK3.4.0 default60s returns request_timeout after60.162s; application-injected0.2s returns after0.324s. Healthy list/namespace/read-only behavior passes; one request per stalled peer. This is API integration, with no Core/path override or service-image adoption.
Remaining acceptance: Waterline#241 owns timeout configuration, browser timeout and run-list Retry under General's coordinated follow-through. The actual fromConfig path still does not pass a configurable timeout, and the browser/Retry gaps remain. Keep this issue open for those product changes; API smoke alone does not complete them. SDK210 tracks a separate confirmed outbound metadata numeric-kind defect and does not hold or undo this timeout qualification.
Activity
rmcdaniel commentedon Oct 21, 2026
Accepted as a product reliability fix spanning the PHP transport and Waterline. Ordinary control-plane requests need a configurable finite bound, a typed timeout outcome, and a usable retry action in the viewer. A never-responding real HTTP fixture will establish the before/after behavior.
Keep long-poll and streaming budgets distinct, and preserve explicitly configured custom transports, private trust stores and dynamic authentication. A timeout after a mutation is an ambiguous outcome; this change must not blindly resubmit starts, signals or updates. Safe command retry remains coordinated with shared #216.
The critical PHP runtime update (Server #591) and the admitted activity-lease delivery are ahead of this P3. Current SDK #202 does not resolve the ordinary-request timeout requirement.
rmcdaniel commentedon Oct 23, 2026
The ordinary-request stall is now reproduced with a normal Composer installation of SDK 3.2.0 at
dfaae75a94b1d855379863799ce2c9a53f900f97, PHP8.3.36, Guzzle8.3.0 and Apache Avro1.12.2. A real local TCP HTTP peer acceptsGET /api/workflows, reads the request and deliberately sends no response. The default Client process is still running after a two-second observation window and requires the fixture to terminate it. This is a bounded observation of an unbounded default, not a claim that the request was observed forever. No published Server or Waterline was exercised in this component check.The same peer with an explicitly supplied Guzzle client configured with
timeout=0.2andconnect_timeout=0.2returns in0.317seconds asDurableWorkflow\Exception\ServerException, status0, withisTransientConnectionFailure()=true. Thus existing custom transport timeout settings already provide a typed failure; the normal Client/default transport still lacks the accepted configurable finite control-plane budget. Client start/signal/update ambiguity must remain visible rather than be automatically retried.No implementation or release is claimed by this observation. #197/PR207 and #193/PR208 remain ahead in the SDK delivery lane. Any SDK timeout implementation must preserve private trust, dynamic authentication, fresh-process activity lease configuration and distinct long-poll/stream budgets, and coordinate actual Waterline configuration/error/Retry acceptance. The disposable peer/client processes and scratch files are retired after recording this result.
rmcdaniel commentedon Oct 23, 2026
A slow-body control reveals a compatibility requirement for the fix. Using PHP8.3.36 and Guzzle7.15.5 from the same published Server2.11.0 tools image, the SDK's unchanged Psr18Transport makes an ordinary workflow-list request to a real HTTP peer. The peer sends valid200 headers and a JSON body one byte every150ms. Both explicit Guzzle handlers are configured with timeout=1, read_timeout=1 and connect_timeout=1.
This is an adapter component check, not a normal installed SDK release qualification. The transport source is unchanged from published3.2.1, and neither product source nor Server implementation was edited. All disposable peer/client processes and scratch are retired after recording the result.
Guzzle8.3.0's official StreamHandler source contains a monotonic deadline-aware buffered-body path. That is source evidence, not runtime qualification of every Guzzle8 profile. Merely passing timeout options is insufficient evidence for the supported Guzzle7 stream profile.
Product decision: the accepted finite control-plane budget needs never-response and slow-header/body controls, with default/custom HTTP clients and supported Guzzle profiles distinguished. Keep worker long polls and supervised payload budgets separate, preserve actual TLS/authentication configuration, and do not retry ambiguous mutations. Retain PHP8.1/Guzzle7 compatibility; a dependency or capability-boundary decision must be explicit before publication. No timeout implementation/release is claimed yet, and SDK3.3.0 qualification remains first. This requirement calls for a product correction, not a docs-only resolution.
rmcdaniel commentedon Oct 23, 2026
Taking the SDK implementation portion next. Default Client control-plane JSON requests will have a configurable finite total budget and a typed request_timeout failure, with no automatic replay of ambiguous mutations. Injected transports keep their actual TLS/authentication and timeout configuration; conflicting SDK timeout overrides must fail explicitly rather than reconstruct or silently ignore a client. Worker long-poll, lease and payload-transfer clocks remain separate.
The verified Guzzle7 stream inactivity gap calls for a mature native HTTP deadline implementation while preserving PHP8.1 and Guzzle7 compatibility. The proposed dependency-boundary exception is limited to the framework-neutral HTTP-client component and its contracts/polyfills. Laravel/Illuminate, framework bundles/containers, Core and Server remain outside the production dependency graph. This maintainer default change will have explicit dependency/security-floor and framework compatibility tests. Actual acceptance includes stalled headers, slow headers/body, cURL/native paths, one mutation attempt, injected-client behavior, private trust/hostname validation and dynamic authentication.
Waterline configuration, its typed backend mapping, browser timeout and run-list Retry remain consumer work in the Waterline repository. This SDK session does not edit Waterline or claim the cross-repository acceptance complete from a library release.
rmcdaniel commentedon Oct 23, 2026
SDK source qualification for prospective3.4.0 at
7073b3de10688b5b75cb95871de632ee156be6c6/ tree307fcbf7440d1b711370751602b5a11a83f056fepasses real published-Server acceptance with the cURL functions disabled on the control client. This is a versioned source/path consumer, not a registry artifact. Its path reference ise188df807c56e4f84d79b1ecf44c2eadd6c2e245.Published Server2.11.0 image
durableworkflow/server@sha256:b29157eb2cf844916debbbb3c01b3257672e0dece778a9c6fa539043a3038f02, PHP8.3.36; consumer Guzzle7.16.0/PSR-message1.1, HTTP_Request2 2.8.0, net_url2 2.2.3 and pear_exception1.0.2. Actual source SDK registration is3.4.0. The typed cross-namespace call retains caller authority, exact Unicode/max-int/null values and one each of StartAccepted, WorkflowStarted, ServiceCallStarted, ServiceCallCompleted and WorkflowCompleted. Receipt: callerphp-caller-4ca963c469cc, targetphp-target-4ca963c469cc, workflowphp-service-4ca963c469cc, run01m5m68ry5zgy0b8y40aaqt3nf, call01m5m68wqgffarq03rtmc8khmq.Local full2774 tests/14312 assertions pass with56 integration skips, PHPStan and production audit/boundary pass, release identity is durable-workflow-php/3.4.0. The normal PHP8.1 CI now resolves an HTTP-only Guzzle7/PSR-message1 consumer with platform cURL/OpenSSL disabled and executes a real HTTP call with TLS functions disabled. Final candidate CI39940766856 is running its ordinary matrix.
The first native HTTP prototype failed a slow-header deadline and the Amp variant raised the OpenSSL installation floor; neither is selected. An intermediate stock blocking TLS upgrade measured0.8227536s for a0.5-second budget after0.3-second CONNECT delay. That failed approach was rejected; the current upgrade is nonblocking inside the same deadline. A fixture filter arity error and an initial source-consumer mount error were corrected; they are not package delivery evidence. Original checkpoints remain in PR209 history.
Sonar currently flags the literal HTTP scheme used to normalize an explicitly configured scheme-less proxy (S5332); HTTPS origins retain verified TLS through CONNECT. That finding needs scoped maintainer triage under the existing HTTP-only compatibility contract; the gate is not bypassed. No tag, published timeout fix or Waterline delivery is claimed yet. Normal registry installation/acceptance follows qualified publication; Waterline follow-through remains separate.
rmcdaniel commentedon Oct 23, 2026
Maintainer dispositions and the requested pre-tag proxy compatibility check are now recorded for prospective 3.4.0, candidate
fb244e708272267657ea741e9a68832f72ca008d, tree3db9cf9221b23869da8688ca301f8d468ac513a3(PR209). No tag exists yet.Sonar API receipts confirm scoped ACCEPTED/WONTFIX transitions for S5332 (
AaFoSpYROvglcMGQ98fr) and Client S107 (AaFoDP6y-57u3iefxzOh). The HTTP literal normalizes only a caller-configured proxy; it does not rewrite an HTTPS origin or disable verified CONNECT/TLS. The optional Client argument is appended because positional and named constructor calls are public compatibility contracts; regrouping or reordering legacy arguments would break consumers. HTTP-only installation and optional OpenSSL follow the existing published platform contract and the maintainer's compatibility decision.The two cognitive-complexity findings were addressed by separating request construction, response decoding, Guzzle options and failure policy inside the same transport. Final analysis is pending. S1448 (
AaFoEliE51wSqM6WSqMj) has scoped ACCEPTED/WONTFIX treatment: this transport implements the existing bounded request and payload/progress interfaces, and splitting it merely to meet a method-count threshold would scatter its shared injected-client and clock/error policies. HTTP parsing and socket deadlines already have their own mature-client adapters. No global rule, profile or quality-gate setting changed.Normally installed baseline compatibility: fresh registry consumers installed SDK 3.3.0, source reference
0f5146c1eee0e9b8b94f8b5f993f6d0e869d87af, with Guzzle 7.16.0 and 8.3.0 (PSR-message2.0), PHP8.3.36. Each passed 6 real proxy tests /94 assertions using both cURL and the owned no-cURL path: plain HTTP forwarding, verified HTTPS through HTTP CONNECT, HTTPS-scheme proxy refusal and NO_PROXY bypass. Against a live TLS proxy and an origin trusting the same private CA, Guzzle7 returned typed ServerException/status0, reason null, "Connection refused for URI https://localhost:.../api/workflows" in0.0324s; its handler context retains the selectedhttps://...proxy. Guzzle8 returned typed ServerException/status0, reason null, "HTTPS proxies are not supported by the stream handler." in0.0301s. Neither submitted an origin request. The new native handler's explicit refusal therefore preserves the previous owned Guzzle support boundary; configured proxies are not bypassed on failure. cURL retains its separate proxy trust policy.A baseline-only diagnostic initially assumed Guzzle8 retained getHandlerContext; that fixture mistake was corrected and the Guzzle8 matrix rerun successfully. The proxy fixture also now accepts the origin-form plus Host representation used by PHP's HTTP wrapper. These are retained fixture failures, not evidence of a published SDK transport regression.
The final candidate keeps the source-qualified transport semantics and runs the normal supported PHP/framework matrix, HTTP-only platform guard, Sonar and other required checks before merge/tag. Normal registry3.4.0 acceptance follows publication; Waterline acceptance remains separate and this issue stays open.
rmcdaniel commentedon Oct 23, 2026
SDK3.4.0 is published and passes fresh normal-registry installed controls. Release; annotated tag
45b768151ed32ad0ed099d3e4e573a2f031e2baa→ merge1bf25b5848f2031e72a3da38794b51db1d533546, tree3db9cf9221b23869da8688ca301f8d468ac513a3. The merge tree exactly matches tested candidatefb244e708272267657ea741e9a68832f72ca008d. Packagist source and dist both identify that merge. Runner/source tests use that candidate; no patched registry package or path repository is used for this acceptance.Candidate CI39945233807, fresh Sonar39945233849, CodeQL and public boundaries pass. Local full2774 tests/14312 assertions pass with56 integration skips; PHPStan, dependency/security-floor and replay/corpus policy pass. Main CI39946365023 passed. Portal39946365179 and source-free SDK3.4.0/Server2.5.0 minimum-baseline smoke39946459071 passed, as did main Sonar39946364882. All ordinary candidate/main checks are now complete; main CodeQL also passed. The source-free Server2.5.0 image resolves to
durableworkflow/server@sha256:66bd0e510c033af611cc1e2191e1ed592acf26e80f89766b0a3e1a0e463ccd19; installed SDK source remains the release merge. Class-oriented execution, nested/query/update state isolation, shared dependency lifetime and worker deregistration pass. Live portal client setup shows the default/custom timeout, HTTP_Request2, HTTP-only and proxy guidance.Fresh Composer installs used exact SDK3.4.0, no custom repository, PHP8.3.36:
durable-workflow-php/3.4.0.Each graph passes 68 real timeout/TLS/proxy tests /683 assertions. Network children load the normally installed package; PHPUnit's parent harness loads repository test utilities. Coverage includes native/cURL never-response, slow headers/body, delayed CONNECT plus stalled TLS under one budget, verified/private/untrusted CA and wrong-host behavior, HTTP forwarding/NO_PROXY, HTTPS-scheme proxy refusal, dynamic/URI authentication, exact namespace URL/query handling, complete/truncated framing, no mutation resubmission and separate worker/payload clocks. The HTTP-only child disables TLS functions; the normal PHP8.1 CI guard also passed. Read-only test mounts emitted only a PHPUnit result-cache write warning after successful assertions.
Published runtime acceptance,2026-10-23 11:30–11:32UTC: immutable Server2.11.0
durableworkflow/server@sha256:b29157eb2cf844916debbbb3c01b3257672e0dece778a9c6fa539043a3038f02, imageIDsha256:d198219f7982b704f4149cdd99cef4e92bef4b3100a77332190bdc44fa87cfd9; HAProxymirror.gcr.io/library/haproxy@sha256:094401c230b9bf01a6a4dc96e455795e261483f3bd2f42f19d0e6f259bd43837, imageIDsha256:650b45a6f585dfa5ebc2f2c5fe577dad0b2c3e36732ac845ee60e21c5ba7847e.Installed Guzzle7/PSR1 with cURL functions disabled on the control client passes typed real cross-namespace execution: caller
php-caller-25b2421966c9, targetphp-target-25b2421966c9, workflowphp-service-25b2421966c9, run01m5m8e47wcnaemb84cfcyq6ks, call01m5m8e4xzjwtc7rwpvx0xgwwk. Exact max-int/Unicode/null values and caller authority survive, with one each StartAccepted, WorkflowStarted, ServiceCallStarted, ServiceCallCompleted and WorkflowCompleted.The same installed SDK passes all three original #197 killed-node controls. Each gets exactly one503 retry, retains its original worker process/claim fence and computed outcome, completes first and later runs once and does not repeat the activity handler:
01m5m8f2j72eb071vp4v3w8se0; php-claim-8d4b71a1c290-later /01m5m8f38exvsn3c6hpspcxs0701m5m8f41v27h98zdrbqm1ajcg; php-claim-f0826efd190d-later /01m5m8f4w3hxbdhjxht2v09d1r01m5m8f5p9e1neq9ax2fstvebc; php-claim-c3d458270cfa-later /01m5m8f7nvwampfbp6vgpyfzqaEach listed run has one WorkflowCompleted; the activity-completion pair also has one ActivityCompleted each. The first attempt mistakenly left the target node alive, so it returned200 and correctly failed the expected-retry assertion while both workflows completed. That was a runner setup failure; the target was stopped and the complete three-scenario matrix rerun successfully. Earlier source/prototype and fixture failures remain at their original records and checkpoints.
HTTP-only/optional OpenSSL follows the existing published platform contract and maintainer compatibility decision. Scoped Sonar S5332/S107/S1448 dispositions and both FIXED complexity findings are recorded above; no global rule/profile change. SDK delivery does not complete Waterline configuration/error/browser/Retry acceptance; keep this issue open for that follow-through. No Server/native/Cloud adoption is inferred.
rmcdaniel commentedon Oct 23, 2026
The Waterline owner has recorded actual normal-package service-consumer API acceptance using Waterline2.13.0/source-dist
30ec59eac270dafd3025afd65175670c6a0f7ab6, SDK3.4.0/source-dist1bf25b5848f2031e72a3da38794b51db1d533546, Laravel12.70.1, PHP8.3.36, Guzzle7.16.0 and PSR-message2.0. No source/path override or embedded Core was used.Laravel HTTP-kernel dispatch exercises installed Waterline routes/middleware and real SDK loopback HTTP. Unmodified RemoteBackend::fromConfig returns HTTP200 with two workflows, the selected namespace and read-only authority against the healthy peer. A stalled peer returns typed HTTP502
remote_transport_failure/request_timeoutafter60.162s under the SDK-owned default. An application-injected normal SDK client with0.2s timeout returns the same typed mapping after0.324s. Each stalled peer received one request, with no mutation/retry.This qualifies the existing service API mapping. It does not establish Waterline timeout configuration: fromConfig still passes endpoint/token/namespace/access only. Browser timeout and run-list Retry also remain unimplemented/unqualified. Keep203 open for those three product gaps. The normal consumer is independent of Waterline's published service-image SDK; no image/dependency-floor adoption is inferred. The owner retired its temporary graph/listeners after retaining its report and reproducible probe.
SDK3.4.0 main/minimum-runtime/portal/installed qualification is complete at receipt6363909756. SDK210's numeric-kind correction remains a separate patch qualification.