diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index b4fbfa88746..dd953f2814f 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -680,6 +680,11 @@ class BackupJob : public ThreadPoolWork { return; } + if (backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED) { + this->ScheduleWork(); + return; + } + int total_pages = sqlite3_backup_pagecount(backup_.get()); int remaining_pages = sqlite3_backup_remaining(backup_.get()); if (remaining_pages != 0) { diff --git a/test/parallel/test-sqlite-backup.mjs b/test/parallel/test-sqlite-backup.mjs index cae097aa92e..78a99ebd552 100644 --- a/test/parallel/test-sqlite-backup.mjs +++ b/test/parallel/test-sqlite-backup.mjs @@ -1,6 +1,7 @@ // Flags: --expose-gc import { isWindows, + platformTimeout, skipIfSQLiteMissing, spawnPromisified, } from '../common/index.mjs'; @@ -335,6 +336,32 @@ test('backup fails when path cannot be opened', async (t) => { }); }); +test('backup waits for a write transaction on the source to end', async (t) => { + const database = makeSourceDb(); + const destDb = nextDb(); + + t.after(() => { database.close(); }); + + database.exec('BEGIN IMMEDIATE'); + database.exec("INSERT INTO data (key, value) VALUES (3, 'value-3')"); + const p = backup(database, destDb); + setTimeout(() => database.exec('COMMIT'), platformTimeout(100)); + + await p; + + const backupDb = new Database(destDb); + + t.after(() => { backupDb.close(); }); + + const rows = backupDb.prepare('SELECT * FROM data').all(); + + t.assert.deepStrictEqual(rows, [ + { __proto__: null, key: 1, value: 'value-1' }, + { __proto__: null, key: 2, value: 'value-2' }, + { __proto__: null, key: 3, value: 'value-3' }, + ]); +}); + test('backup has correct name and length', (t) => { t.assert.strictEqual(backup.name, 'backup'); t.assert.strictEqual(backup.length, 2);