Skip to content

Repair the decode-drop-recovery stress test so the job can pass (backport #745 to v5.2) - #759

Merged
kriszyp merged 1 commit into
v5.2from
kris/backport-745-v52
Aug 25, 2026
Merged

kriszyp merged 1 commit into
v5.2from
kris/backport-745-v52

Conversation

@kriszyp

@kriszyp kriszyp commented Aug 25, 2026

Copy link
Copy Markdown
Member

Backport of Repair the decode-drop-recovery stress test so the job can pass (#745, merged to main as 4bd15ec) onto v5.2, so the decode-drop-recovery stress job can go green on the release line.

Clean cherry-pick — git diff against the original merge commit is byte-identical, so the rationale and the four open decisions raised for the reviewer in #745 carry over unchanged. Test-only: one .mjs file, no product code.

v5.2 is affected

Both defects are present on v5.2 (both date to #691, which is on this branch):

  • integrationTests/cluster/decodeDropRecovery.test.mjs:151 authorizes add_node with ctx.nodeA.HDB_ADMIN_USERNAME / HDB_ADMIN_PASSWORD — property names the harness does not expose, so the join goes out unauthenticated and A closes it 1008 Unauthorized.
  • after() passes bare node objects to teardownHarper, which early-returns on a falsy ctx.harper, so both Harper children leak past teardown.

Verified against the installed harness on this branch (@harperfast/integration-testing 0.7.1): the node object exposes admin: { username, password } (dist/harperLifecycle.js:452) and teardownHarper(ctx) early-returns on !ctx.harper (dist/harperLifecycle.js:608). And decode-drop-recovery is in this branch's stress matrix (.github/workflows/stress-tests.yaml:135), so the job runs here and is red for exactly these two reasons.

Verification

Ran the stress test itself on this branch, at all three states, reproducing #745's table on v5.2:

HARPER_RUN_STRESS_TESTS=1 node integrationTests/run.mjs integrationTests/cluster/decodeDropRecovery.test.mjs
state test assertion run exit
neither fix (origin/v5.2 file) fails at add_node, Connection closed Unauthorized 1008 and connection was required to sign certificate, HTTP 500 1
auth fix only (teardown hunk reverted) passes 1 — leaked-child backstop fired
both fixes passes 0

So both hunks are independently load-bearing on this branch, not just on main.

The pass is non-vacuous. With logs pinned to a known directory, node B logged Error decoding replication message exactly 3 times (= POISON_COUNT) and node A 0 times, with 0 Error handling incoming replication message close lines on either node — assertion (3) ruled on real decode drops, not on an injection that never fired. This is structural as well as observed: assertion (3) is ok(/Error decoding replication message/.test(logB)), so the test cannot pass without a real drop.

Not run: the full integration gate. This is a single stress-gated file that no other suite imports, so the blast radius is that file.

Complexity: easy

…rdown (#745)

The `decode-drop-recovery` stress job could never pass. Two defects in the test,
both dating to when it was added in #691:

`add_node` was authorized with `ctx.nodeA.HDB_ADMIN_USERNAME` /
`HDB_ADMIN_PASSWORD`. Those properties do not exist on the integration-testing
harness's node object, which exposes `admin: { username, password }` — the name
appears in exactly one file in `integrationTests/`, this one. Both values were
`undefined`, so the operation went out as `authorization: {}` and `add_node_back`
followed with `authorization: null`. Node B then presented its still-self-signed
certificate to A with no credentials behind it, and A — installed with the prod
config profile, so certificate validation is enforced — closed the connection
1008 Unauthorized. The test died in ~230ms at its first operation, never reaching
any decode-drop behavior.

`after()` called `teardownHarper(ctx.nodeA)`, passing the node where the harness
expects a context. `teardownHarper` opens with `if (!ctx.harper) return`, so both
calls were silent no-ops and both Harper children survived the suite. The
leaked-child backstop in `integrationTests/run.mjs` then forced a non-zero exit,
which would have kept the job red even once the join was fixed. Teardown now also
runs both nodes concurrently, matching the sibling suites, so a hang on A cannot
strand B.

Neither surfaced earlier because the job's three runs before 2026-08-20 were all
cancelled; the 2026-08-20 run was its first completed run.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 4bd15ec)
@kriszyp
kriszyp requested a review from a team as a code owner August 25, 2026 03:47

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the test cleanup in decodeDropRecovery.test.mjs to tear down Harper nodes concurrently using Promise.all and simplifies the authorization configuration. The review feedback recommends logging any teardown errors with identifying information instead of silently swallowing them in the .catch block.

Comment on lines +138 to +140
await Promise.all(
[ctx.nodeA, ctx.nodeB].filter(Boolean).map((node) => teardownHarper({ harper: node }).catch(() => {}))
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In test cleanup hooks, any failures during process termination or cleanup should be logged with identifying information (such as hostnames) for easier diagnosis, rather than being silently swallowed with an empty .catch(() => {}) block.

Suggested change
await Promise.all(
[ctx.nodeA, ctx.nodeB].filter(Boolean).map((node) => teardownHarper({ harper: node }).catch(() => {}))
);
await Promise.all(
[ctx.nodeA, ctx.nodeB].filter(Boolean).map((node) =>
teardownHarper({ harper: node }).catch((err) => {
console.error('Failed to teardown Harper node ' + (node.hostname || 'unknown') + ':', err);
})
)
);
References
  1. In test cleanup hooks, wrap individual process termination or cleanup steps in separate try-catch blocks to ensure they are attempted independently, and log any failures with identifying information (such as hostnames) for easier diagnosis.

@claude

claude Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

@kriszyp
kriszyp merged commit f303fd8 into v5.2 Aug 25, 2026
36 checks passed
@kriszyp
kriszyp deleted the kris/backport-745-v52 branch August 25, 2026 04:07
@cb1kenobi

Copy link
Copy Markdown
Member

Reviewed e086be21 — no issues found. This PR looks good, nice job!

Verified independently on v5.2, not just re-read against #745:

  • Cherry-pick is exact. git range-diff bc55bf2..e086be2 4bd15eca^..4bd15eca differs only by the (cherry picked from ...) trailer; the patch bytes are identical, and the pre-fix file is identical on v5.2 and main.
  • Both defects confirmed present on v5.2. Against the installed harness (@harperfast/integration-testing 0.7.1): HDB_ADMIN_USERNAME appears only as an install CLI flag (dist/harperLifecycle.js:421), never as a node property, so the old call sent {username: undefined, password: undefined}; teardownHarper reads only ctx.harper (plus killHarper's ctx.harper?.process), so { harper: node } is a complete context and matches the convention 8 sibling cluster suites already use.
  • Reproduced your table on v5.2. Base file → dies at add_node in 131ms with Connection closed Unauthorized 1008 and connection was required to sign certificate (500 vs 200), and the leaked-child backstop fires. Auth fix only → assertion passes, run still exits 1 on the backstop. Both fixes → exit 0. Both hunks are independently load-bearing here.
  • No bar-lowering. The diff touches only after() and the authorization field. TOTAL_ROWS (200), POISON_COUNT (3), CONVERGE_TIMEOUT_MS (90s), the 300s suite timeout, and assertions (1)–(4) are all byte-identical to the base. No new skip or gate: skip: !STRESS is pre-existing and the stress job sets HARPER_RUN_STRESS_TESTS: '1'.
  • 10/10 green at head, deterministic: exit 0, pass 1 / fail 0, no leaked-child message, and exactly 3 Error decoding replication message lines on B with 0 on A and 0 close lines in every run — the same non-vacuity signature you reported.
  • Detection proven by mutation, both rebuilt to dist/ with the marker confirmed as exactly one isolated match:

So the repair restores detection rather than lowering the bar: the base test detected nothing at all (it died at its first operation, never reaching decode-drop behavior), and the repaired test catches both a recovery regression and a dead injection.

The four decisions you carried over from #745 are still open and still pre-existing (log-string coupling in assertions (3)/(4), convergence by record_count rather than row identity, the swallowing .catch(() => {}), and patching call sites instead of hardening teardownHarper) — none of them are introduced or worsened here, so nothing to hold this backport on.

—
Generated by Barber AI

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