Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2103,7 +2103,7 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());

BaseObjectPtr<Session> session =
Session::Create(env, BaseObjectWeakPtr<DatabaseSync>(db), pSession);
Session::Create(env, BaseObjectPtr<DatabaseSync>(db), pSession);
args.GetReturnValue().Set(session->object());
}

Expand Down Expand Up @@ -3770,7 +3770,7 @@ void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) {

Session::Session(Environment* env,
Local<Object> object,
BaseObjectWeakPtr<DatabaseSync> database,
BaseObjectPtr<DatabaseSync> database,
sqlite3_session* session)
: BaseObject(env, object),
session_(session),
Expand All @@ -3783,7 +3783,7 @@ Session::~Session() {
}

BaseObjectPtr<Session> Session::Create(Environment* env,
BaseObjectWeakPtr<DatabaseSync> database,
BaseObjectPtr<DatabaseSync> database,
sqlite3_session* session) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
Expand Down Expand Up @@ -3817,7 +3817,9 @@ Local<FunctionTemplate> Session::GetConstructorTemplate(Environment* env) {
return tmpl;
}

void Session::MemoryInfo(MemoryTracker* tracker) const {}
void Session::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("database", database_);
}

template <Sqlite3ChangesetGenFunc sqliteChangesetFunc>
void Session::Changeset(const FunctionCallbackInfo<Value>& args) {
Expand Down
6 changes: 3 additions & 3 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ class Session : public BaseObject {
public:
Session(Environment* env,
v8::Local<v8::Object> object,
BaseObjectWeakPtr<DatabaseSync> database,
BaseObjectPtr<DatabaseSync> database,
sqlite3_session* session);
~Session() override;
template <Sqlite3ChangesetGenFunc sqliteChangesetFunc>
Expand All @@ -349,7 +349,7 @@ class Session : public BaseObject {
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static BaseObjectPtr<Session> Create(Environment* env,
BaseObjectWeakPtr<DatabaseSync> database,
BaseObjectPtr<DatabaseSync> database,
sqlite3_session* session);

void MemoryInfo(MemoryTracker* tracker) const override;
Expand All @@ -359,7 +359,7 @@ class Session : public BaseObject {
private:
void Delete();
sqlite3_session* session_;
BaseObjectWeakPtr<DatabaseSync> database_; // The Parent Database
BaseObjectPtr<DatabaseSync> database_; // The Parent Database
};

class SQLTagStore : public BaseObject {
Expand Down
32 changes: 31 additions & 1 deletion test/parallel/test-sqlite-session.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Flags: --experimental-sqlite
// Flags: --expose-gc --experimental-sqlite
'use strict';
const { skipIfSQLiteMissing } = require('../common');
skipIfSQLiteMissing();
Expand Down Expand Up @@ -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;
Expand Down
Loading