Skip to content

test: metadata lost during fork crash recovery - #1798

Open
mdproctor wants to merge 2 commits into
open-workflow-specification:mainfrom
mdproctor:test/fork-metadata-restore-order
Open

mdproctor wants to merge 2 commits into
open-workflow-specification:mainfrom
mdproctor:test/fork-metadata-restore-order

Conversation

@mdproctor

Copy link
Copy Markdown
Contributor

Summary

  • Minimal failing test demonstrating that instance metadata accumulated across fork branches can be silently lost during crash recovery
  • A listener writes a growing list of completed task names to metadata via removeMetadata + addMetadataIfAbsent. Two of three fork branches complete before crash. After recovery, restoreContext processes each branch's persisted metadata snapshot — but the last branch to process wins, and with fork branches the order is non-deterministic (HashMap iteration)
  • Test forces the wrong order by using a single-threaded fork executor and branch names whose hash codes place the older-snapshot branch after the newer-snapshot branch in HashMap bucket order

What the test shows

  1. Pre-crash: taskX completes first (metadata = "taskX"), taskA completes second (metadata = "taskX,taskA")
  2. Both tasks are fully persisted with their respective metadata snapshots
  3. After recovery: restoreContext for branch "a" (newer: "taskX,taskA") runs first, then branch "x" (older: "taskX") overwrites
  4. Listener reads metadata on first new onTaskCompleted — sees "taskX", missing "taskA"

Discussion

Looking for guidance on the intended architecture for listener state recovery across crashes with forks. Options discussed:

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 22, 2026 19:29
@mdproctor
mdproctor requested a review from fjtirado as a code owner October 22, 2026 19:29
3 voters in parallel fork branches, quorum requires all 3.
voterX and voterA vote before crash. After recovery, restoreContext
processes branches in HashMap iteration order. Branch "a" (2 votes,
newer snapshot) processes first, then branch "x" (1 vote, older
snapshot) overwrites — voterA's vote is lost.

voterZ then casts the deciding vote. The listener sees 2/3 votes
instead of 3/3. The quorum never fires.

Uses a single-threaded fork executor and branch names chosen to
force deterministic HashMap iteration that triggers the overwrite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mdproctor
mdproctor force-pushed the test/fork-metadata-restore-order branch from 8c00391 to 2eb0361 Compare October 22, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timing-dependent setup undermines the reproduction, and phase-one failures bypass resource cleanup.

2 open issues
What changed in this PR

Adds a failing regression test demonstrating how fork crash recovery can overwrite newer instance metadata with an older branch snapshot. This supports the listener-state recovery discussion without changing production behavior.

Changes:

  • Records completed task names in metadata across three fork branches.
  • Uses MVStore persistence to simulate shutdown and recovery.
  • Configures single-threaded recovery to expose stale metadata overwrites.
File Description
impl/​test/​src/​test/​java/​org/​openworkflow/​sdk/​impl/​test/​ForkMetadataRestoreTest.java Adds the fork metadata-loss reproduction and recovery assertion.

Review effort: Balanced

Comment on lines +140 to +141
assertThat(twoTasksDone.await(5, TimeUnit.SECONDS)).isTrue();
Thread.sleep(200);
Copilot AI balanced review requested due to automatic review settings October 22, 2026 19:33
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timing-dependent crash setup and cleanup before asynchronous persistence completes undermine the reproduction’s reliability.

3 open issues
1 resolved since last review

Review effort: Balanced

Comment on lines +144 to +145
assertThat(twoVotesCast.await(5, TimeUnit.SECONDS)).isTrue();
Thread.sleep(200);
Comment on lines +204 to +208
restored.start();

Awaitility.await()
.atMost(Duration.ofSeconds(10))
.until(() -> restored.status() == WorkflowStatus.COMPLETED);
Copilot AI balanced review requested due to automatic review settings October 22, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The timing-dependent crash setup does not reliably isolate metadata restoration failures.

3 open issues

Review effort: Balanced

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.

2 participants