Skip to content
Merged
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
10 changes: 9 additions & 1 deletion src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ class MigratorState {
}
}

/** The alarm time saved when the object was marked migrated, until a rollback restores it. */
get savedAlarm(): number | null {
return this.#savedAlarm;
}

get name(): string | null {
return this.#savedName ?? this.ctx.id.name ?? null;
}
Expand All @@ -238,6 +243,7 @@ class MigratorState {
this.sql!.exec(`INSERT OR REPLACE INTO "${STATE_TABLE}"(key, value) VALUES ('state', 'migrated')`);
if (alarm !== null) {
this.sql!.exec(`INSERT OR REPLACE INTO "${STATE_TABLE}"(key, value) VALUES ('alarm', ?)`, alarm);
this.#savedAlarm = alarm;
}
this.#migrated = true;
}
Expand Down Expand Up @@ -381,7 +387,9 @@ async function exportPage(
return { statements: out, cursor: encodeCursor(cursor) };
}

const alarm = await m.ctx.storage.getAlarm();
// A migrated object's alarm was removed and saved, so a second export
// (for example after the destination failed) still reports it.
const alarm = (await m.ctx.storage.getAlarm()) ?? m.savedAlarm;
const finalCursor = cursor;
m.ctx.storage.transactionSync(() => {
for (const s of tailStatements(sql, tables, m, alarm)) push(s);
Expand Down
14 changes: 14 additions & 0 deletions tests/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ describe("after migration", () => {
const again = await exportInto("moved");
expect(again.db.prepare(`SELECT count(*) AS n FROM notes`).get()).toEqual({ n: 3 });
});

test("an object exported a second time still carries its alarm", async () => {
await seed(worker, "alarm-twice");
const { alarm } = await sourceSnapshot(worker, "alarm-twice");
expect(alarm).not.toBeNull();

const alarmIn = async () => {
const { db } = await exportInto("alarm-twice");
return (db.prepare(`SELECT value FROM __do_meta WHERE key = 'alarm'`).get() as { value: number | null }).value;
};
// For example, the destination failed after the last page and migrate runs again.
expect(await alarmIn()).toBe(alarm);
expect(await alarmIn()).toBe(alarm);
});
});

describe("auth", () => {
Expand Down
8 changes: 8 additions & 0 deletions tests/smoke/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ await check("resolve by id returns the name the Durable Object saved", async ()
assert.equal(resolved.name, name);
});

await check("a migrated Durable Object exported again still reports its alarm", async () => {
const db = new DatabaseSync(":memory:", { enableForeignKeyConstraints: false });
for await (const page of migrator.exportObject({ binding: "ROOMS", id })) db.exec(page.sql);
const alarm = db.prepare(`SELECT value FROM __do_meta WHERE key = 'alarm'`).get() as { value: unknown };
// The fixture's seed schedules its alarm for 2099-01-01.
assert.equal(alarm.value, Date.UTC(2099, 0, 1));
});

await check("a KV write during the export makes it fail", async () => {
const dirty = `${run}-dirty`;
assert.equal((await app(dirty, "seed?big=1")).status, 200);
Expand Down
Loading