leveldown: Assertion `dummy_versions_.next_ == &dummy_versions_' failed.

I’ve been getting this quite a lot lately. It seems to come and go at random, although probably mostly when the DB is undergoing strain

nodejs: ../deps/leveldb/leveldb-1.18.0/db/version_set.cc:789: leveldb::VersionSet::~VersionSet(): Assertion `dummy_versions_.next_ == &dummy_versions_' failed.
Aborted (core dumped)

Any tips? Is this a known bug?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 22 (15 by maintainers)

Most upvoted comments

In case it’s helpful, this is the PouchDB test that’s failing:

it('Closing db does not cause a crash if changes cancelled', function (done) {
  var db = new PouchDB(dbs.name);
  var called = 0;
  function checkDone() {
    called++;
    if (called === 2) {
      done();
    }
  }
  db.bulkDocs({ docs: [{ foo: 'bar' }] }, function () {
    var changes = db.changes({
      live: true
    }).on('complete', function (result) {
      result.status.should.equal('cancelled');
      checkDone();
    });
    should.exist(changes);
    changes.cancel.should.be.a('function');
    changes.cancel();
    db.close(function (error) {
      should.not.exist(error);
      checkDone();
    });
  });
});

https://github.com/pouchdb/pouchdb/blob/master/tests/integration/test.changes.js#L2312

Still failing in 1.6.0: https://github.com/pouchdb/pouchdb/pull/6202 (see issue for Travis runs & logs).

Snippet:

