Skip to content

Commit 419cfd4

Browse files
committed
sqlite: change approach to fix segfault SQLTagStore
1 parent 7199a61 commit 419cfd4

File tree

3 files changed

+51
-7
lines changed

3 files changed

+51
-7
lines changed

src/node_sqlite.cc

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -831,8 +831,8 @@ void DatabaseSync::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
831831
capacity = args[0].As<Number>()->Value();
832832
}
833833

834-
BaseObjectPtr<SQLTagStore> session =
835-
SQLTagStore::Create(env, BaseObjectWeakPtr<DatabaseSync>(db), capacity);
834+
BaseObjectPtr<SQLTagStore> session = SQLTagStore::Create(
835+
env, BaseObjectWeakPtr<DatabaseSync>(db), args.This(), capacity);
836836
if (!session) {
837837
// Handle error if creation failed
838838
THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore");
@@ -2710,14 +2710,18 @@ Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) {
27102710
}
27112711

27122712
BaseObjectPtr<SQLTagStore> SQLTagStore::Create(
2713-
Environment* env, BaseObjectWeakPtr<DatabaseSync> database, int capacity) {
2713+
Environment* env,
2714+
BaseObjectWeakPtr<DatabaseSync> database,
2715+
Local<Object> database_object,
2716+
int capacity) {
27142717
Local<Object> obj;
27152718
if (!GetConstructorTemplate(env)
27162719
->InstanceTemplate()
27172720
->NewInstance(env->context())
27182721
.ToLocal(&obj)) {
27192722
return nullptr;
27202723
}
2724+
obj->SetInternalField(kDatabaseObject, database_object);
27212725
return MakeBaseObject<SQLTagStore>(env, obj, std::move(database), capacity);
27222726
}
27232727

@@ -2728,9 +2732,8 @@ void SQLTagStore::CapacityGetter(const FunctionCallbackInfo<Value>& args) {
27282732
}
27292733

27302734
void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo<Value>& args) {
2731-
SQLTagStore* store;
2732-
ASSIGN_OR_RETURN_UNWRAP(&store, args.This());
2733-
args.GetReturnValue().Set(store->database_->object());
2735+
args.GetReturnValue().Set(
2736+
args.This()->GetInternalField(kDatabaseObject).As<Value>());
27342737
}
27352738

27362739
void SQLTagStore::SizeGetter(const FunctionCallbackInfo<Value>& args) {

src/node_sqlite.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,13 +304,21 @@ class Session : public BaseObject {
304304

305305
class SQLTagStore : public BaseObject {
306306
public:
307+
enum InternalFields {
308+
kDatabaseObject = BaseObject::kInternalFieldCount,
309+
kInternalFieldCount
310+
};
311+
307312
SQLTagStore(Environment* env,
308313
v8::Local<v8::Object> object,
309314
BaseObjectWeakPtr<DatabaseSync> database,
310315
int capacity);
311316
~SQLTagStore() override;
312317
static BaseObjectPtr<SQLTagStore> Create(
313-
Environment* env, BaseObjectWeakPtr<DatabaseSync> database, int capacity);
318+
Environment* env,
319+
BaseObjectWeakPtr<DatabaseSync> database,
320+
v8::Local<v8::Object> database_object,
321+
int capacity);
314322
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
315323
Environment* env);
316324
static void All(const v8::FunctionCallbackInfo<v8::Value>& args);

test/parallel/test-sqlite-template-tag.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
'use strict';
2+
// Flags: --expose-gc
3+
24
const { skipIfSQLiteMissing } = require('../common');
35
skipIfSQLiteMissing();
46

@@ -124,3 +126,34 @@ test('sql error messages are descriptive', () => {
124126
message: /no such table/i,
125127
});
126128
});
129+
130+
test('a tag store keeps the database alive by itself', () => {
131+
const sql = new DatabaseSync(':memory:').createTagStore();
132+
133+
sql.db.exec('CREATE TABLE test (data INTEGER)');
134+
135+
global.gc();
136+
137+
// eslint-disable-next-line no-unused-expressions
138+
sql.run`INSERT INTO test (data) VALUES (1)`;
139+
});
140+
141+
test('tag store prevents circular reference leaks', async () => {
142+
const { gcUntil } = require('../common/gc');
143+
144+
const before = process.memoryUsage().heapUsed;
145+
146+
// Create many SQLTagStore + DatabaseSync pairs with circular references
147+
for (let i = 0; i < 1000; i++) {
148+
const sql = new DatabaseSync(':memory:').createTagStore();
149+
sql.db.exec('CREATE TABLE test (data INTEGER)');
150+
sql.db.setAuthorizer(() => void sql.db);
151+
}
152+
153+
// GC until memory stabilizes or give up after 20 attempts
154+
await gcUntil('tag store leak check', () => {
155+
const after = process.memoryUsage().heapUsed;
156+
// Memory should not grow significantly (allow 50% margin for noise)
157+
return after < before * 1.5;
158+
}, 20);
159+
});

0 commit comments

Comments
 (0)