22
33#include < napi-macros.h>
44#include < node_api.h>
5+ #include < assert.h>
56
67#include < leveldb/db.h>
78#include < leveldb/write_batch.h>
@@ -289,7 +290,7 @@ struct BaseWorker {
289290 delete self;
290291 }
291292
292- void DoComplete () {
293+ virtual void DoComplete () {
293294 if (status_.ok ()) {
294295 return HandleOKCallback ();
295296 }
@@ -308,7 +309,7 @@ struct BaseWorker {
308309 CallFunction (env_, callback, 1 , &argv);
309310 }
310311
311- void Queue () {
312+ virtual void Queue () {
312313 napi_queue_async_work (env_, asyncWork_);
313314 }
314315
@@ -332,6 +333,7 @@ struct Database {
332333 blockCache_(NULL ),
333334 filterPolicy_(leveldb::NewBloomFilterPolicy(10 )),
334335 currentIteratorId_(0 ),
336+ priorityWork_(0 ),
335337 pendingCloseWorker_(NULL ) {}
336338
337339 ~Database () {
@@ -404,21 +406,41 @@ struct Database {
404406 return db_->ReleaseSnapshot (snapshot);
405407 }
406408
407- void ReleaseIterator (uint32_t id) {
409+ void AttachIterator (uint32_t id, Iterator* iterator) {
410+ iterators_[id] = iterator;
411+ IncrementPriorityWork ();
412+ }
413+
414+ void DetachIterator (uint32_t id) {
408415 iterators_.erase (id);
409- if (iterators_.empty () && pendingCloseWorker_ != NULL ) {
416+ DecrementPriorityWork ();
417+ }
418+
419+ void IncrementPriorityWork () {
420+ ++priorityWork_;
421+ }
422+
423+ void DecrementPriorityWork () {
424+ if (--priorityWork_ == 0 && pendingCloseWorker_ != NULL ) {
410425 pendingCloseWorker_->Queue ();
411426 pendingCloseWorker_ = NULL ;
412427 }
413428 }
414429
430+ bool HasPriorityWork () {
431+ return priorityWork_ > 0 ;
432+ }
433+
415434 napi_env env_;
416435 leveldb::DB* db_;
417436 leveldb::Cache* blockCache_;
418437 const leveldb::FilterPolicy* filterPolicy_;
419438 uint32_t currentIteratorId_;
420439 BaseWorker *pendingCloseWorker_;
421440 std::map< uint32_t , Iterator * > iterators_;
441+
442+ private:
443+ uint32_t priorityWork_;
422444};
423445
424446/* *
@@ -430,6 +452,28 @@ static void FinalizeDatabase (napi_env env, void* data, void* hint) {
430452 }
431453}
432454
455+ /* *
456+ * Base worker class for doing async work that defers closing the database.
457+ * @TODO Use this for Get-, Del-, Batch-, BatchWrite-, ApproximateSize- and CompactRangeWorker too.
458+ */
459+ struct PriorityWorker : public BaseWorker {
460+ PriorityWorker (napi_env env, Database* database, napi_value callback, const char * resourceName)
461+ : BaseWorker(env, database, callback, resourceName) {
462+ }
463+
464+ virtual ~PriorityWorker () {}
465+
466+ void Queue () final {
467+ database_->IncrementPriorityWork ();
468+ BaseWorker::Queue ();
469+ }
470+
471+ void DoComplete () final {
472+ database_->DecrementPriorityWork ();
473+ BaseWorker::DoComplete ();
474+ }
475+ };
476+
433477/* *
434478 * Owns a leveldb iterator.
435479 */
@@ -472,13 +516,15 @@ struct Iterator {
472516 landed_(false ),
473517 nexting_(false ),
474518 ended_(false ),
475- endWorker_(NULL ) {
519+ endWorker_(NULL ),
520+ ref_(NULL ) {
476521 options_ = new leveldb::ReadOptions ();
477522 options_->fill_cache = fillCache;
478523 options_->snapshot = database->NewSnapshot ();
479524 }
480525
481526 ~Iterator () {
527+ assert (ended_);
482528 ReleaseTarget ();
483529 if (start_ != NULL ) {
484530 // Special case for `start` option: it won't be
@@ -506,6 +552,7 @@ struct Iterator {
506552 if (gte_ != NULL ) {
507553 delete gte_;
508554 }
555+ delete options_;
509556 }
510557
511558 void ReleaseTarget () {
@@ -518,8 +565,14 @@ struct Iterator {
518565 }
519566 }
520567
521- void Release () {
522- database_->ReleaseIterator (id_);
568+ void Attach (napi_ref ref) {
569+ ref_ = ref;
570+ database_->AttachIterator (id_, this );
571+ }
572+
573+ napi_ref Detach () {
574+ database_->DetachIterator (id_);
575+ return ref_;
523576 }
524577
525578 leveldb::Status IteratorStatus () {
@@ -681,6 +734,9 @@ struct Iterator {
681734
682735 leveldb::ReadOptions* options_;
683736 EndWorker* endWorker_;
737+
738+ private:
739+ napi_ref ref_;
684740};
685741
686742/* *
@@ -803,7 +859,7 @@ NAPI_METHOD(db_close) {
803859 napi_value callback = argv[1 ];
804860 CloseWorker* worker = new CloseWorker (env, database, callback);
805861
806- if (database->iterators_ . empty ()) {
862+ if (! database->HasPriorityWork ()) {
807863 worker->Queue ();
808864 NAPI_RETURN_UNDEFINED ();
809865 }
@@ -813,13 +869,11 @@ NAPI_METHOD(db_close) {
813869 napi_value noop;
814870 napi_create_function (env, NULL , 0 , noop_callback, NULL , &noop);
815871
816- std::map< uint32_t , Iterator * >::iterator it;
817- for (it = database->iterators_ .begin ();
818- it != database->iterators_ .end (); ++it) {
819- Iterator *iterator = it->second ;
820- if (!iterator->ended_ ) {
821- iterator_end_do (env, iterator, noop);
822- }
872+ std::map<uint32_t , Iterator*> iterators = database->iterators_ ;
873+ std::map<uint32_t , Iterator*>::iterator it;
874+
875+ for (it = iterators.begin (); it != iterators.end (); ++it) {
876+ iterator_end_do (env, it->second , noop);
823877 }
824878
825879 NAPI_RETURN_UNDEFINED ();
@@ -828,14 +882,14 @@ NAPI_METHOD(db_close) {
828882/* *
829883 * Worker class for putting key/value to the database
830884 */
831- struct PutWorker : public BaseWorker {
885+ struct PutWorker : public PriorityWorker {
832886 PutWorker (napi_env env,
833887 Database* database,
834888 napi_value callback,
835889 leveldb::Slice key,
836890 leveldb::Slice value,
837891 bool sync)
838- : BaseWorker (env, database, callback, " leveldown.db.put" ),
892+ : PriorityWorker (env, database, callback, " leveldown.db.put" ),
839893 key_ (key), value_(value) {
840894 options_.sync = sync;
841895 }
@@ -1292,12 +1346,18 @@ NAPI_METHOD(iterator_init) {
12921346 Iterator* iterator = new Iterator (database, id, start, end, reverse, keys,
12931347 values, limit, lt, lte, gt, gte, fillCache,
12941348 keyAsBuffer, valueAsBuffer, highWaterMark);
1295- database->iterators_ [id] = iterator;
1296-
12971349 napi_value result;
1350+ napi_ref ref;
1351+
12981352 NAPI_STATUS_THROWS (napi_create_external (env, iterator,
12991353 FinalizeIterator,
13001354 NULL , &result));
1355+
1356+ // Prevent GC of JS object before the iterator is ended (explicitly or on
1357+ // db close) and keep track of non-ended iterators to end them on db close.
1358+ NAPI_STATUS_THROWS (napi_create_reference (env, result, 1 , &ref));
1359+ iterator->Attach (ref);
1360+
13011361 return result;
13021362}
13031363
@@ -1372,7 +1432,7 @@ struct EndWorker : public BaseWorker {
13721432 }
13731433
13741434 virtual void HandleOKCallback () {
1375- iterator_->Release ( );
1435+ napi_delete_reference (env_, iterator_->Detach () );
13761436 BaseWorker::HandleOKCallback ();
13771437 }
13781438
0 commit comments