Repository navigation
Retry the PHP reference's lock-pressure refusals in parity probes - #515
Conversation
The published PHP SQLite reference shares its database file with its maintenance loop and sometimes refuses a request with its documented HTTP 503 backend_lock_pressure reply: retryable, nothing committed, worker mutations fenced by task and attempt. Probe runs failed on these refusals for polls, worker operations and a Rust SDK actor's deregistration. - Parity probes now build their SDK clients through parityClient(), whose default transport is the SDK's own Psr18Transport over Guzzle with an innermost retry middleware. The transport keeps every capability the SDK checks (bounded requests and payload transfer), unlike the earlier wrapper. Observed-transport stacks add the same middleware innermost, so recording middleware sees only the final reply. - lockPressureOperationRetryDelay retries any non-poll request only on status 503, reason backend_lock_pressure and retryable true, within 15 retries, 30 s of waiting and the request's own timeout. Polls keep the stricter rule that no task was handed out. Bodies that cannot be replayed are never retried. Retries are reported once on stderr. - The Rust SDK actor retries its deregistration on the same refusal, under the same bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parity jobs write Rust test output to evidence files, so a failed cargo test left only its exit code in the step log. An ERR trap now prints the panics and the failed test names from those files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Docker workflow isolation policy only accepts repository helpers in statically known positions, as the container log tails already use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guzzle wraps a handler stack in reverse, so HandlerStack::unshift put the retry outermost and each recorder logged the refused attempt as well as the retried reply. The patch deployment contract then rejected a published PHP activity-poll refusal it did not expect. The retry is now pushed after each recorder, which makes it innermost, so recorders see only the final reply, as the middleware intended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This probe retries lock-pressure poll refusals itself and records each retry, while a recording proxy outside the client logs every refused poll. Retrying below the SDK hid those retries from the probe, so the contract found a refused poll without its recorded retry. The probe keeps its plain client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Parity run 39431491496 on What failed: against native, the published Python SDK 2.5.15 client in Local reproduction: a loop of only this case (native, observer, Python client, |
|
Merged main ( |
|
Main moved again with #525 (PHP workflow definition evolution: app code, PHP feature tests and docs, nothing under the parity paths). Merged as |
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Part of #325. Replaces the closed #511, whose wrapper transport hid SDK capabilities.
Shared runs keep failing on the published PHP SQLite reference's documented HTTP 503
backend_lock_pressurerefusal. It shares its database file with its maintenance loop, and SQLite refuses a writer while another connection writes. Seen so far:task: null);operation: destroy,outcome: deferred,retryable: true).The refusal means nothing was committed, and worker mutations stay fenced by task and attempt, so repeating the request cannot record a second outcome. The probes treated it as fatal.
Change
probe.php, the probe files it loads andcooperative-restart.phpbuild their clients throughparityClient()(lock-pressure-middleware.php). A caller's transport is kept. Otherwise the default is the SDK's ownPsr18Transportover Guzzle with an innermost retry middleware, so the transport keeps every capability the SDK checks (bounded requests, payload transfer). The observed-transport stacks inprobe.php,cooperative-restart.phpand the namespace and patch-deployment probes add the same middleware innermost, so recording middleware sees only the final reply.lockPressureOperationRetryDelayretries a non-poll request only on status 503, reasonbackend_lock_pressureandretryable: true. Polls keeplockPressurePollRetryDelay's stricter rule thattaskis null. Both stop at 15 retries, 30 s of waiting and the request's own timeout. A request body that cannot be replayed is never retried. Retries are reported once on stderr at exit, so observations stay comparable across PHP, native and embedded targets.corrected-markers/rust/src/lock_pressure.rs).probe-failure.phpkeeps its plain client and 1 s timeouts.activity-deadline-probe.phpkeeps its plain client too. It retries its own lock-pressure polls and records each retry, and its contract checks those against a recording proxy outside the client.Two fixes since the first push, both found by this PR's own runs:
HandlerStack::unshifthad put the retry outermost, and recorders logged each refused attempt. The patch deployment contract then rejected a refused activity poll. The retry is now pushed after each recorder, which makes it innermost.Diagnosable Rust test failures
The parity jobs write
cargo testoutput to evidence files, so a failed Rust test left only its exit code in the step log (theexecution-and-codecfailure on this PR's previous head showed nothing more). An ERR trap in the three test steps now runsrust-test-failures.sh, which prints the panics and failed test names from those files.Checks
ServerParityLockPressureRetryTestadds operation cases: deferred worker refusals and control-plane refusals are retried, while missing or falseretryable, another reason, another status and the budgets are not, and a poll through the operation entry point still needstask: null.parityClient(), with the SDK still recognizing the transport's bounded-request capability.execution-and-codecstep's fixtures and SDK directions (the step that failed on Preserve explicit worker command declarations in native registration #508) passed with these changes. No refusal happened in that run, so it shows the transport change is transparent rather than exercising a retry.rust-test-failures.shrun against a local failing suite output printed both panics and the closing failure list, and printed nothing for passing output.🤖 Generated with Claude Code