Skip to content

AB2D-7374 Crash/fault injection and observable recovery for pause-resume prototype - #1861

Draft
Sadibhatla wants to merge 3 commits into
AB2D-7319/pause-resume-prototypefrom
AB2D-7374/prototype-crash-testing
Draft

AB2D-7374 Crash/fault injection and observable recovery for pause-resume prototype#1861
Sadibhatla wants to merge 3 commits into
AB2D-7319/pause-resume-prototypefrom
AB2D-7374/prototype-crash-testing

Conversation

@Sadibhatla

@Sadibhatla Sadibhatla commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🎫 Ticket

https://jira.cms.gov/browse/AB2D-7374

🛠 Changes

Adds crash/fault injection and observable recovery to the pause-resume prototype worker so recovery pathways can be validated in a deployed environment.

CrashInjector- a small, config-driven component that halts the worker at a chosen pipeline point.

Runbook— docs/prototype-crash-testing.md: how to crash a deployed worker (env-var injector or AWS FIS / 'ecs stop-task'), what log markers to watch, and what to verify after recovery.

Default behavior is unchanged: 'crash-probability=0' means every injection point is a no-op unless explicitly armed for testing.

ℹ️ Context

We want confidence that a worker can safely restart a job in a deployed environment without data corruption or faulty output (including crashes mid-file-write). Unit and integration tests already cover many recovery cases in-process; this adds a way to exercise recovery on a real, deployed container and to follow the crash → recovery → clean-completion story in the logs.

No new dependencies, security controls, or data flows are affected. Crash injection is off by default and intended for dev/test only.

🧪 Validation

mvn -pl worker -am test — full build green on JDK 25.
CrashInjectorTest: 3/3 pass (arming off by default, fires only at the configured point, case-insensitive).
PrototypeCrashPointIntegrationTest: 5/5 pass, including the new crashDuringReadingRecovers; full prototype recovery suite 17/17, every scenario recovering to SUCCESSFUL with every beneficiary delivered exactly once.

  • To validate on a deployed worker: set AB2D_DB-style env PAUSE_RESUME_PROTOTYPE_CRASH_AT + PAUSE_RESUME_PROTOTYPE_CRASH_PROBABILITY, submit a job, and confirm the CRASH-INJECTION → HARD RECOVERY → finished with status COMPLETED sequence in the logs (see runbook).

…resume prototype

Introduce a small config-driven CrashInjector that can halt the worker at a
chosen pipeline point (process, read, write, assemble) so recovery can be
exercised in a deployed container, replacing the one hardcoded crash hook in
EobItemProcessor. Wire it into the reader, writer, and assembler as well.

Add greppable log markers for the crash/recovery lifecycle (CRASH-INJECTION
ARMED/firing, and SOFT RESUME / HARD RECOVERY in PrototypeJobRecovery) so a
deployed run can be validated from the logs.

Tests: CrashInjectorTest for the decision logic, and a new reader-crash
recovery case (crashDuringReadingRecovers) filling the one uncovered stage.
Add docs/prototype-crash-testing.md runbook for crashing a deployed worker
(env-var injector or AWS FIS / ecs stop-task) and what to verify.
@Sadibhatla
Sadibhatla requested a review from a team as a code owner September 3, 2026 17:04
@Sadibhatla
Sadibhatla marked this pull request as draft September 3, 2026 17:05
}

long fenceToken = jobLease.bump(jobUuid, owner);
log.info("RECOVERY for job {}: HARD RECOVERY on new token {} - a crashed or stale worker was fenced, "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Technically acquire gets run every time a job gets picked up, including the first time. So this will log a hard recovery on job pickup. Honestly, we should probably just tighten up the fresh/soft/hard acquisition because fresh/soft are bleeding into each other a little bit.

Hard/soft recovery is already logged, these logs (this hard recovery one and the soft resume one above it) could be edits to the existing ones in the processor.


@Override
public CoverageSummary read() {
crashInjector.maybeCrash("read");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would be good to make the injection point names read/write/process/assembly an enum.

this.crashProbability = crashProbability;
if (crashProbability > 0) {
// Loud on purpose: if you see this in a deployed worker's logs, it is going to crash itself.
log.warn("CRASH-INJECTION ARMED at '{}' with probability {} - this worker will halt itself, "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Going off the enum idea, this would be a good place to validate that the property matches a value in the enum

in-process. This runbook is for the other half: proving a **deployed** worker container recovers from a
real crash without corrupting or duplicating output.

Crash injection is off by default and is only meant for `dev`/`test`. **Never arm it in prod.**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The crash injector should check execution.env and refuse to arm if it's prod/sandbox.


@Override
public void write(@NonNull Chunk<? extends SerializedEobs> chunk) throws Exception {
crashInjector.maybeCrash("write");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The crash is synchronous so this always crashes before we start writing. We'd want to crash mid-write.

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