../deps/leveldb/leveldb-1.19/db/version_set.cc:789: leveldb::VersionSet::~VersionSet(): Assertion `dummy_versions_.next_ == &dummy_versions_' failed.

Afaik it’s related to closing the database while there are still open iterators. So the close operation have to wait until all iterators have ended before finally closing.

@ralphtheninja Actually I’ve already stole it 🤣: test/iterator-test.js

Here’s a repro without pouchdb. Runs ok with leveldown 1.5.0, consistently crashes with 1.5.1.

const test = require('tape')
const leveldown = require('../')
const testCommon = require('abstract-leveldown/testCommon')
const location = testCommon.location

test('close db with open iterator', function (t) {
  t.plan(4)

  const loc = location()
  const db = leveldown(loc)

  db.open(function (err) {
    t.ifError(err, 'no error from open()')

    db.put('key', 'value', function (err) {
      t.ifError(err, 'no error from put()')

      const ite = db.iterator()

      ;(function loop() {
        ite.next(function (err, key, value) {
          if (err) return t.fail(err)

          if (key === undefined) {
            t.ok(true, 'end of iterator')

            return ite.end(function (err) {
              t.ifError(err, 'no error from end()')
            })
          }

          loop()
        })
      })()

      db.close(function (err) {
        t.error(err, 'no error from close()')
      })
    })
  })
})

@nolanlawson I made a quick diff check (git diff v1.5.0 v1.5.1) and there was some work done on src/iterator.{h,cc} (I removed everything else from the diff that was not related to the c++ side). So there might be something in there that’s not 100% ok.

cc @Level/owners

--- a/src/iterator.cc
+++ b/src/iterator.cc
@@ -56,7 +56,9 @@ Iterator::Iterator (
   options->snapshot = database->NewSnapshot();
   dbIterator = NULL;
   count      = 0;
+  target     = NULL;
   seeking    = false;
+  landed     = false;
   nexting    = false;
   ended      = false;
   endWorker  = NULL;
@@ -64,6 +66,7 @@ Iterator::Iterator (
 
 Iterator::~Iterator () {
   delete options;
+  ReleaseTarget();
   if (start != NULL) {
     // Special case for `start` option: it won't be
     // freed up by any of the delete calls below.
@@ -171,6 +174,38 @@ bool Iterator::Read (std::string& key, std::string& value) {
   return false;
 }
 
+bool Iterator::OutOfRange (leveldb::Slice* target) {
+  if (lt != NULL) {
+    if (target->compare(*lt) >= 0)
+      return true;
+  } else if (lte != NULL) {
+    if (target->compare(*lte) > 0)
+      return true;
+  } else if (start != NULL && reverse) {
+    if (target->compare(*start) > 0)
+      return true;
+  }
+
+  if (end != NULL) {
+    int d = target->compare(*end);
+    if (reverse ? d < 0 : d > 0)
+      return true;
+  }
+
+  if (gt != NULL) {
+    if (target->compare(*gt) <= 0)
+      return true;
+  } else if (gte != NULL) {
+    if (target->compare(*gte) < 0)
+      return true;
+  } else if (start != NULL && !reverse) {
+    if (target->compare(*start) < 0)
+      return true;
+  }
+
+  return false;
+}
+
 bool Iterator::IteratorNext (std::vector<std::pair<std::string, std::string> >& result) {
   size_t size = 0;
   while(true) {
@@ -181,6 +216,11 @@ bool Iterator::IteratorNext (std::vector<std::pair<std::string, std::string> >&
       result.push_back(std::make_pair(key, value));
       size = size + key.size() + value.size();
 
+      if (!landed) {
+        landed = true;
+        return true;
+      }
+
       if (size > highWaterMark)
         return true;
 
@@ -205,7 +245,19 @@ void Iterator::Release () {
   database->ReleaseIterator(id);
 }
 
+void Iterator::ReleaseTarget () {
+  if (target != NULL) {
+
+    if (!target->empty())
+      delete[] target->data();
+
+    delete target;
+    target = NULL;
+  }
+}
+
 void checkEndCallback (Iterator* iterator) {
+  iterator->ReleaseTarget();
   iterator->nexting = false;
   if (iterator->endWorker != NULL) {
     Nan::AsyncQueueWorker(iterator->endWorker);
@@ -215,35 +267,53 @@ void checkEndCallback (Iterator* iterator) {
 
 NAN_METHOD(Iterator::Seek) {
   Iterator* iterator = Nan::ObjectWrap::Unwrap<Iterator>(info.This());
+
+  iterator->ReleaseTarget();
+
+  v8::Local<v8::Value> targetBuffer = info[0].As<v8::Value>();
+  LD_STRING_OR_BUFFER_TO_COPY(_target, targetBuffer, target);
+  iterator->target = new leveldb::Slice(_targetCh_, _targetSz_);
+
   iterator->GetIterator();
   leveldb::Iterator* dbIterator = iterator->dbIterator;
-  Nan::Utf8String key(info[0]);
 
-  dbIterator->Seek(*key);
+  dbIterator->Seek(*iterator->target);
   iterator->seeking = true;
+  iterator->landed = false;
 
-  if (dbIterator->Valid()) {
-    int cmp = dbIterator->key().compare(*key);
-    if (cmp > 0 && iterator->reverse) {
-      dbIterator->Prev();
-    } else if (cmp < 0 && !iterator->reverse) {
-      dbIterator->Next();
-    }
-  } else {
+  if (iterator->OutOfRange(iterator->target)) {
     if (iterator->reverse) {
-      dbIterator->SeekToLast();
-    } else {
       dbIterator->SeekToFirst();
+      dbIterator->Prev();
+    } else {
+      dbIterator->SeekToLast();
+      dbIterator->Next();
     }
+  }
+  else {
     if (dbIterator->Valid()) {
-      int cmp = dbIterator->key().compare(*key);
+      int cmp = dbIterator->key().compare(*iterator->target);
       if (cmp > 0 && iterator->reverse) {
-        dbIterator->SeekToFirst();
         dbIterator->Prev();
       } else if (cmp < 0 && !iterator->reverse) {
-        dbIterator->SeekToLast();
         dbIterator->Next();
       }
+    } else {
+      if (iterator->reverse) {
+        dbIterator->SeekToLast();
+      } else {
+        dbIterator->SeekToFirst();
+      }
+      if (dbIterator->Valid()) {
+        int cmp = dbIterator->key().compare(*iterator->target);
+        if (cmp > 0 && iterator->reverse) {
+          dbIterator->SeekToFirst();
+          dbIterator->Prev();
+        } else if (cmp < 0 && !iterator->reverse) {
+          dbIterator->SeekToLast();
+          dbIterator->Next();
+        }
+      }
     }
   }
 
diff --git a/src/iterator.h b/src/iterator.h
index bed53f8..07e67cd 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -53,6 +53,7 @@ public:
   leveldb::Status IteratorStatus ();
   void IteratorEnd ();
   void Release ();
+  void ReleaseTarget ();
 
 private:
   Database* database;
@@ -60,8 +61,10 @@ private:
   leveldb::Iterator* dbIterator;
   leveldb::ReadOptions* options;
   leveldb::Slice* start;
+  leveldb::Slice* target;
   std::string* end;
   bool seeking;
+  bool landed;
   bool reverse;
   bool keys;
   bool values;
@@ -83,6 +86,7 @@ public:
 private:
   bool Read (std::string& key, std::string& value);
   bool GetIterator ();
+  bool OutOfRange (leveldb::Slice* target);
 
   static NAN_METHOD(New);
   static NAN_METHOD(Seek);
diff --git a/test/iterator-test.js b/test/iterator-test.js
index 87db93b..49d2e2a 100644