diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 5892f4af12b..368eba9f81f 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -624,9 +624,13 @@ class BackupJob : public ThreadPoolWork { } void AfterThreadPoolWork(int status) override { - HandleScope handle_scope(env()->isolate()); + Isolate* isolate = env()->isolate(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(env()->context()); + InternalCallbackScope callback_scope( + env(), Object::New(isolate), {0, 0}, InternalCallbackScope::kNoFlags); Local resolver = - Local::New(env()->isolate(), resolver_); + Local::New(isolate, resolver_); if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE || backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) { diff --git a/test/fixtures/sqlite/backup-last-request.mjs b/test/fixtures/sqlite/backup-last-request.mjs new file mode 100644 index 00000000000..91ad84e77ed --- /dev/null +++ b/test/fixtures/sqlite/backup-last-request.mjs @@ -0,0 +1,34 @@ +import { backup, DatabaseSync } from 'node:sqlite'; + +const source = new DatabaseSync(':memory:'); +source.exec(` + CREATE TABLE data(value); + INSERT INTO data VALUES (zeroblob(1048576)); +`); + +let keepAlive = setInterval(() => {}, 1_000); +let settled = false; + +process.once('beforeExit', () => { + if (!settled) { + process.stderr.write('backup promise did not settle before the event loop became idle\n'); + process.exit(1); + } +}); + +backup(source, process.argv[2], { + rate: 1, + progress() { + if (keepAlive !== undefined) { + clearInterval(keepAlive); + keepAlive = undefined; + } + }, +}).then(() => { + settled = true; + source.close(); +}, (error) => { + settled = true; + process.stderr.write(`${error.stack ?? error}\n`); + process.exitCode = 1; +}); diff --git a/test/parallel/test-sqlite-backup.mjs b/test/parallel/test-sqlite-backup.mjs index d1e09569e1c..f995ae3ca72 100644 --- a/test/parallel/test-sqlite-backup.mjs +++ b/test/parallel/test-sqlite-backup.mjs @@ -1,5 +1,10 @@ // Flags: --expose-gc -import { isWindows, skipIfSQLiteMissing } from '../common/index.mjs'; +import { + isWindows, + skipIfSQLiteMissing, + spawnPromisified, +} from '../common/index.mjs'; +import fixtures from '../common/fixtures.js'; import tmpdir from '../common/tmpdir.js'; import { join } from 'node:path'; import { describe, test } from 'node:test'; @@ -364,3 +369,13 @@ test('source database is kept alive while a backup is in flight', async (t) => { const rows = backupDb.prepare('SELECT COUNT(*) AS n FROM data').get(); t.assert.strictEqual(rows.n, 500); }); + +test('backup promise settles when the backup is the last active request', async (t) => { + const { code, signal, stderr } = await spawnPromisified(process.execPath, [ + fixtures.path('sqlite', 'backup-last-request.mjs'), + nextDb(), + ]); + + t.assert.strictEqual(signal, null); + t.assert.strictEqual(code, 0, stderr); +});