Version
v27.0.0-pre
Platform
Linux x64 (platform-independent)
Subsystem
sqlite
What steps will reproduce the bug?
database.createSession() returns a Session that holds only a weak reference to its DatabaseSync. If the database becomes unreachable and is garbage-collected while the session is still alive, calling any session method dereferences the now-null weak pointer and crashes the process.
// run with: node --expose-gc --experimental-sqlite repro.js
const { DatabaseSync } = require('node:sqlite');
function createSession() {
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, value TEXT)');
return db.createSession(); // session keeps only a WEAK ref to db
}
const session = createSession(); // no strong reference to db survives
global.gc(); // DatabaseSync is collected here
session.changeset(); // SIGSEGV (also: .patchset(), .close(), Symbol.dispose)
How often does it reproduce? Is there a required condition?
Deterministically, once the DatabaseSync has actually been collected.
What is the expected behavior? Why is that the expected behavior?
A thrown Error (code: 'ERR_INVALID_STATE', message "database is not open"), consistent with database.limits and other post-close paths... not a process crash.
What do you see instead?
Segmentation fault (core dumped) # exit 139
GDB shows the fault in node::sqlite::Session::Changeset at instruction cmpq $0x0,0xc0(%rax) with %rax = 0
Additional information
Root cause: Session held only a weak reference to its DatabaseSync, unlike StatementSync (from database.prepare()), which holds a strong reference and keeps its database alive. When the database handle is dropped and collected while the session is still reachable, the weak pointer is nulled and Session::Changeset/Close dereference it via !session->database_->IsOpen(), reading connection_ through a null this. The session should instead keep the database alive, collection of the db handle is not the same as the user calling database.close().
Version
v27.0.0-pre
Platform
Subsystem
sqlite
What steps will reproduce the bug?
database.createSession()returns aSessionthat holds only a weak reference to itsDatabaseSync. If the database becomes unreachable and is garbage-collected while the session is still alive, calling any session method dereferences the now-null weak pointer and crashes the process.How often does it reproduce? Is there a required condition?
Deterministically, once the
DatabaseSynchas actually been collected.What is the expected behavior? Why is that the expected behavior?
A thrown Error (code: 'ERR_INVALID_STATE', message "database is not open"), consistent with database.limits and other post-close paths... not a process crash.
What do you see instead?
Segmentation fault (core dumped) # exit 139
GDB shows the fault in
node::sqlite::Session::Changesetat instructioncmpq $0x0,0xc0(%rax)with%rax = 0Additional information
Root cause: Session held only a weak reference to its DatabaseSync, unlike StatementSync (from database.prepare()), which holds a strong reference and keeps its database alive. When the database handle is dropped and collected while the session is still reachable, the weak pointer is nulled and Session::Changeset/Close dereference it via !session->database_->IsOpen(), reading connection_ through a null this. The session should instead keep the database alive, collection of the db handle is not the same as the user calling database.close().