Skip to content
This repository was archived by the owner on Dec 1, 2024. It is now read-only.
/ leveldown Public archive

Commit 9a3f59a

Browse files
committed
Prevent GC of db during clear() and other operations
1 parent aedf49e commit 9a3f59a

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ jobs:
3939
uses: GabrielBB/xvfb-action@v1
4040
with:
4141
run: npm run test-electron
42+
- name: Test GC
43+
run: npm run test-gc

binding.cc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,14 @@ struct Database {
361361
filterPolicy_(leveldb::NewBloomFilterPolicy(10)),
362362
currentIteratorId_(0),
363363
pendingCloseWorker_(NULL),
364+
ref_(NULL),
364365
priorityWork_(0) {}
365366

366367
~Database () {
368+
if (ref_ != NULL) {
369+
napi_delete_reference(env_, ref_);
370+
}
371+
367372
if (db_ != NULL) {
368373
delete db_;
369374
db_ = NULL;
@@ -444,11 +449,13 @@ struct Database {
444449
}
445450

446451
void IncrementPriorityWork () {
447-
++priorityWork_;
452+
napi_reference_ref(env_, ref_, &priorityWork_);
448453
}
449454

450455
void DecrementPriorityWork () {
451-
if (--priorityWork_ == 0 && pendingCloseWorker_ != NULL) {
456+
napi_reference_unref(env_, ref_, &priorityWork_);
457+
458+
if (priorityWork_ == 0 && pendingCloseWorker_ != NULL) {
452459
pendingCloseWorker_->Queue();
453460
pendingCloseWorker_ = NULL;
454461
}
@@ -465,6 +472,7 @@ struct Database {
465472
uint32_t currentIteratorId_;
466473
BaseWorker *pendingCloseWorker_;
467474
std::map< uint32_t, Iterator * > iterators_;
475+
napi_ref ref_;
468476

469477
private:
470478
uint32_t priorityWork_;
@@ -828,11 +836,16 @@ NAPI_METHOD(db_init) {
828836
NAPI_STATUS_THROWS(napi_create_external(env, database,
829837
FinalizeDatabase,
830838
NULL, &result));
839+
840+
// Reference counter to prevent GC of database while priority workers are active
841+
NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->ref_));
842+
831843
return result;
832844
}
833845

834846
/**
835847
* Worker class for opening a database.
848+
* TODO: shouldn't this be a PriorityWorker?
836849
*/
837850
struct OpenWorker final : public BaseWorker {
838851
OpenWorker (napi_env env,
@@ -1133,7 +1146,6 @@ struct ClearWorker final : public PriorityWorker {
11331146
}
11341147

11351148
~ClearWorker () {
1136-
// TODO: write GC tests
11371149
delete baseIterator_;
11381150
delete writeOptions_;
11391151
}
@@ -1476,6 +1488,7 @@ struct EndWorker final : public BaseWorker {
14761488
}
14771489

14781490
void HandleOKCallback () override {
1491+
// TODO: if we don't use EndWorker, do we still delete the reference?
14791492
napi_delete_reference(env_, iterator_->Detach());
14801493
BaseWorker::HandleOKCallback();
14811494
}

test/clear-gc-test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict'
2+
3+
const test = require('tape')
4+
const testCommon = require('./common')
5+
const sourceData = []
6+
7+
for (let i = 0; i < 1e3; i++) {
8+
sourceData.push({
9+
type: 'put',
10+
key: i.toString(),
11+
value: Math.random().toString()
12+
})
13+
}
14+
15+
test('db without ref does not get GCed while clear() is in progress', function (t) {
16+
t.plan(4)
17+
18+
let db = testCommon.factory()
19+
20+
db.open(function (err) {
21+
t.ifError(err, 'no open error')
22+
23+
// Insert test data
24+
db.batch(sourceData.slice(), function (err) {
25+
t.ifError(err, 'no batch error')
26+
27+
// Start async work
28+
db.clear(function () {
29+
t.pass('got callback')
30+
31+
// Give GC another chance to run, to rule out other issues.
32+
setImmediate(function () {
33+
if (global.gc) global.gc()
34+
t.pass()
35+
})
36+
})
37+
38+
// Remove reference. The db should not get garbage collected
39+
// until after the clear() callback, thanks to a napi_ref.
40+
db = null
41+
42+
// Useful for manual testing with "node --expose-gc".
43+
// The pending tap assertion may also allow GC to kick in.
44+
if (global.gc) global.gc()
45+
})
46+
})
47+
})

test/gc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ if (!global.gc) {
1010
require('./cleanup-hanging-iterators-test')
1111
require('./iterator-gc-test')
1212
require('./chained-batch-gc-test')
13+
require('./clear-gc-test')

0 commit comments

Comments
 (0)