diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 87cd52d7283f71..4a503cc20970d8 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2103,7 +2103,7 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo& args) { CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); BaseObjectPtr session = - Session::Create(env, BaseObjectWeakPtr(db), pSession); + Session::Create(env, BaseObjectPtr(db), pSession); args.GetReturnValue().Set(session->object()); } @@ -3770,7 +3770,7 @@ void StatementSyncIterator::Return(const FunctionCallbackInfo& args) { Session::Session(Environment* env, Local object, - BaseObjectWeakPtr database, + BaseObjectPtr database, sqlite3_session* session) : BaseObject(env, object), session_(session), @@ -3783,7 +3783,7 @@ Session::~Session() { } BaseObjectPtr Session::Create(Environment* env, - BaseObjectWeakPtr database, + BaseObjectPtr database, sqlite3_session* session) { Local obj; if (!GetConstructorTemplate(env) @@ -3817,7 +3817,9 @@ Local Session::GetConstructorTemplate(Environment* env) { return tmpl; } -void Session::MemoryInfo(MemoryTracker* tracker) const {} +void Session::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("database", database_); +} template void Session::Changeset(const FunctionCallbackInfo& args) { diff --git a/src/node_sqlite.h b/src/node_sqlite.h index e7281ed266af5d..84c0e26e61ad8f 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -339,7 +339,7 @@ class Session : public BaseObject { public: Session(Environment* env, v8::Local object, - BaseObjectWeakPtr database, + BaseObjectPtr database, sqlite3_session* session); ~Session() override; template @@ -349,7 +349,7 @@ class Session : public BaseObject { static v8::Local GetConstructorTemplate( Environment* env); static BaseObjectPtr Create(Environment* env, - BaseObjectWeakPtr database, + BaseObjectPtr database, sqlite3_session* session); void MemoryInfo(MemoryTracker* tracker) const override; @@ -359,7 +359,7 @@ class Session : public BaseObject { private: void Delete(); sqlite3_session* session_; - BaseObjectWeakPtr database_; // The Parent Database + BaseObjectPtr database_; // The Parent Database }; class SQLTagStore : public BaseObject { diff --git a/test/parallel/test-sqlite-session.js b/test/parallel/test-sqlite-session.js index 189835ce4c3003..a91b9af2e8908f 100644 --- a/test/parallel/test-sqlite-session.js +++ b/test/parallel/test-sqlite-session.js @@ -1,4 +1,4 @@ -// Flags: --experimental-sqlite +// Flags: --expose-gc --experimental-sqlite 'use strict'; const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); @@ -589,6 +589,36 @@ test('session.close() - closing twice', (t) => { }); }); +test('session - keeps its database alive after the db handle is dropped', async (t) => { + const { gcUntil, onGC } = require('../common/gc'); + + // The DatabaseSync handle is created in a nested scope and never referenced + // again, so the returned session is the only thing keeping it reachable. + let dbCollected = false; + const session = (() => { + const database = new DatabaseSync(':memory:'); + database.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, value TEXT)'); + onGC(database, { ongc: () => { dbCollected = true; } }); + const s = database.createSession(); + database.exec("INSERT INTO data VALUES (1, 'hello')"); + return s; + })(); + + // The session must keep the database alive across GC. Previously it held + // only a weak reference, so the database could be collected and using the + // session afterwards dereferenced a dangling pointer and crashed. + await gcUntil('database is collected', () => dbCollected, 5).then( + () => { throw new Error('session did not keep its database alive'); }, + () => {}, // Expected: the database is never collected, so gcUntil rejects. + ); + t.assert.strictEqual(dbCollected, false); + + // The database is still open and usable through the still-alive session. + const changeset = session.changeset(); + t.assert.ok(changeset.byteLength > 0); + session.close(); +}); + test('session supports ERM', (t) => { const database = new DatabaseSync(':memory:'); let afterDisposeSession;