Skip to content

Add a restart regression test: dropped tables stay dropped and same-name recreates start empty - #2901

Draft
kriszyp wants to merge 5 commits into
mainfrom
test/drop-table-restart-recreate
Draft

kriszyp wants to merge 5 commits into
mainfrom
test/drop-table-restart-recreate

Conversation

@kriszyp

@kriszyp kriszyp commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Adds an integration test that drives a real Harper process through drop_table, then a graceful restart or a SIGKILL, then a same-name recreate, and checks that no pre-drop row ever comes back. It also records in resources/DESIGN.md that the drop tombstone is node-local. Refs #1212. It does not close that issue: a peer that is offline for the drop still brings the table back (measured below).

For the human reviewer

  1. Requirement: only the single-node half lands here. The task owner asked for both Dropped table can be resurrected after cluster restart — drop is not a durable, replayable txn-log event #1212 scenarios. The cluster scenarios need replication, which is harper-pro-only, and this run was authorized to push only to HarperFast/harper. So they were run in a scratch harper-pro build and are reported under Verification, not committed. Scenario 2 (a peer offline during the drop) fails on current main. Open questions: should Dropped table can be resurrected after cluster restart — drop is not a durable, replayable txn-log event #1212 be narrowed to that case, and should the cluster test go into harper-pro, with scenario 2 as todo until there is a fix? Saying no to this PR costs little, but core CI would then have no process-level check of a local drop.
  2. The kill comes after drop_table returns, not during it. dropTable removes its dropping tombstone and awaits the commit before returning, so this test never reaches completeInterruptedDrop. An interrupted drop is covered only by the unit simulation in unitTests/resources/dropTableGhost.test.js. A fault-injection arm could be added later.
  3. The post-restart reads wait on a replay barrier. Boot does not await replayLogs (resources/databases.ts:1201). Before each restart the test writes a barrier row, and after boot it waits until that row is visible. This relies on replay applying one database's log in order. If replay were ever parallelized, the barrier would stop working without any error. The alternative is for boot to expose a replay-complete signal.
  4. Core resources/DESIGN.md now describes harper-pro behavior: a peer offline for the drop re-creates the table through the DB_SCHEMA handshake. The note is anchored to core's own replicateOperation call and labeled as observed on a cluster. It would be easy to move to harper-pro's design notes instead.
  5. CI cost. The suite does 13 serial boots, about 19 s locally. Each arm has a 300 s timeout and the suite has none, so slow boots cannot eat into a shared budget.
  6. Refs, not Closes. A closing keyword would close Dropped table can be resurrected after cluster restart — drop is not a durable, replayable txn-log event #1212 on merge, while the cluster failure it reports still reproduces.
  7. Milestone v5.3, not the issue's v5.2. This is a regression test, not the Dropped table can be resurrected after cluster restart — drop is not a durable, replayable txn-log event #1212 fix, and cherry-picking it onto v5.2 conflicts in both DESIGN.md files, whose notes are laid out differently there. Re-milestoning to v5.2 would need a /patch-pr for those two hunks. The test passed on both cores tried with rocksdb-js 2.1.0 or later: current main and core before fix(schema): atomic table drops, interrupted-drop completion at startup, and create-lock release (ghost tables) #1246.

Changes

Verification

End-to-end route: the new integration test. npm run test:integration -- integrationTests/database/drop-table-restart-recreate.test.ts

Build Result
This branch, core 7c06b8364 (current main), rocksdb-js 2.10.0 6/6 pass, 3 consecutive runs under the default runner; the SIGKILL arms confirm signalCode === 'SIGKILL'
Core before #1246 (17a3f60d2) with rocksdb-js 2.0.0, which predates HarperFast/rocksdb-js#647 4 restart-first arms pass; both in-process arms fail: Invalid column family specified in write batch (the ghost-table signature)
Core before #1246 with rocksdb-js 2.1.0 6/6 pass, so this test pins the binding fix; #1246's core-side hardening stays covered by its unit tests

Cluster run, not in this PR: harper-pro main 505ff332 with core 7c06b8364, 2 nodes, bidirectional, rows written on both.

Scenario Result
1: dropped on both nodes, graceful restart of both, recreate, restart again pass: stays dropped, recreate empty on both
1b: same, both nodes SIGKILLed right after the drop pass
2: B killed, drop_table on A (A reports B failed: ECONNREFUSED), B restarted fail: A has the table again with 0 rows, B still has 40/40 pre-drop rows
2, continued: both nodes restarted fail: A 0 rows, B 40; the nodes stay diverged
2c: B down, drop then recreate on A, B rejoins, one new write on B A holds only the new write; B still has 40 pre-drop rows

