2

I get some super annoying asserts when I try to delete the leveldb instance and I'm not sure why it's happening!

The assert is happening in the version_set.cc file:

void VersionSet::AppendVersion(Version* v) {
  // Make "v" current
  assert(v->refs_ == 0); // <---??? how do I avoid this assertion?
  // the rest of the source code is available in the link to version_set.cc
}

Additionally, it asserts in another place in the same file:

Version::~Version() {
  assert(refs_ == 0); // <-- Again... how do I avoid this one too?
  // the rest of the source code is available in the link to version_set.cc
}

Here is more background details on the usage in my system, I have a:

  • class ExtStorage (Extended Storage) which has a LevelDB::DB instance.
  • class EextStorageDotNet, which is the C++/CLI wrapper around the ExtStorage.
  • class AltStorage, which holds a pointer to an ExtStorage class (passed through the constructor):
  • class AltStorageDotNet, which is the C++/CLI wrapper around the AltStorage.

The alternate storage class looks like this:

class AltStorage{
    ExtStorage* instance;
public:
    AltStorage(ExtStorage* extStorage):instance(extStorage){}

    ~AltStorage(){
        delete instance;
        instance = NULL;
    }
};

The ExtStorage class looks like this:

class ExtStorage{
    leveldb::DB* mydb;
public:
    ExtStorage(/*some parameters*/){
         mydb = new leveldb::DB(/*parameters*/);
    }

    // Destructor
    ~ExtStorage() {
        Close();
    }

    // deletes the leveldb::DB instance
    void Close() {
        if(mydb == NULL) {
            delete mydb; // <-- Asserts every time I get here when using with the AltStorageDotNet
            mydb= NULL;

            // Close the L1 and L2 caches
            // only once (
        }
    }
}

The AltStorageDotNet class looks like this:

public ref class AltStorageDotNet{
    AltStorage* altInstance;
    ExtStorageDotNet^ extInstance;
public:
    AltStorageDotNet() {
        ExtStorage extStorage = new ExtStorage(/*params*/);
        altInstance = new AltStorage(extStorage);
        extInstance = gcnew ExtStorageDotNet(extStorage);
    }

    ~AltStorageDotNet(){
        delete altInstance;
        altInstance = NULL;
        // no need to delete extInstance since it was created with gcnew 
    }

    !AltStorageDotNet(){
        delete altInstance;
        altInstance = NULL;
        // no need to delete extInstance since it was created with gcnew
    }

    inline ExtStorageDotNet^ GetExtInstance(){return extInstance;}
};

The DotNet wrappers look like this:

public ref class ExtStorageDotNet{
private:
    ExtStorage* instance;
public:
    ExtStorageDotNet(ExtStorage* extStorage){
        instance = extStorage;
    }

    ~ExtStorageDotNet(){
        delete instance;
        instance = NULL;
    }

    !ExtStorageDotNet(){
        delete instance;
        instance = NULL;
    }

    void Close(){instance->Close();}
};

Whenever I use the ExtStorageDotNet wrapper in my C# application everything works good and there are no asserts. However, when I use the AltStorageDotNet wrapper and I access the ExtStorageDotNet wrapper, then I get the asserts when closing the database. This is all part of a test suite in which I initialize an instance for each test case and close it after each test case; the associated database files get deleted before a new test case begins. I don't see any reason why it should happen and the assertion is not helpful in tracking down the problem.

