Skip to content

node:sqlite: process crash (SIGSEGV) when a Session outlives its garbage-collected DatabaseSync #63796

@3zrv

Description

@3zrv

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().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions