Skip to content

fix(parallel): report a detail-less panic as unavailable, never as another task's - #26

Merged
lisachenko merged 2 commits into
mainfrom
claude/coroutines-repo-session-he07ux
Aug 16, 2026
Merged

fix(parallel): report a detail-less panic as unavailable, never as another task's#26
lisachenko merged 2 commits into
mainfrom
claude/coroutines-repo-session-he07ux

Conversation

@lisachenko

Copy link
Copy Markdown
Owner

Closes #19 — Option 1, as directed in the issue thread: straight to one error object per panic, no interim panic-id detection.

Where Option 1 already stood

The cardinality fix itself landed as a by-product of #15: the substrate keys persisted graphs per instance (lisachenko/php-shared-data-extension#23), and SharedError::capture() rides persistInstance() — each panic is its own three-string instance graph whose address travels in the panicking task's own slot. Lifetime and identity are the slot's: the detail is reachable exactly as long as the slot's payload points at it, which is what Option 1 asked for. (The #16 interplay is ready when slots become reclaimable: dropInstance($address) is the substrate hook for reclaiming a slot's error object with it.)

This PR closes out the issue by pinning all three acceptance criteria and fixing the one place the runtime could still misreport.

What changed

  • The detail-unavailable fallback stops lying. A panic slot whose payload does not attach as a SharedError used to surface as a WorkerCrashedException — but the worker settled the slot, so it is demonstrably alive, and a waiter catching ParallelTaskException never saw the panic at all. It is now a ParallelTaskException that states the detail is unavailable, with no fabricated class and no borrowed trace — the same shape WorkerSupervisor::panicFor() already uses for a detail-less PANIC record on the no-arena path.
  • testEachPanicKeepsItsOwnSharedError now pins the full first criterion. The two concurrent panics differ by exception class, message and task frame (PanicWithMessageTaskRuntimeException, new PanicWithDomainErrorTaskDomainException), and each waiter asserts its exception carries its own class, its own message and its own trace — with the other task's frame asserted absent from it.
  • New testAPanicWhoseDetailCannotBeAttachedSaysSoExplicitly pins the third criterion: a panic slot settled by hand with the address of a shared object that is not a SharedError still surfaces as a task panic, told explicitly that the detail is unavailable — never another object's fields presented as this task's failure.
  • Docs: the stale class-keyed/upsert claim in SharedArena::persist()'s docblock (left behind by feat(parallel): let any number of same-class tasks be in flight at once #25) now describes the per-instance keying; the AGENTS.md panic bullet records the fallback contract.

Acceptance criteria from #19

  • Two tasks panicking concurrently produce two ParallelTaskExceptions, each carrying its own class, message and tracetestEachPanicKeepsItsOwnSharedError.phpt, strengthened as above.
  • No panic path serializes anything — the existing source-scanning test is untouched and still green; nothing new crosses a boundary.
  • If detail is unavailable for a slot, the exception says so explicitly rather than reporting another task'stestAPanicWhoseDetailCannotBeAttachedSaysSoExplicitly.phpt.

Verification

PHP 8.4.19, -d ffi.enable=1 -d opcache.jit=off, local vendor tree (8.4 z-engine line; the 8.5 matrix leg runs in CI, which resolves its own line):

  • Full suite OK (98 tests, 98 assertions) — 97 existing + 1 new, no leftover children.
  • composer phpstan — level max, no errors.
  • composer cs:check — clean.

The new fallback test discriminates by construction: under the previous behaviour the raised WorkerCrashedException escapes its catch (ParallelTaskException) block and fails the run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U5QRWPfmkZME7hDjU6i5EA


Generated by Claude Code

lisachenko and others added 2 commits August 16, 2026 08:33
…other task's

The close-out of #19, Option 1. The cardinality fix itself landed with
the substrate's per-instance keying (#25 here,
lisachenko/php-shared-data-extension#23): each panic is its own
SharedError instance graph whose address rides in the panicking task's
own slot, so two concurrent panics can no longer share one error entry.
What remained was pinning the acceptance criteria and making the
detail-unavailable path tell the truth.

- The fallback for a panic slot whose payload does not attach as a
  SharedError was a WorkerCrashedException — but the worker settled the
  slot, so it is demonstrably alive, and a waiter catching
  ParallelTaskException never saw the panic at all. It is now a
  ParallelTaskException that states the detail is unavailable, with no
  fabricated class and no borrowed trace — the same shape the no-arena
  supervisor path already uses for a detail-less PANIC record.

- testEachPanicKeepsItsOwnSharedError now pins all three fields of the
  first acceptance criterion: the two concurrent panics differ by
  class, message and task frame, and each waiter asserts its exception
  carries its own class, its own message AND its own trace — with the
  other task's frame asserted absent.

- New testAPanicWhoseDetailCannotBeAttachedSaysSoExplicitly pins the
  third criterion by settling a panic slot by hand with the address of
  a shared object that is not a SharedError: the waiter still sees a
  task panic, told explicitly that the detail is unavailable.

The second criterion — no panic path serializes anything — is the
existing source-scanning test, untouched and still green.

Closes #19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5QRWPfmkZME7hDjU6i5EA
…rsist()

The docblock still described the substrate's old registry contract —
class-keyed storage where a second persist of one class is an upsert —
while the method body beneath it has ridden persistInstance() since the
per-instance keying landed. Say what the code does: one entry per graph,
named by its own root address, any number of one class live at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5QRWPfmkZME7hDjU6i5EA
@lisachenko
lisachenko marked this pull request as ready for review August 16, 2026 08:40
@lisachenko
lisachenko merged commit 9d46419 into main Aug 16, 2026
4 checks passed
@lisachenko
lisachenko deleted the claude/coroutines-repo-session-he07ux branch August 16, 2026 08:40
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.

Only one panic survives per store, so ParallelTaskException can report the wrong one

1 participant