Skip to content

test(harness): bound the deadlock partner read/cleanup and report the thrown error (Ref #68, #37) - #75

Merged
megyptm merged 3 commits into
mainfrom
test/68-deadlock-harness-bounded-partner
Oct 23, 2026
Merged

megyptm merged 3 commits into
mainfrom
test/68-deadlock-harness-bounded-partner

Conversation

@megyptm

@megyptm megyptm commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Test-support-only hardening for #68 (Lead authorization: F68-01 / F68-02). Ref #68, #37. The original intermittent 2 vs 4 failure stays UNVERIFIED; this PR neither explains nor claims to fix it, it makes the harness bounded and any recurrence diagnosable. #76 (the same legacy pattern in ScopedOrderingManagerLazySavepointTest) is deliberately not touched.

  • Bounded read — PartnerProcess (new, tests/Support/MySql/): stdout reads use stream_select() against a monotonic hrtime() deadline (no busy spin) and fail with sanitized, truncated diagnostics (pid, waited time, running state, partner stderr). DeadlockRetryIntegrationTestCase uses it; the 30 s partner deadline, the 15 s awaitBlockedLockingRead() wait, the 3-retry bound and every assertion are unchanged.
  • Bounded, fail-closed cleanup (F75-02) — stop(): close stdin → SIGTERM → SIGKILL after 2 s (delivery falls back to kill if proc_terminate fails) → reap → close pipes. It only calls proc_close() for a child it has seen exit; otherwise it closes the pipes and throws with diagnostics (pid, SIGTERM/SIGKILL delivery status), keeping the resource so a later stop() retries. Teardown stops every partner and rethrows the first failure. Limitation: a child that survives SIGKILL is leaked and PHP may wait for it at shutdown.
  • Deterministic freeze control (F75-01 / F75-01b) — the negative control starts the real DeadlockPartner.php behind sh -c 'kill -STOP $$; exec …', so READY cannot be written before the freeze. PartnerProcess::awaitStopped() is the startup barrier. Because the kernel reports a stop only to the first status query that sees it, all status queries go through status(), which latches the event — including the query start() makes to read the pid — so a stop that happened before start() returned is accepted instead of lost.
  • Diagnostics (F68-02) — the exhaustion test's assertCount(4) keeps its strength and now reports callback attempts, deadlocks provoked and the thrown class / SQLSTATE / driver code / message; assertSame($finalDeadlock, $thrown) and the strict SQLSTATE/1213 checks are untouched.
  • Tests (PartnerProcessIntegrationTest, 5 tests): frozen-before-exec partner fails within the deadline and is killed/reaped; fail-closed stop() with failed signal delivery (seam) then retry; barrier accepts a stop that happens after start() returned; barrier fails within its deadline for a child that never stops; normal partner READY positive control.

No change to src/, retry/transaction logic, dependencies, PHP requirements, CI, schema, standards or security policy. Windows is unsupported by the partner harness (POSIX pipes/signals; the test class skips there).

Identity

  • Base: main@a486cc7224945628e737c7f90978b0faad53c20c, tree 1a7beb6871698010a4727b2b9f52da0d9fd3fedf
  • Commits (sequential, no amend / force-push): 8312752 → 8830047 → HEAD 77e795040b7c3dd8e63afcadb89b53a38c1b4152, tree d3446e86fd60fa62dbe407bd7db84445d39551a0
  • Files: tests/Support/MySql/PartnerProcess.php (new), tests/Support/MySql/DeadlockRetryIntegrationTestCase.php, tests/Integration/Pdo/Transaction/PdoDeadlockRetryRunnerIntegrationTest.php, tests/Integration/Support/PartnerProcessIntegrationTest.php (new)

RED → GREEN (real MySQL 8.4.10 container, scratch copies; injected delays exist only in scratch)

Finding RED GREEN
Original: stuck read baseline readPartnerLine() (declared 30 s) on a SIGSTOPed real partner returned after 45.0 s, only when a watchdog killed the child frozen partner fails at the deadline with diagnostics
Original: cleanup (Linux) old proc_terminate+proc_close on a stopped child returned only after a 20 s watchdog stop() bounded; TERM→KILL 2.0 s
F75-01 previous test + 3 s parent delay between spawn and SIGSTOP → READY returned, test failed wrapper freezes before exec; READY impossible
F75-02 (Linux) previous stop() with signal delivery failing: returned only after 20 s watchdog throws after 4.0 s, child left alive, no proc_close(); retry with delivery restored 2.0 s, child gone
F75-01b HEAD 8830047 + 3 s delay between proc_open() and the first proc_get_status() (macOS, real MySQL): did not stop within 10.0 s; Linux php:8.2-cli, 12 trials, 0.2 s delay: observed stop 0/12 same delay: 5/5 tests OK (macOS); Linux 12/12 observed
Diagnostics — scratch-injected failure on attempt 3 prints Callback attempts: 3; real deadlocks provoked: 2; runner threw RuntimeException (SQLSTATE n/a, driver code n/a): …

Positive / stress controls: no-delay PartnerProcessIntegrationTest 15/15 idle and 10/10 under CPU saturation (48 burners, load avg ≥ 50); exhaustion test 20/20 repeated (both runners, 4 real deadlocks each, same final exception object); no DeadlockPartner process left behind.

Local gate matrix (real MySQL 8.4.10 container, PHP 8.4.4)

{lowest, Composer 2.8.6} and {latest, Composer 2.10.2}: validate --strict, update, platform check, audit --abandoned=fail (clean), dump-autoload --strict-psr, syntax, PHPStan [OK] No errors, php-cs-fixer, unit 383, regression 97, integration 228 (×2), consumer harness, check:residue, check:whitespace — all passed. PHP 8.2/8.3/8.5 are covered by this PR's CI.

Risks / notes

  • The awaitStopped() latch records that the child stopped at some point; it is not cleared by SIGCONT (documented).
  • stop() can take up to ~4 s only for a child that ignores SIGTERM; the normal path is immediate.
  • Quote aggregate evidence per phase only (earlier 106×4 = 424 arithmetic note acknowledged).

🤖 Generated with Claude Code

… thrown error (Ref #68, #37)

Test support only. The partner stdout read used stream_set_timeout() +
blocking fgets(), which proc_open pipes do not honour: a frozen partner hung
the run past its declared 30 s deadline, and the old shutdown (SIGTERM +
proc_close) blocks forever on a stopped child on Linux.

- add PartnerProcess: stream_select() reads against a monotonic deadline,
  sanitized/truncated diagnostics, idempotent stop() that escalates
  SIGTERM -> SIGKILL, reaps the child and closes all pipes
- DeadlockRetryIntegrationTestCase uses it; the 30 s deadline, the 15 s
  awaitBlockedLockingRead() wait and all assertions are unchanged
- exhaustion test: assertCount(4) message now reports callback attempts,
  deadlocks provoked and the thrown class/SQLSTATE/driver code/message
- PartnerProcessIntegrationTest: frozen real partner fails within the
  deadline and is killed and reaped; normal partner positive control

No change to src/, retry logic, dependencies, CI or standards. The original
"2 vs 4" failure remains UNVERIFIED.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

megyptm commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Technical Lead — Fresh Full Re-Review on exact HEAD: MERGE NO-GO (2 concrete test-harness reliability findings)

Independent evidence: Reviewed AGENTS.md, pinned Testing/CI rules, entire cumulative PR #75 diff, parent and new partner source plus call sites; Base main@a486cc7224945628e737c7f90978b0faad53c20c (Tree 1a7beb6871698010a4727b2b9f52da0d9fd3fedf), HEAD 8312752d8b18eb12d672b00c2152e985836cec0b (Tree 274b1c486e8d978866508ec01f1ca6658a1dda7a, parent = Base). Four tests/-only files (+389/−55). No src/, public API, dependencies, CI or standards changes. The original intermittent 2 vs 4 remains UNVERIFIED.

CI is green, NOT the blocker: exact-HEAD Run #39935592448 10/10 SUCCESS. Independently checked Lowest job 120354220752 (clean audit, PHPStan clean, 383 Unit / 97 Regression, 225 Integration twice, real MySQL residue clean), Integration PHP 8.2/8.5 jobs (225 twice per job, both Consumer Harness runs and residue clean) and final CI Gate 120355449921. Executor's targeted RED/frozen-child controls and local PHP 8.4 matrices remain reported, not independently replayed by Lead.

F75-01 — BLOCKER: negative SIGSTOP test has a startup race (new flake)

  • tests/Integration/Support/PartnerProcessIntegrationTest.php:27–31: PartnerProcess::deadlockPartner() launches the actual CLI process before signal($partner, 'STOP'). The child tests/Support/MySql/DeadlockPartner.php:66–68 may emit READY …\n before parent sends SIGSTOP (especially under scheduling pressure).
  • Then readLine(1.0) may legitimately return buffered READY instead of timing out, causing the test to fail even though the new helper is correct. Passing CI (including real MySQL) does not establish the freeze-before-READY ordering.
  • Required minimal fix: introduce a deterministic pre-output startup barrier for this test-only negative control so the child is proven frozen/blocked before any READY can be written; no dependence on scheduler speed or arbitrary sleeps. Preserve the real-partner positive path, exact-deadlock assertions and supported Linux/macOS semantics. Add concrete stress evidence that the negative control remains stable when the parent is delayed. Do not simply widen the <5s assertion.

F75-02 — BLOCKER: advertised bounded stop is not enforced if SIGKILL fails

  • tests/Support/MySql/PartnerProcess.php:178–200: after SIGTERM grace, the return of proc_terminate(SIGKILL) and final waitForExit(2.0) are ignored; proc_close($this->process) runs unconditionally, even if the child is still alive. proc_close waits for process termination and can defeat the claim of bounded teardown on a failed signal / uninterruptible child.
  • Required minimal fix: explicitly handle failed termination or reap within the deadline; do not enter an unbounded proc_close with a known running child. State an accurate, supportable contract and use bounded negative evidence for the path; if OS behavior makes a universal bound impossible, state that limitation and ensure the normal freeze/TERM→KILL case remains bounded and leak-checked. Include fail-closed diagnostics; no swallowed cleanup failures, no modified production behavior. The same initialization-failure path should not issue unguarded proc_close after an unchecked kill.

Both findings arise inside this PR's exact objective (deterministic timeout and bounded test process lifecycle) and can be fixed in its test-only scope. No new runtime work or weakening of assertions. Separate already existing issue is not a substitute for these blockers.

Separate out-of-scope discovery filed: #76 tracks the same legacy pipe/teardown pattern in ScopedOrderingManagerLazySavepointTest.php:56–69,693–704; do not broaden this PR or imply that independent test has been fixed.

Required delivery: one or more normal commits to PR #75 (no amend/force-push) addressing F75-01/02, targeted RED/GREEN/negative controls on real MySQL, repeat Linux and macOS-relevant cases, local full gates and new exact-HEAD GitHub 10/10. Include Base/HEAD/Tree with proofs in an updated PR report. Lead will perform another fresh exact-Tree review before MERGE GO. No merge or auto-merge now.

…stop (F75-01, F75-02; Ref #68)

F75-01: the frozen-partner control spawned the real partner and then sent
SIGSTOP, so READY could be written first. It now starts the real partner
script behind `sh -c 'kill -STOP $$; exec ...'`: the shell stops itself
before exec, so nothing can be written before the freeze. A new
PartnerProcess::awaitStopped() startup barrier waits for the kernel to
report the stop (no sleep, no scheduling assumption).

F75-02: stop() ignored failed SIGTERM/SIGKILL delivery and always called
proc_close(), which blocks without bound on a child that is still running.
Signal delivery now falls back to `kill`, stop() only reaps a child it has
seen exit, and otherwise closes the pipes and throws with diagnostics
without calling proc_close() (the child is leaked and a later stop() can
retry). Teardown stops every partner even if one fails. A test seam on
PartnerProcess::start() drives the failed-termination path.

Test support only; no change to src/, retry logic, dependencies or CI.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@megyptm

megyptm commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Executor update — F75-01 / F75-02 fixed in a new commit (no amend, no force-push)

  • Base: main@a486cc7224945628e737c7f90978b0faad53c20c, tree 1a7beb6871698010a4727b2b9f52da0d9fd3fedf
  • Previous HEAD: 8312752d8b18eb12d672b00c2152e985836cec0b
  • New HEAD: 8830047a92d066685a7836b4853f7ca4d0d0ac88, tree de7cd6a8b7d98168d2d5779ba144656b583d3f8a (parent = previous HEAD). Test-support files only; test(reliability): bound LazySavepoint deadlock partner reads and teardown #76 and ScopedOrderingManagerLazySavepointTest untouched.

F75-01 — deterministic freeze-before-READY

The control now starts the real DeadlockPartner.php behind sh -c 'kill -STOP $$; exec "$0" "$@"'. The shell stops itself before exec'ing PHP, so READY cannot be written, whatever the scheduling. PartnerProcess::awaitStopped() is the startup barrier (bounded poll of proc_get_status()['stopped'], no timing sleep). Positive path (deadlockPartner(), real READY) unchanged.

  • RED (previous HEAD's test, scratch copy, real MySQL, 3 s parent delay injected between spawn and SIGSTOP): Failed asserting that 'A frozen partner cannot report readiness.' … contains "produced no complete output line within 1.0 s" — READY was returned.
  • GREEN (new test, same 3 s delay injected before the barrier): OK ×3. Real-MySQL run of PartnerProcessIntegrationTest: 3 tests / 27 assertions OK; 15/15 under CPU saturation (48 burners, load avg ≥10) and 15/15 idle; no DeadlockPartner process left.

F75-02 — no unbounded proc_close() after failed termination

stop() now (a) tracks delivery of SIGTERM/SIGKILL (default delivery falls back to kill if proc_terminate fails), (b) only calls proc_close() for a child it has seen exit, (c) otherwise closes the pipes and throws with diagnostics (pid, SIGTERM/SIGKILL delivery status, running state, sanitized stderr), keeping the resource so a later stop() retries. The same start() failure path now goes through stop(). Teardown in DeadlockRetryIntegrationTestCase stops every partner and rethrows the first failure. Stated limitation: a child that survives SIGKILL is leaked, and PHP may wait on it at process shutdown.

  • RED (previous HEAD's PartnerProcess, Linux php:8.2-cli, signal delivery forced to fail, SIGSTOPed child): stop() returned only after the 20 s watchdog freed the child — unbounded.
  • GREEN (new class, same scenario): stop() threw after 4.0 s ("still running 2.0 s after SIGKILL (SIGTERM delivery failed, SIGKILL delivery failed); proc_close() is not called…"), child still alive (leaked by design); after restoring delivery, a retry stop() took 2.0 s and the child was gone. Same scenario as an integration test on real MySQL/macOS (testStopFailsClosedWithoutAnUnboundedWaitWhenTheChildCannotBeTerminated): passes, bounded, leak then cleaned up. Normal Linux freeze → TERM→KILL path: stop() 2.0 s, child not alive.

Local gates (real MySQL 8.4.10 container, PHP 8.4.4)

{lowest, Composer 2.8.6} and {latest, Composer 2.10.2}: validate, update, platform check, audit clean, autoload, syntax, PHPStan [OK], php-cs-fixer, unit 383, regression 97, integration ×2 (226 each), consumer harness, residue, whitespace — all passed. Exact-HEAD GitHub CI to follow.

megyptm commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Technical Lead — Independent Fresh Full Re-Review #2: MERGE NO-GO — F75-01 still has a deterministic Linux timing failure

Identities independently re-checked: PR #75 OPEN, mergeable, 2 sequential commits (no amend), Base main@a486cc7224945628e737c7f90978b0faad53c20c / Tree 1a7beb6871698010a4727b2b9f52da0d9fd3fedf; current HEAD 8830047a92d066685a7836b4853f7ca4d0d0ac88 / Tree de7cd6a8b7d98168d2d5779ba144656b583d3f8a, parent 8312752d8b18eb12d672b00c2152e985836cec0b. Entire cumulative patch: 4 files under tests/ (+524/−54), src/, public API, Composer/CI/pinned standards unchanged. Original isolated 2 vs 4 failure remains UNVERIFIED.

CI verified independently, not blocking: exact-HEAD Actions run #39949251793 COMPLETED SUCCESS 10/10, including Lowest PHP 8.2 real MySQL 8.4.10 (audit clean, PHPStan no errors, Unit 383, Regression 97, Integration 226 twice, DB residue clean), Integration 8.2 + 8.5 (each Consumer Harness 2 passes + 226 Integration twice + residue clean), and final CI Gate. Local RED/GREEN and saturation tests remain executor-reported, not independently run by Lead.

Disposition of previous findings

  • F75-02 — ACCEPTED: PartnerProcess::stop() now uses a bounded TERM→KILL grace check, avoids calling proc_close() on a child still observed running, and raises an explicit failure. Test-injected failed-signaling control and retry are reasonable; real-world unkillable child / PHP-shutdown resource limitation is explicitly documented, not deceptively declared solved. No additional F75-02 remediation required at this stage.
  • F75-01 — STILL BLOCKING, precise newly reproduced gap F75-01b: tests/Support/MySql/PartnerProcess.php:93 invokes proc_get_status($process) during start() to read PID. awaitStopped() at lines 196–214 expects to observe $status['stopped'] in a later proc_get_status call. On Linux, the stopped status is an event consumable by the first process-status query. When the child reaches kill -STOP $$ before start() queries status (the exact parent-starvation scenario of this reliability work), start() consumes the stopped event; awaitStopped() can subsequently see running=true, stopped=false indefinitely until its 10s timeout, even though the child is genuinely stopped and incapable of emitting READY.
  • Independent RED empirical reproduction (outside repo): Linux host, PHP 8.4.24, 150 standalone real POSIX proc_open(['sh','-c','kill -STOP $$; exec /bin/true']) trials following the same status-query sequence (proc_get_status in start, then polling in awaitStopped), with a controlled 15ms parent delay before the first status call on every third trial: 50/150 trials lost the stopped event (initialStopped=50; neverSawStop=50); 100/150 observed stopped later. Every child was killed/reaped in the probe. This is an independent mechanism-level proof, not a reproduced full MySQL test failure. A separate probe caching the first observed stopped flag made 150/150 trials observe the stopped state (initialStopped=50; lateStopped=100; failed=0).
  • Why prior GREEN did not cover it: the executor's 3s delay was between PartnerProcess::start() returning and awaitStopped(), after the first proc_get_status() had already executed. That does not control the race in start().

Required narrow fix: Preserve the stopped status from start()'s initial proc_get_status and let awaitStopped() accept a previously consumed STOP event, or use another proven deterministic mechanism; maintain exact PID and process status lifecycle semantics. Provide controlled RED/GREEN with the parent delayed between proc_open() and the first proc_get_status(), and a no-delay positive control, not only a delay before the barrier. Make the test deterministic without timing-based assumptions in permanent production tests; any injected scheduler delay belongs in scratch evidence. Keep cleanup bounded and all existing negative/positive assertions intact.

PR documentation follow-up: The PR body still cites old HEAD 8312752… and old 225-Integration-test count despite the latest correction and 226 tests; synchronize final HEAD/Tree, method behavior, proof summaries and gate counts before final approval. This is straightforward report reconciliation, not permission to expand code scope.

Decision: MERGE NO-GO on HEAD 8830047a92d066685a7836b4853f7ca4d0d0ac88 solely due to the reproducible startup-stop event race. Do not merge, auto-merge, force-push, tag or release. Update PR #75 in a new regular commit for F75-01b, post bounded RED/GREEN and exact HEAD/Tree, rerun all applicable CI gates on that exact head, then request independent Fresh Full Re-Review. No src/ changes and no scope widening into #76.

…barrier cannot lose it (F75-01b; Ref #68)

The kernel reports a stop only to the first status query that sees it.
PartnerProcess::start() queries the status to read the pid, so a child that
stopped itself before that query had its stop event consumed and
awaitStopped() waited for it until its deadline.

All status queries now go through status(), which latches a reported stop;
start() latches the one it makes, and awaitStopped() accepts a stop observed
at any earlier point. Adds tests for a stop that happens after start()
returned and for a child that never stops (barrier fails within its deadline).

Test support only; no change to src/, retry logic, dependencies or CI.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

megyptm commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

Technical Lead — FINAL Fresh Full Acceptance Re-Review #3: MERGE GO for exact HEAD / Tree

Identifiers / scope verified independently on GitHub

  • PR #75 OPEN, not merged, ready-for-review, mergeable. main still exactly a486cc7224945628e737c7f90978b0faad53c20c, Tree 1a7beb6871698010a4727b2b9f52da0d9fd3fedf.
  • HEAD 77e795040b7c3dd8e63afcadb89b53a38c1b4152, Tree d3446e86fd60fa62dbe407bd7db84445d39551a0; three sequential ordinary commits 8312752 → 8830047 → 77e7950, branch ahead 3 / behind 0 of the reviewed Base. This is the exact cumulative 4 test-only files (+580/−54): PartnerProcess.php, DeadlockRetryIntegrationTestCase.php, PdoDeadlockRetryRunnerIntegrationTest.php, PartnerProcessIntegrationTest.php. Zero src/, runtime, public API, dependency, Composer/PHP-minimum, schema, CI, standards or security policy changes. PR body now correctly references the current HEAD/Tree, 228 Integration tests and the three-stage RED/GREEN evidence.
  • No open review threads. The original reported intermittent 2 vs 4 remains UNVERIFIED and is not labeled fixed.

Prior findings — all closed for PR acceptance

  • F75-01: deterministic test negative control stops a POSIX shell before it execs the real deadlock partner PHP script. There is no READY-before-SIGSTOP window. Normal partner positive control and strict real-deadlock assertion remain intact.
  • F75-01b (previous rejection): PartnerProcess::start():96–99 now records $status['stopped'] from its first proc_get_status query as stopObserved; every later core status check via status():313–319 OR-latches it. awaitStopped():203–222 accepts a stop observed either before or after start() returns. This closes the consumed initial stopped event regression, not merely the previous between-start-and-barrier delay. A permanent late-stop and never-stop barrier control were added. Independent mechanism validation by Lead: Linux PHP 8.4.24, 36 real proc_open(['sh','-c','kill -STOP $$; exec /bin/true']) trials with the same initial status capture / latch / deadline polling, alternating controlled pre-first-status-query parent delay; 36/36 observed stop, including 18/18 where the initial query consumed the stop and 18/18 where it was first observed later; all children killed/reaped. This validates the precise process-state race mechanism; it is not claimed to be an independently re-run entire real-MySQL suite. Executor's macOS/Linux prior RED and current GREEN matrices remain executor-attributed.
  • F75-02: verified cumulative cleanup behavior: bounded attempt with TERM→KILL, does not invoke proc_close() when a child is still observed alive, emits a failure with sanitized diagnostics and supports retry. The unsupported pathological unkillable child / process-resource destructor at PHP shutdown limitation is disclosed, not asserted solved. This finding was accepted on previous review, and current final commit does not regress it.
  • F68-02: when assertCount(4) fails it now includes callback attempts, real deadlocks provoked and thrown type / SQLSTATE / driver code / message; original assertCount(4), SQLSTATE/1213 and same-final-exception-object strict assertions remain intact.

Exact-head independent GitHub CI

Run #39966122280 attempt 1 on exact 77e795040b7c3dd8e63afcadb89b53a38c1b4152 is completed/success, 10/10 jobs/checks successful. Logs examined:

  • Lowest 120456323196: Composer 2.10.2, phpunit 11.5.50, symfony/process 5.4.51, audit 0 advisories, PHPStan 0 errors, Unit 383 / 2690 assertions, Regression 97 / 1085, real-MySQL Integration 228 twice (1724 / 1725 assertions), residue clean.
  • Integration PHP 8.2 120456323736 and PHP 8.5 120456323488: each Consumer Verification Harness two successes, Integration 228 twice, residue clean. Quality, Workflow lint, Unit/Regression PHP 8.2/8.3/8.4/8.5 and final CI Gate 120457966960 all passed; final gate log confirms all required jobs successful.
  • Executor also reports PHP 8.4.4 real-MySQL local latest/lowest matrices, targeted RED/GREEN and repeated stress tests, not independently rerun locally by Lead.

Final decision

MERGE GO for this precise HEAD 77e795040b7c3dd8e63afcadb89b53a38c1b4152 / Tree d3446e86fd60fa62dbe407bd7db84445d39551a0, with no code-blocking findings remaining in the complete reviewed change. Not an unconditional standards/release PASS: the legacy F4-04 / #66 policy conflict remains as recorded in #37; the separate ScopedOrderingManagerLazySavepointTest legacy partner lifecycle is #76, not touched or silently fixed.

Owner's explicit authorization to merge remains required. No merge, auto-merge, new repo, tag or release executed by Lead. Once Owner squash-merges the exact reviewed HEAD, verify squash commit and Tree equals d3446e86fd60fa62dbe407bd7db84445d39551a0, post-main-push CI 10/10, reconcile #68 and #37. Decide #76 scope separately before migration baseline to the new maatify/php-persistence package.

@megyptm
megyptm merged commit 9b06ec5 into main Oct 23, 2026
10 checks passed
@megyptm
megyptm deleted the test/68-deadlock-harness-bounded-partner branch October 23, 2026 14:26
megyptm added a commit that referenced this pull request Oct 23, 2026
…ocess (Closes #76, Ref #37) (#77)

* test(harness): run the LazySavepoint deadlock partner through PartnerProcess and clean up fail-safe (Ref #76, #37)

Replaces the hand-rolled proc_open/fgets partner handling (nominal 30 s
stream_set_timeout that does not bound a blocking read, teardown that
terminated and closed without checking) with the bounded PartnerProcess
from #75. tearDown now attempts every cleanup (stop partners, roll back
connections, drop schema) and rethrows the first failure afterwards.
Adds a frozen-LazySavepoint-partner negative to PartnerProcessIntegrationTest.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* test(harness): surface rollback failures in tearDown and keep first-failure cleanup honest (Ref #76, #37, F77-01)

tearDown no longer swallows inTransaction()/rollBack() failures (a false
rollBack() counts too). All failures are collected after every cleanup step
was attempted: one is rethrown as is, several are reported with the first as
primary (a failed PartnerProcess::stop() stays first). Docblock now promises
attempts, not guaranteed release. Adds two regression tests: a real
rollback failure (killed server session) and a failed partner stop with the
database cleanup still performed.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
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