diff --git a/binding.cc b/binding.cc index 4b5d4f69..12a32153 100644 --- a/binding.cc +++ b/binding.cc @@ -937,6 +937,7 @@ struct Iterator final : public BaseIterator { first_(true), nexting_(false), isClosing_(false), + aborted_(false), ended_(false), ref_(NULL) { } @@ -959,7 +960,7 @@ struct Iterator final : public BaseIterator { size_t bytesRead = 0; leveldb::Slice empty; - while (true) { + while (!aborted_) { if (!first_) Next(); else first_ = false; @@ -998,6 +999,7 @@ struct Iterator final : public BaseIterator { bool first_; bool nexting_; bool isClosing_; + bool aborted_; bool ended_; std::vector cache_; @@ -1805,6 +1807,16 @@ NAPI_METHOD(iterator_close) { return promise; } +/** + * Aborts a NextWorker (if any, eventually). + */ +NAPI_METHOD(iterator_abort) { + NAPI_ARGV(1); + NAPI_ITERATOR_CONTEXT(); + iterator->aborted_ = true; + NAPI_RETURN_UNDEFINED(); +} + /** * Worker class for nexting an iterator. */ @@ -1828,6 +1840,15 @@ struct NextWorker final : public BaseWorker { } void HandleOKCallback (napi_env env, napi_deferred deferred) override { + if (iterator_->aborted_) { + napi_value err = CreateCodeError(env, "LEVEL_ABORTED", "Operation has been aborted"); + napi_value name; + napi_create_string_utf8(env, "AbortError", NAPI_AUTO_LENGTH, &name); + napi_set_named_property(env, err, "name", name); + napi_reject_deferred(env, deferred, err); + return; + } + size_t size = iterator_->cache_.size(); napi_value jsArray; napi_create_array_with_length(env, size, &jsArray); @@ -2154,6 +2175,7 @@ NAPI_INIT() { NAPI_EXPORT_FUNCTION(iterator_seek); NAPI_EXPORT_FUNCTION(iterator_close); NAPI_EXPORT_FUNCTION(iterator_nextv); + NAPI_EXPORT_FUNCTION(iterator_abort); NAPI_EXPORT_FUNCTION(batch_do); NAPI_EXPORT_FUNCTION(batch_init); diff --git a/index.js b/index.js index 16ada932..b288eec5 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,9 @@ class ClassicLevel extends AbstractLevel { additionalMethods: { approximateSize: true, compactRange: true + }, + signals: { + iterators: true } }, options) diff --git a/iterator.js b/iterator.js index cd73f0d5..9b2b6382 100644 --- a/iterator.js +++ b/iterator.js @@ -7,6 +7,8 @@ const kContext = Symbol('context') const kCache = Symbol('cache') const kFirst = Symbol('first') const kPosition = Symbol('position') +const kSignal = Symbol('signal') +const kAbort = Symbol('abort') const empty = [] // Does not implement _all() because the default implementation @@ -22,6 +24,15 @@ class Iterator extends AbstractIterator { this[kFirst] = true this[kCache] = empty this[kPosition] = 0 + this[kAbort] = this[kAbort].bind(this) + + // TODO: consider exposing iterator.signal in abstract-level + if (options.signal != null) { + this[kSignal] = options.signal + this[kSignal].addEventListener('abort', this[kAbort], { once: true }) + } else { + this[kSignal] = null + } } _seek (target, options) { @@ -63,9 +74,20 @@ class Iterator extends AbstractIterator { async _close () { this[kCache] = empty + + if (this[kSignal] !== null) { + this[kSignal].removeEventListener('abort', this[kAbort]) + this[kSignal] = null + } + return binding.iterator_close(this[kContext]) } + [kAbort] () { + this[kSignal] = null + binding.iterator_abort(this[kContext]) + } + // Undocumented, exposed for tests only get cached () { return this[kCache].length - this[kPosition]