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

Commit 7356ba4

Browse files
committed
Cleanup hanging iterator also when next() errored
1 parent 0f88586 commit 7356ba4

File tree

2 files changed

+66
-44
lines changed

2 files changed

+66
-44
lines changed

binding.cc

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ static napi_status CallFunction (napi_env env,
260260
*
261261
* - DoExecute (abstract, worker pool thread): main work
262262
* - HandleOKCallback (main thread): call JS callback on success
263+
* - HandleErrorCallback (main thread): call JS callback on error
263264
* - DoFinally (main thread): do cleanup regardless of success
264265
*/
265266
struct BaseWorker {
@@ -310,48 +311,52 @@ struct BaseWorker {
310311
}
311312

312313
virtual void DoExecute () = 0;
313-
virtual void DoFinally (napi_env env) {};
314314

315315
static void Complete (napi_env env, napi_status status, void* data) {
316316
BaseWorker* self = (BaseWorker*)data;
317317

318318
self->DoComplete(env);
319319
self->DoFinally(env);
320-
321-
napi_delete_reference(env, self->callbackRef_);
322-
napi_delete_async_work(env, self->asyncWork_);
323-
324-
delete self;
325320
}
326321

327322
void DoComplete (napi_env env) {
328-
if (status_.ok()) {
329-
return HandleOKCallback(env);
330-
}
331-
332-
napi_value argv = CreateError(env, errMsg_);
333323
napi_value callback;
334324
napi_get_reference_value(env, callbackRef_, &callback);
335-
CallFunction(env, callback, 1, &argv);
325+
326+
if (status_.ok()) {
327+
HandleOKCallback(env, callback);
328+
} else {
329+
HandleErrorCallback(env, callback);
330+
}
336331
}
337332

338-
virtual void HandleOKCallback (napi_env env) {
333+
virtual void HandleOKCallback (napi_env env, napi_value callback) {
339334
napi_value argv;
340335
napi_get_null(env, &argv);
341-
napi_value callback;
342-
napi_get_reference_value(env, callbackRef_, &callback);
343336
CallFunction(env, callback, 1, &argv);
344337
}
345338

339+
virtual void HandleErrorCallback (napi_env env, napi_value callback) {
340+
napi_value argv = CreateError(env, errMsg_);
341+
CallFunction(env, callback, 1, &argv);
342+
}
343+
344+
virtual void DoFinally (napi_env env) {
345+
napi_delete_reference(env, callbackRef_);
346+
napi_delete_async_work(env, asyncWork_);
347+
348+
delete this;
349+
}
350+
346351
void Queue (napi_env env) {
347352
napi_queue_async_work(env, asyncWork_);
348353
}
349354

350-
napi_ref callbackRef_;
351-
napi_async_work asyncWork_;
352355
Database* database_;
353356

354357
private:
358+
napi_ref callbackRef_;
359+
napi_async_work asyncWork_;
355360
leveldb::Status status_;
356361
char *errMsg_;
357362
};
@@ -491,6 +496,7 @@ struct PriorityWorker : public BaseWorker {
491496

492497
void DoFinally (napi_env env) override {
493498
database_->DecrementPriorityWork(env);
499+
BaseWorker::DoFinally(env);
494500
}
495501
};
496502

@@ -507,7 +513,6 @@ struct BaseIterator {
507513
int limit,
508514
bool fillCache)
509515
: database_(database),
510-
isEnding_(false),
511516
hasEnded_(false),
512517
didSeek_(false),
513518
reverse_(reverse),
@@ -669,7 +674,6 @@ struct BaseIterator {
669674
}
670675

671676
Database* database_;
672-
bool isEnding_;
673677
bool hasEnded_;
674678

675679
private:
@@ -713,6 +717,7 @@ struct Iterator final : public BaseIterator {
713717
highWaterMark_(highWaterMark),
714718
landed_(false),
715719
nexting_(false),
720+
isEnding_(false),
716721
endWorker_(NULL),
717722
ref_(NULL) {
718723
}
@@ -729,15 +734,6 @@ struct Iterator final : public BaseIterator {
729734
if (ref_ != NULL) napi_delete_reference(env, ref_);
730735
}
731736

732-
void CheckEndCallback (napi_env env) {
733-
nexting_ = false;
734-
735-
if (endWorker_ != NULL) {
736-
endWorker_->Queue(env);
737-
endWorker_ = NULL;
738-
}
739-
}
740-
741737
bool ReadMany (uint32_t size, std::vector<std::pair<std::string, std::string>>& result) {
742738
size_t bytesRead = 0;
743739

@@ -780,6 +776,7 @@ struct Iterator final : public BaseIterator {
780776
uint32_t highWaterMark_;
781777
bool landed_;
782778
bool nexting_;
779+
bool isEnding_;
783780
BaseWorker* endWorker_;
784781

785782
private:
@@ -1043,7 +1040,7 @@ struct GetWorker final : public PriorityWorker {
10431040
SetStatus(database_->Get(options_, key_, value_));
10441041
}
10451042

1046-
void HandleOKCallback (napi_env env) override {
1043+
void HandleOKCallback (napi_env env, napi_value callback) override {
10471044
napi_value argv[2];
10481045
napi_get_null(env, &argv[0]);
10491046

@@ -1053,8 +1050,6 @@ struct GetWorker final : public PriorityWorker {
10531050
napi_create_string_utf8(env, value_.data(), value_.size(), &argv[1]);
10541051
}
10551052

1056-
napi_value callback;
1057-
napi_get_reference_value(env, callbackRef_, &callback);
10581053
CallFunction(env, callback, 2, argv);
10591054
}
10601055

@@ -1233,12 +1228,10 @@ struct ApproximateSizeWorker final : public PriorityWorker {
12331228
size_ = database_->ApproximateSize(&range);
12341229
}
12351230

1236-
void HandleOKCallback (napi_env env) override {
1231+
void HandleOKCallback (napi_env env, napi_value callback) override {
12371232
napi_value argv[2];
12381233
napi_get_null(env, &argv[0]);
12391234
napi_create_int64(env, (uint64_t)size_, &argv[1]);
1240-
napi_value callback;
1241-
napi_get_reference_value(env, callbackRef_, &callback);
12421235
CallFunction(env, callback, 2, argv);
12431236
}
12441237

@@ -1486,10 +1479,9 @@ struct EndWorker final : public BaseWorker {
14861479
iterator_->End();
14871480
}
14881481

1489-
void HandleOKCallback (napi_env env) override {
1490-
// TODO: would this be safe(r) to do in DoFinally() i.e. after we call the callback?
1482+
void DoFinally (napi_env env) override {
14911483
iterator_->Detach(env);
1492-
BaseWorker::HandleOKCallback(env);
1484+
BaseWorker::DoFinally(env);
14931485
}
14941486

14951487
Iterator* iterator_;
@@ -1551,7 +1543,7 @@ struct NextWorker final : public BaseWorker {
15511543
}
15521544
}
15531545

1554-
void HandleOKCallback (napi_env env) override {
1546+
void HandleOKCallback (napi_env env, napi_value callback) override {
15551547
size_t arraySize = result_.size() * 2;
15561548
napi_value jsArray;
15571549
napi_create_array_with_length(env, arraySize, &jsArray);
@@ -1580,19 +1572,25 @@ struct NextWorker final : public BaseWorker {
15801572
napi_set_element(env, jsArray, static_cast<int>(arraySize - idx * 2 - 2), returnValue);
15811573
}
15821574

1583-
// clean up & handle the next/end state
1584-
// TODO: always do this, even on error
1585-
iterator_->CheckEndCallback(env);
1586-
15871575
napi_value argv[3];
15881576
napi_get_null(env, &argv[0]);
15891577
argv[1] = jsArray;
15901578
napi_get_boolean(env, !ok_, &argv[2]);
1591-
napi_value callback;
1592-
napi_get_reference_value(env, callbackRef_, &callback);
15931579
CallFunction(env, callback, 3, argv);
15941580
}
15951581

1582+
void DoFinally (napi_env env) override {
1583+
// clean up & handle the next/end state
1584+
iterator_->nexting_ = false;
1585+
1586+
if (iterator_->endWorker_ != NULL) {
1587+
iterator_->endWorker_->Queue(env);
1588+
iterator_->endWorker_ = NULL;
1589+
}
1590+
1591+
BaseWorker::DoFinally(env);
1592+
}
1593+
15961594
Iterator* iterator_;
15971595
std::vector<std::pair<std::string, std::string> > result_;
15981596
bool ok_;

test/cleanup-hanging-iterators-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,27 @@ makeTest('test ending iterators', function (db, t, done) {
9292
done()
9393
})
9494
})
95+
96+
makeTest('test recursive next', function (db, t, done) {
97+
// Test that we're able to close when user keeps scheduling work
98+
const it = db.iterator({ highWaterMark: 0 })
99+
100+
it.next(function loop (err, key) {
101+
if (err && err.message !== 'iterator has ended') throw err
102+
if (key !== undefined) it.next(loop)
103+
})
104+
105+
done()
106+
})
107+
108+
makeTest('test recursive next (random)', function (db, t, done) {
109+
// Same as the test above but closing at a random time
110+
const it = db.iterator({ highWaterMark: 0 })
111+
112+
it.next(function loop (err, key) {
113+
if (err && err.message !== 'iterator has ended') throw err
114+
if (key !== undefined) it.next(loop)
115+
})
116+
117+
setTimeout(done, Math.floor(Math.random() * 50))
118+
})

0 commit comments

Comments
 (0)