You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
node:sqlite: process crash (SIGSEGV) when a Session outlives its garbage-collected DatabaseSync #63796
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().
Activity
sqlite: fix crash when a session outlives its database
sqlite: fix crash when a session outlives its database
sqlite: fix crash when a session outlives its database
sqlite: fix crash when a session outlives its database