Kiril
  • 39,672
  • 31
  • 167
  • 226
  • `// no need to delete extInstance since it was created with gcnew` this comment is **very** misguided. – ildjarn Aug 17 '11 at 23:24
  • I can't delete extInstance, there is no gcdelete... I can set it to null, but that's about it. "gcnew is for .NET reference objects; objects created with gcnew are automatically garbage-collected; it is important to use gcnew with CLR types" (http://stackoverflow.com/questions/202459/what-is-gcnew/202464#202464) – Kiril Aug 17 '11 at 23:30
  • No, there is no `gcdelete`, but `delete` has entirely different semantics on managed types vs. unmanaged types. You still _should_ use it, and in some cases _need_ to -- this is likely one of those cases. – ildjarn Aug 17 '11 at 23:32
  • What about the automatic garbage collection? – Kiril Aug 17 '11 at 23:35
  • Garbage collection is about memory management; `IDisposable` is about deterministic finalization -- for your purposes, these are completely orthogonal concepts. – ildjarn Aug 17 '11 at 23:36

2 Answers2

3

I got rid of the nested references, but that didn't solve the problem. As it turns out, the issue causing this problem was not quite where I was looking at. The issue occurs when a user gets an iterator to the database and fails to delete the iterator prior to deleting the database. This was discussed in a google group relating to level db.

// Caller should delete the iterator when it is no longer needed.
// The returned iterator should be deleted before this db is deleted.
virtual Iterator* NewIterator(const ReadOptions& options) = 0; 

When the user gets the iterator they should delete it prior to deleting the db, otherwise they will get the assertion mentioned above.

Kiril
  • 39,672
  • 31
  • 167
  • 226
1

I don't know whether it's related to your asserts or not, but this code is guaranteed to cause memory corruption. Both ExtStorageDotNet and AltStorageDotNet are disposable and finalizable, and both (directly or indirectly) delete the instance of ExtStorage -- invariably, one will delete it after the other already has.

Also, as I said in my comment, the comment in your code saying no need to delete extInstance since it was created with gcnew is way off -- see this answer for details. (I assume you know C# and thus about IDisposable; if you don't, then you really need to do some reading.)

Community
  • 1
  • 1
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • OK, I don't see how I would get memory corruption if `delete` is equivalent to a nop on a NULL pointer (which is what I set the pointer to after I delete it). Furthermore, according to [the answer on this question](http://stackoverflow.com/questions/202459/what-is-gcnew/202464#202464), objects created with gcnew get garbage collected automatically. – Kiril Aug 17 '11 at 23:34
  • @Lirik : You set `ExtStorageDotNet::instance` to `NULL`, but how do you expect that to affect `AltStorage::instance` (and vice-versa)? Also, forget about garbage collection for this discussion, as it's entirely irrelevant to `IDisposable`. ;-] – ildjarn Aug 17 '11 at 23:37
  • got it: don't need to delete the `AltStorage::instance`. – Kiril Aug 17 '11 at 23:44
  • @Lirik : Not quite. :-/ You need to restructure your code so that only one object owns a given unmanaged object, _and_ you need to `delete` the managed objects you own in your destructors (but not your finalizers). Again, please do some reading about `IDisposable`; there are many articles about implementing it correctly on MSDN. – ildjarn Aug 17 '11 at 23:47
  • OK, I don't implement `IDisposable`, so how would deleting `AltStoragDotNet::extInstance` help me? – Kiril Aug 17 '11 at 23:50
  • @Lirik : A "destructor" on a managed type _is_ an implementation of `IDisposable`; the destructor syntax is just syntactic sugar. Likewise, the `!Classname()` syntax is just syntactic sugar for overriding [`Object::Finalize`](http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx). Now it may be time to read up on some C++/CLI fundamentals. ;-] – ildjarn Aug 17 '11 at 23:51
  • And `!AltStorageDotNet` is the finalizer? So I can just get rid of the finalizer and that should take care of one of the problems. – Kiril Aug 17 '11 at 23:52
  • @Lirik : No... When you own unmanaged memory/objects, you **always** need a finalizer. This should be covered in any article about implementing `IDisposable` correctly. – ildjarn Aug 17 '11 at 23:53
  • So where do I delete, in the finalizer or in the dispose/distructor? – Kiril Aug 17 '11 at 23:54
  • @Lirik : `delete` unmanaged objects in both; `delete` managed objects in only the destructor. Again, this should be covered in any article about implementing `IDisposable` correctly (_hint_, _hint_ -- go read some). – ildjarn Aug 17 '11 at 23:54