Not run locally: the Bun runtime (not installed here), Windows, and the full test:integration:all (CI runs them). prettier, oxlint and tsc -p integrationTests are clean for the changed files.

Planning review: Framing-Verdict: chosen-approach-sound.

Signed: Claude Opus 5.5

Complexity: medium

🤖 Generated with Claude Code

Origin — the dispatch brief this PR was written from

LIVE CONVERSATION about #1212.

You are answering a person, in a thread, one turn at a time. Every turn:

  1. Read the whole thread in this dispatch file's # Log — it is the conversation so far, and
    each of your previous turns is in it. Read the PR/issue and the code as needed.
  2. Answer the LAST message. Append your answer to # Log as your turn. Prose, not a report:
    they are talking to you, and a status template is not an answer.
  3. Set status: needs-input and stop. The thread stays open; their next message resumes it.

Each turn arrives as ASK (answer it, change nothing) or PERFORM (do it, then say what you did) —
the person chose which when they sent it, and the run's own prompt tells you which one this is.
Never infer it from the wording: an unrequested commit in the middle of a discussion and a polite
description of work that was supposed to happen are the two failures this exists to prevent.

Never mark a PR ready and never merge from this conversation.

Dispatch: task chat-issue-harper-1212-kriszyp · queued by unknown · ran by codex/gpt-6.1-sol/xhigh · worker kzyp-xps-1

Review-Coverage: authored=claude; ran=codex,cursor-composer; adjudicated=domain; blocked=gemini(no-output); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=5; full=1 @ 2af50b7

Human-Review-Need: 3 (decisions: post-ack-kill-vs-mid-drop-fault, replay-barrier-in-test, single-node-now-peer-later, thirteen-serial-boots) @ 2af50b7

kriszyp and others added 3 commits September 29, 2026 05:34
…me-name recreate starts empty

Runs a real Harper process through drop_table followed by a graceful restart or a SIGKILL, for
three ways the table comes back: recreated in-process before the restart (the ghost-table
incident flow), recreated with create_table after it, and recreated at boot from a component's
schema.graphql. Each arm checks the primary store and a secondary index, writes a fresh row,
and restarts again.

The in-process arms fail on pre-#1246 core with rocksdb-js 2.0.0 ("Invalid column family
specified in write batch") and pass from rocksdb-js 2.1.0 on. DESIGN.md records that the drop
tombstone is node-local: a peer offline for the drop re-creates the table on reconnect (#1212).

Refs #1212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stic

Send SIGKILL directly instead of racing the SIGTERM handler's immediate exit, wait for a barrier
row written just before each restart so reads never run ahead of the unawaited boot replay, time
each arm separately, and scope the header and design note to acknowledged drops.

Refs #1212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ushed

Refs #1212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kriszyp kriszyp added this to the v5.2 milestone Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Release cherry-pick v5.2: cancelled

Cherry-pick branch cherry-pick/v5.2/pr-2901 was deleted — this PR no longer targets v5.2 (milestone is now v5.3).

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request adds integration tests and updates design documentation to ensure that dropped tables remain dropped across restarts and that same-name recreations start empty. The feedback recommends guarding the after hook in the new test suite to prevent secondary TypeError exceptions from masking setup failures if the before hook fails.

Comment on lines +79 to +81
after(async () => {
await teardownHarper(ctx);
});

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.

medium

In node:test, the after hook is executed even if the before hook fails. If setupHarperWithFixture fails in the before hook, ctx.harper will be undefined, and calling teardownHarper(ctx) will throw a secondary TypeError, masking the original failure. Guard the teardown call with a safety check on ctx.harper.

	after(async () => {
		if (ctx.harper) {
			await teardownHarper(ctx);
		}
	});
References
  1. In test teardown hooks (such as after or afterEach), ensure cleanup operations for temporary resources are guarded so they only execute if the resources were successfully initialized. Wrap independent teardown steps in try/finally blocks to guarantee all cleanup executes and to prevent secondary errors from masking the original setup failure.
  2. In node:test, after hooks are executed even if a before hook throws an error. Do not duplicate cleanup logic or wrap assertions in try...catch blocks inside before hooks solely to ensure cleanup.

…e budget

Refs #1212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kriszyp kriszyp modified the milestones: v5.2, v5.3 Sep 29, 2026
…e in design notes

Refs #1212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

Dropped table can be resurrected after cluster restart — drop is not a durable, replayable txn-log event

1 participant