Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/cli/src/commands/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ export const start = Command.make("start", {
)
const pidPath = pidFilePath(dataDir)

// First run: `~/.maple` does not exist yet, and everything startup touches
// before the data dir itself — the PID file, the `--background` log, the
// restore/reset transactions, the migration journal, the maintenance lock —
// is a *sibling* of the data dir, i.e. lives in that missing parent. Create
// it up front so first-run ordering is a stated precondition rather than a
// side effect of whichever helper happens to run first.
yield* fs.makeDirectory(dirname(dataDir), { recursive: true })

// Already-running guard.
const existingPid = yield* readPid(fs, pidPath)
if (Option.isSome(existingPid) && isProcessAlive(existingPid.value)) {
Expand Down
17 changes: 17 additions & 0 deletions apps/cli/test/archive-pins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,23 @@ describe("maintenance lock", () => {
})
})

// First run on a clean machine: `~/.maple` does not exist, so the lock — a
// *sibling* of `~/.maple/data` — has no parent to be created in. `maple start`
// reconciles checkpoint recovery under this lock before it creates any
// directory, so an ENOENT here is a hard startup failure, not a warning.
it("acquires the lock when the data dir's parent does not exist yet", async () => {
const root = mkdtempSync(join(tmpdir(), "maple-first-run-lock-test-"))
const dataDir = join(root, ".maple", "data")
try {
ok(!existsSync(dirname(dataDir)), "precondition: the maple home is absent")
const result = await withMaintenanceLock(dataDir, newCheckpointId(), async () => "done")
strictEqual(result, "done")
ok(!existsSync(`${dataDir}.maple-maintenance-lock`), "maintenance lock released")
} finally {
rmSync(root, { recursive: true, force: true })
}
})

it("releases the lock even when the task throws", async () => {
await withDataDir(async (dataDir) => {
await rejects(
Expand Down
15 changes: 15 additions & 0 deletions apps/cli/test/checkpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,21 @@ describe("live restore transaction reconciliation", () => {
})
})

// `maple start` reconciles recovery before it creates any directory, so on a
// clean machine this runs with `~/.maple` itself absent — and every path it
// touches (transaction journal, quarantine, maintenance lock) is a sibling of
// the data dir, living in that missing parent.
it("is a no-op on first run, when the data dir's parent does not exist yet", async () => {
const root = mkdtempSync(join(tmpdir(), "maple-first-run-recovery-test-"))
const dataDir = join(root, ".maple", "data")
try {
ok(!existsSync(dirname(dataDir)), "precondition: the maple home is absent")
await Effect.runPromise(reconcileCheckpointRecovery(dataDir))
} finally {
rmSync(root, { recursive: true, force: true })
}
})

it("preserves an interrupted pre-ready restore and leaves the old live store selected", async () => {
await withDataDir(async (dataDir) => {
const operationId = newCheckpointOperationId()
Expand Down
Loading