// -------------------------------------------------------------------
//
// eleveldb: Erlang Wrapper for LevelDB (http://code.google.com/p/leveldb/)
//
// Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved.
//
// This file is provided to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file
// except in compliance with the License.  You may obtain
// a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
//
// -------------------------------------------------------------------

#include "rocksdb/utilities/backupable_db.h"
#include "refobjects.h"
#include "detail.hpp"

namespace erocksdb {

/**
 * RefObject Functions
 */

RefObject::RefObject()
    : m_RefCount(0)
{
}   // RefObject::RefObject


RefObject::~RefObject()
{
}   // RefObject::~RefObject


uint32_t
RefObject::RefInc()
{

    return(erocksdb::inc_and_fetch(&m_RefCount));

}   // RefObject::RefInc


uint32_t
RefObject::RefDec()
{
    uint32_t current_refs;

    current_refs=erocksdb::dec_and_fetch(&m_RefCount);
    if (0==current_refs)
        delete this;

    return(current_refs);

}   // RefObject::RefDec


/**
 * Erlang reference object
 */

ErlRefObject::ErlRefObject()
    : m_CloseRequested(0)
{
    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&m_CloseMutex, &attr);
    pthread_cond_init(&m_CloseCond, NULL);
    pthread_mutexattr_destroy(&attr);

    return;

}   // ErlRefObject::ErlRefObject


ErlRefObject::~ErlRefObject()
{

    pthread_mutex_lock(&m_CloseMutex);
    m_CloseRequested=3;
    pthread_cond_broadcast(&m_CloseCond);
    pthread_mutex_unlock(&m_CloseMutex);

    // DO NOT DESTROY m_CloseMutex or m_CloseCond here

}   // ErlRefObject::~ErlRefObject


bool
ErlRefObject::InitiateCloseRequest(
    ErlRefObject * Object)
{
    bool ret_flag;

    ret_flag=false;

    // special handling since destructor may have been called
    if (NULL!=Object && 0==Object->m_CloseRequested)
        ret_flag=compare_and_swap(&Object->m_CloseRequested, 0, 1);

    // vtable is still good, this thread is initiating close
    //   ask object to clean-up
    if (ret_flag)
    {
        Object->Shutdown();
    }   // if

    return(ret_flag);

}   // ErlRefObject::InitiateCloseRequest


void
ErlRefObject::AwaitCloseAndDestructor(
    ErlRefObject * Object)
{
    // NOTE:  it is possible, actually likely, that this
    //        routine is called AFTER the destructor is called
    //        Don't panic.

    if (NULL!=Object)
    {
        // quick test if any work pending
        if (3!=Object->m_CloseRequested)
        {
            pthread_mutex_lock(&Object->m_CloseMutex);

            // retest after mutex helc
            while (3!=Object->m_CloseRequested)
            {
                pthread_cond_wait(&Object->m_CloseCond, &Object->m_CloseMutex);
            }   // while
            pthread_mutex_unlock(&Object->m_CloseMutex);
        }   // if

        pthread_mutex_destroy(&Object->m_CloseMutex);
        pthread_cond_destroy(&Object->m_CloseCond);
    }   // if

    return;

}   // ErlRefObject::AwaitCloseAndDestructor


uint32_t
ErlRefObject::RefDec()
{
    uint32_t cur_count;

    cur_count=erocksdb::dec_and_fetch(&m_RefCount);

    // this the last active after close requested?
    //  (atomic swap should be unnecessary ... but going for safety)
    if (0==cur_count && compare_and_swap(&m_CloseRequested, 1, 2))
    {
        // deconstruct, but let erlang deallocate memory later
        this->~ErlRefObject();
    }   // if

    return(cur_count);

}   // DbObject::RefDec



/**
 * DbObject Functions
 */

ErlNifResourceType * DbObject::m_Db_RESOURCE(NULL);


void
DbObject::CreateDbObjectType(
    ErlNifEnv * Env)
{
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_Db_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_DbObject",
                                            &DbObject::DbObjectResourceCleanup,
                                            flags, NULL);

    return;

}   // DbObject::CreateDbObjectType


DbObject *
DbObject::CreateDbObject(rocksdb::DB * Db)
{
    DbObject * ret_ptr;
    void * alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr=enif_alloc_resource(m_Db_RESOURCE, sizeof(DbObject));
    ret_ptr=new (alloc_ptr) DbObject(Db);

    // manual reference increase to keep active until "close" called
    //  only inc local counter, leave erl ref count alone ... will force
    //  erlang to call us if process holding ref dies
    ret_ptr->RefInc();

    // see OpenTask::operator() for release of reference count

    return(ret_ptr);

}   // DbObject::CreateDbObject

DbObject *
DbObject::RetrieveDbObject(
    ErlNifEnv * Env,
    const ERL_NIF_TERM & DbTerm)
{
    DbObject * ret_ptr;

    ret_ptr=NULL;

    if (enif_get_resource(Env, DbTerm, m_Db_RESOURCE, (void **)&ret_ptr))
    {
        // has close been requested?
        if (ret_ptr->m_CloseRequested)
        {
            // object already closing
            ret_ptr=NULL;
        }   // else
    }   // if

    return(ret_ptr);

}   // DbObject::RetrieveDbObject


void
DbObject::DbObjectResourceCleanup(
    ErlNifEnv * /*env*/,
    void * arg)
{
    DbObject * db_ptr;

    db_ptr=(DbObject *)arg;

    // YES, the destructor may already have been called
    InitiateCloseRequest(db_ptr);

    // YES, the destructor may already have been called
    AwaitCloseAndDestructor(db_ptr);

    return;

}   // DbObject::DbObjectResourceCleanup


DbObject::DbObject(rocksdb::DB * DbPtr)
    : m_Db(DbPtr)
    {}   // DbObject::DbObject


DbObject::~DbObject()
{

    // close the db
    delete m_Db;
    m_Db=NULL;

    // do not clean up m_CloseMutex and m_CloseCond

    return;

}   // DbObject::~DbObject


void
DbObject::Shutdown()
{
#if 1
    bool again;
    ColumnFamilyObject *column_family_ptr;
    ItrObject * itr_ptr;
    SnapshotObject * snapshot_ptr;
    TLogItrObject *tlog_ptr;

    do
    {
        again=false;
        itr_ptr=NULL;

        // lock the ItrList
        {
            MutexLock lock(m_ItrMutex);

            if (!m_ItrList.empty())
            {
                again=true;
                itr_ptr=m_ItrList.front();
                m_ItrList.pop_front();
            }   // if
        }

        // must be outside lock so ItrObject can attempt
        //  RemoveReference
        if (again)
            ItrObject::InitiateCloseRequest(itr_ptr);

    } while(again);

    // clean columns families
    do {
        again = false;
        column_family_ptr = NULL;

        // lock the SnapshotList
        {
            MutexLock lock(m_ColumnFamilyMutex);

            if (!m_ColumnFamilyList.empty()) {
                again = true;
                column_family_ptr = m_ColumnFamilyList.front();
                m_ColumnFamilyList.pop_front();
            }   // if
        }

        // must be outside lock so SnapshotObject can attempt
        //  RemoveReference
        if (again)
            ColumnFamilyObject::InitiateCloseRequest(column_family_ptr);

    } while (again);

    // clean snapshots linked to the database object
    do
    {
        again=false;
        snapshot_ptr=NULL;

        // lock the SnapshotList
        {
            MutexLock lock(m_SnapshotMutex);

            if (!m_SnapshotList.empty())
            {
                again=true;
                snapshot_ptr=m_SnapshotList.front();
                m_SnapshotList.pop_front();
            }   // if
        }

        // must be outside lock so SnapshotObject can attempt
        //  RemoveReference
        if (again)
            SnapshotObject::InitiateCloseRequest(snapshot_ptr);

    } while(again);

    // clean transaction log iterators
    do {
        again = false;
        tlog_ptr = NULL;

        // lock the SnapshotList
        {
            MutexLock lock(m_TLogItrMutex);

            if (!m_TLogItrList.empty()) {
                again = true;
                tlog_ptr = m_TLogItrList.front();
                m_TLogItrList.pop_front();
            }   // if
        }

        // must be outside lock so SnapshotObject can attempt
        //  RemoveReference
        if (again)
            TLogItrObject::InitiateCloseRequest(tlog_ptr);

    } while (again);

#endif

    RefDec();

    return;

}   // DbObject::Shutdown

void
DbObject::AddColumnFamilyReference(
        ColumnFamilyObject *ColumnFamilyPtr) {
    MutexLock lock(m_ColumnFamilyMutex);
    m_ColumnFamilyList.push_back(ColumnFamilyPtr);
    return;
}   // DbObject::ColumnFamilyReference


void
DbObject::RemoveColumnFamilyReference(
        ColumnFamilyObject *ColumnFamilyPtr) {
    MutexLock lock(m_ColumnFamilyMutex);
    m_ColumnFamilyList.remove(ColumnFamilyPtr);
    return;
}   // DbObject::RemoveColumnFamilyReference

bool
DbObject::AddReference(
    ItrObject * ItrPtr)
{
    bool ret_flag;
    MutexLock lock(m_ItrMutex);

    ret_flag=(0==m_CloseRequested);
    if (ret_flag)
        m_ItrList.push_back(ItrPtr);

    return(ret_flag);
}   // DbObject::AddReference


void
DbObject::RemoveReference(
    ItrObject * ItrPtr)
{
    MutexLock lock(m_ItrMutex);
    m_ItrList.remove(ItrPtr);
    return;

}   // DbObject::RemoveReference

void
DbObject::AddSnapshotReference(
    SnapshotObject * SnapshotPtr)
{
    MutexLock lock(m_SnapshotMutex);
    m_SnapshotList.push_back(SnapshotPtr);
    return;

}   // DbObject::AddSnapshotReference


void
DbObject::RemoveSnapshotReference(
    SnapshotObject * SnapshotPtr)
{
    MutexLock lock(m_SnapshotMutex);

    m_SnapshotList.remove(SnapshotPtr);

    return;
}   // DbObject::RemoveSnapshotReference

void
DbObject::AddTLogReference(TLogItrObject *TLogItrPtr) {
    MutexLock lock(m_TLogItrMutex);

    m_TLogItrList.push_back(TLogItrPtr);

    return;

}   // DbObject::AddTLogReference


void
DbObject::RemoveTLogReference(TLogItrObject *TLogItrPtr) {
    MutexLock lock(m_TLogItrMutex);

    m_TLogItrList.remove(TLogItrPtr);

    return;

}   // DbObject::RemoveTLogReference

/**
* ColumnFamily object
*/

ErlNifResourceType *ColumnFamilyObject::m_ColumnFamily_RESOURCE(NULL);


void
ColumnFamilyObject::CreateColumnFamilyObjectType(
        ErlNifEnv *Env) {
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_ColumnFamily_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_ColumnFamilyObject",
                                                      &ColumnFamilyObject::ColumnFamilyObjectResourceCleanup,
                                                      flags, NULL);
    return;

}   // ColumnFamilyObject::CreateSnapshotObjectType


ColumnFamilyObject *
ColumnFamilyObject::CreateColumnFamilyObject(
        DbObject *DbPtr,
        rocksdb::ColumnFamilyHandle *Handle) {
    ColumnFamilyObject *ret_ptr;
    void *alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr = enif_alloc_resource(m_ColumnFamily_RESOURCE, sizeof(ColumnFamilyObject));
    ret_ptr = new(alloc_ptr) ColumnFamilyObject(DbPtr, Handle);

    // manual reference increase to keep active until "close" called
    //  only inc local counter
    ret_ptr->RefInc();

    // see IterTask::operator() for release of reference count

    return (ret_ptr);

}   // ColumnFamilyObject::ColumnFamilySnapshotObject


ColumnFamilyObject *
ColumnFamilyObject::RetrieveColumnFamilyObject(
        ErlNifEnv *Env,
        const ERL_NIF_TERM &ColumnFamilyTerm) {
    ColumnFamilyObject *ret_ptr;

    ret_ptr = NULL;

    if (enif_get_resource(Env, ColumnFamilyTerm, m_ColumnFamily_RESOURCE, (void **) &ret_ptr)) {
        // has close been requested?
        if (ret_ptr->m_CloseRequested) {
            // object already closing
            ret_ptr = NULL;
        }   // else
    }   // if

    return (ret_ptr);

}   // ColumnFamilyObject::RetrieveColumnFamilyObject


void
ColumnFamilyObject::ColumnFamilyObjectResourceCleanup(
        ErlNifEnv * /*env*/,
        void *arg) {
    ColumnFamilyObject *handle_ptr;
    handle_ptr = (ColumnFamilyObject *)arg;
    // vtable for snapshot_ptr could be invalid if close already
    //  occurred
    InitiateCloseRequest(handle_ptr);
    // YES this can be called after snapshot_ptr destructor.  Don't panic.
    AwaitCloseAndDestructor(handle_ptr);
    return;
}   // ColumnFamilyObject::ColumnFamilyObjectResourceCleanup


ColumnFamilyObject::ColumnFamilyObject(
        DbObject *DbPtr,
        rocksdb::ColumnFamilyHandle *Handle)
        : m_ColumnFamily(Handle), m_DbPtr(DbPtr) {


    if (NULL != DbPtr)
        DbPtr->AddColumnFamilyReference(this);
}   // ColumnFamilyObject::ColumnFamilyObject


ColumnFamilyObject::~ColumnFamilyObject() {
    if (NULL != m_ColumnFamily) {
        m_DbPtr->m_Db->DestroyColumnFamilyHandle(m_ColumnFamily);
        m_ColumnFamily = NULL;
    }
    if (NULL != m_DbPtr.get())
        m_DbPtr->RemoveColumnFamilyReference(this);
    return;
}   // ColumnFamilyObject::~ColumnFamilyObject


void
ColumnFamilyObject::Shutdown() {
    RefDec();
    return;
}   // ColumnFamilyObject::CloseRequest


/**
 * snapshot object
 */

ErlNifResourceType* SnapshotObject::m_DbSnapshot_RESOURCE(NULL);


void
SnapshotObject::CreateSnapshotObjectType(
    ErlNifEnv* Env)
{
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_DbSnapshot_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_SnapshotObject",
                                             &SnapshotObject::SnapshotObjectResourceCleanup,
                                             flags, NULL);

    return;

}   // SnapshotObject::CreateSnapshotObjectType


SnapshotObject *
SnapshotObject::CreateSnapshotObject(
    DbObject* DbPtr,
    const rocksdb::Snapshot* Snapshot)
{
    SnapshotObject* ret_ptr;
    void * alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr=enif_alloc_resource(m_DbSnapshot_RESOURCE, sizeof(SnapshotObject));

    ret_ptr=new (alloc_ptr) SnapshotObject(DbPtr, Snapshot);

    // manual reference increase to keep active until "close" called
    //  only inc local counter
    ret_ptr->RefInc();

    // see IterTask::operator() for release of reference count

    return(ret_ptr);

}   // SnapshotObject::CreateSnapshotObject


SnapshotObject *
SnapshotObject::RetrieveSnapshotObject(
    ErlNifEnv* Env,
    const ERL_NIF_TERM & SnapshotTerm)
{
    SnapshotObject* ret_ptr;

    ret_ptr=NULL;

    if (enif_get_resource(Env, SnapshotTerm, m_DbSnapshot_RESOURCE, (void **)&ret_ptr))
    {
        // has close been requested?
        if (ret_ptr->m_CloseRequested)
        {
            // object already closing
            ret_ptr=NULL;
        }   // else
    }   // if

    return(ret_ptr);

}   // SnapshotObject::RetrieveSnapshotObject


void
SnapshotObject::SnapshotObjectResourceCleanup(
    ErlNifEnv* /*env*/,
    void * arg)
{
    SnapshotObject* snapshot_ptr;

    snapshot_ptr=(SnapshotObject *)arg;

    if(NULL!=snapshot_ptr->m_Snapshot)
        snapshot_ptr->m_DbPtr->m_Db->ReleaseSnapshot(snapshot_ptr->m_Snapshot);

    // vtable for snapshot_ptr could be invalid if close already
    //  occurred
    InitiateCloseRequest(snapshot_ptr);

    // YES this can be called after snapshot_ptr destructor.  Don't panic.
    AwaitCloseAndDestructor(snapshot_ptr);

    return;

}   // SnapshotObject::SnapshotObjectResourceCleanup


SnapshotObject::SnapshotObject(
    DbObject* DbPtr,
    const rocksdb::Snapshot* Snapshot)
    : m_Snapshot(Snapshot), m_DbPtr(DbPtr)
{
    if (NULL!=DbPtr)
        DbPtr->AddSnapshotReference(this);

}   // SnapshotObject::SnapshotObject


SnapshotObject::~SnapshotObject()
{

    if (NULL!=m_DbPtr.get())
    {
        m_DbPtr->RemoveSnapshotReference(this);
    }

    m_Snapshot=NULL;

    // do not clean up m_CloseMutex and m_CloseCond

    return;

}   // SnapshotObject::~SnapshotObject


void
SnapshotObject::Shutdown()
{
    RefDec();

    return;
}   // ItrObject::CloseRequest


/**
* Iterator management object
*/

ErlNifResourceType *ItrObject::m_Itr_RESOURCE(NULL);


void
ItrObject::CreateItrObjectType(
        ErlNifEnv *Env) {
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_Itr_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_ItrObject",
                                             &ItrObject::ItrObjectResourceCleanup,
                                             flags, NULL);

    return;

}   // ItrObject::CreateItrObjectType


ItrObject *
ItrObject::CreateItrObject(
        DbObject *DbPtr,
        std::shared_ptr<erocksdb::ErlEnvCtr> Env,
        rocksdb::Iterator *Iterator) {
    ItrObject *ret_ptr;
    void *alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr = enif_alloc_resource(m_Itr_RESOURCE, sizeof(ItrObject));

    ret_ptr = new(alloc_ptr) ItrObject(DbPtr, Env, Iterator);

    // manual reference increase to keep active until "close" called
    //  only inc local counter
    ret_ptr->RefInc();

    // see IterTask::operator() for release of reference count

    return (ret_ptr);

}   // ItrObject::CreateItrObject


ItrObject *
ItrObject::RetrieveItrObject(
        ErlNifEnv *Env,
        const ERL_NIF_TERM &ItrTerm, bool ItrClosing) {
    ItrObject *ret_ptr;

    ret_ptr = NULL;

    if (enif_get_resource(Env, ItrTerm, m_Itr_RESOURCE, (void **) &ret_ptr)) {
        // has close been requested?
        if (ret_ptr->m_CloseRequested
            || (!ItrClosing && ret_ptr->m_DbPtr->m_CloseRequested)) {
            // object already closing
            ret_ptr = NULL;
        }   // else
    }   // if

    return (ret_ptr);

}   // ItrObject::RetrieveItrObject


void
ItrObject::ItrObjectResourceCleanup(
        ErlNifEnv * /*env*/,
        void *arg) {
    ItrObject *itr_ptr;

    itr_ptr = (ItrObject *) arg;

    // vtable for itr_ptr could be invalid if close already
    //  occurred
    InitiateCloseRequest(itr_ptr);

    // YES this can be called after itr_ptr destructor.  Don't panic.
    AwaitCloseAndDestructor(itr_ptr);

    return;

}   // ItrObject::ItrObjectResourceCleanup


void
ItrObject::SetUpperBoundSlice(rocksdb::Slice *slice)
{
    upper_bound_slice = slice;
}

void
ItrObject::SetLowerBoundSlice(rocksdb::Slice *slice) {
    lower_bound_slice = slice;
}



ItrObject::ItrObject(
        DbObject *DbPtr,
        std::shared_ptr<erocksdb::ErlEnvCtr> Env,
        rocksdb::Iterator *Iterator)
        : m_Iterator(Iterator),
          env(Env),
          m_DbPtr(DbPtr),
          upper_bound_slice(nullptr),
          lower_bound_slice(nullptr)
{

    if (NULL != DbPtr)
        DbPtr->AddReference(this);

}   // ItrObject::ItrObject



ItrObject::~ItrObject() {
    // not likely to have active reuse item since it would
    //  block destruction


    if (nullptr != m_DbPtr.get())
        m_DbPtr->RemoveReference(this);

    if(upper_bound_slice != nullptr)
        delete upper_bound_slice;

    if(lower_bound_slice != nullptr)
        delete lower_bound_slice;


    delete m_Iterator;
    //m_Iterator = nullptr;

    return;

}   // ItrObject::~ItrObject


void
ItrObject::Shutdown() {
    // if there is an active move object, set it up to delete
    //  (reuse_move holds a counter to this object, which will
    //   release when move object destructs)
    RefDec();

    return;

}   // ItrObject::CloseRequest

/**
* transaction log object
*/

ErlNifResourceType *TLogItrObject::m_TLogItr_RESOURCE(NULL);


void
TLogItrObject::CreateTLogItrObjectType(
        ErlNifEnv *Env) {
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_TLogItr_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_TLogItrObject",
                                                 &TLogItrObject::TLogItrObjectResourceCleanup,
                                                 flags, NULL);

    return;

}   // SnapshotObject::CreateSnapshotObjectType


TLogItrObject *
TLogItrObject::CreateTLogItrObject(
        DbObject *DbPtr,
        rocksdb::TransactionLogIterator * Itr) {
    TLogItrObject *ret_ptr;
    void *alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr = enif_alloc_resource(m_TLogItr_RESOURCE, sizeof(TLogItrObject));

    ret_ptr = new(alloc_ptr) TLogItrObject(DbPtr, Itr);

    // manual reference increase to keep active until "close" called
    //  only inc local counter
    ret_ptr->RefInc();

    // see IterTask::operator() for release of reference count

    return (ret_ptr);

}   // TLogItrObject::CreateTLogItrObject


TLogItrObject *
TLogItrObject::RetrieveTLogItrObject(
        ErlNifEnv *Env,
        const ERL_NIF_TERM &TLogItrTerm) {
    TLogItrObject *ret_ptr;

    ret_ptr = NULL;

    if (enif_get_resource(Env, TLogItrTerm, m_TLogItr_RESOURCE, (void **) &ret_ptr)) {
        // has close been requested?
        if (ret_ptr->m_CloseRequested) {
            // object already closing
            ret_ptr = NULL;
        }   // else
    }   // if

    return (ret_ptr);

}   // TLogItrObject::RetrieveTLogItrObject


void
TLogItrObject::TLogItrObjectResourceCleanup(
        ErlNifEnv * /*env*/,
        void *arg) {
    TLogItrObject *tlog_ptr;

    tlog_ptr = (TLogItrObject *)arg;

    // vtable for snapshot_ptr could be invalid if close already
    //  occurred
    InitiateCloseRequest(tlog_ptr);

    // YES this can be called after snapshot_ptr destructor.  Don't panic.
    AwaitCloseAndDestructor(tlog_ptr);

    return;

}   // SnapshotObject::SnapshotObjectResourceCleanup


TLogItrObject::TLogItrObject(
        DbObject *DbPtr,
        rocksdb::TransactionLogIterator * Itr)
        : m_Iter(Itr), m_DbPtr(DbPtr) {

    if (NULL != DbPtr)
        DbPtr->AddTLogReference(this);

}   // TLogItrObject::TLogItrObject


TLogItrObject::~TLogItrObject() {

    if (NULL != m_DbPtr.get())
        m_DbPtr->RemoveTLogReference(this);

    m_Iter = NULL;

    // do not clean up m_CloseMutex and m_CloseCond

    return;

}   // TLogItrObject::~TLogItrObject


void
TLogItrObject::Shutdown() {
    RefDec();

    return;
}   // TLogItrObject::CloseRequest



/**
 * BackupEngineObject Functions
 */

ErlNifResourceType * BackupEngineObject::m_BackupEngine_RESOURCE(NULL);


void
BackupEngineObject::CreateBackupEngineObjectType(
    ErlNifEnv * Env)
{
    ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);

    m_BackupEngine_RESOURCE = enif_open_resource_type(Env, NULL, "erocksdb_BackupEngineObject",
                                            &BackupEngineObject::BackupEngineObjectResourceCleanup,
                                            flags, NULL);

    return;

}


BackupEngineObject *
BackupEngineObject::CreateBackupEngineObject(
    rocksdb::BackupEngine * BackupEngine)
{
    BackupEngineObject * ret_ptr;
    void * alloc_ptr;

    // the alloc call initializes the reference count to "one"
    alloc_ptr=enif_alloc_resource(m_BackupEngine_RESOURCE, sizeof(BackupEngineObject));
    ret_ptr=new (alloc_ptr) BackupEngineObject(BackupEngine);

    // manual reference increase to keep active until "close" called
    //  only inc local counter, leave erl ref count alone ... will force
    //  erlang to call us if process holding ref dies
    ret_ptr->RefInc();

    // see OpenTask::operator() for release of reference count

    return(ret_ptr);

}

BackupEngineObject *
BackupEngineObject::RetrieveBackupEngineObject(
    ErlNifEnv * Env,
    const ERL_NIF_TERM & BackupEngineTerm)
{
    BackupEngineObject * ret_ptr;
    ret_ptr=NULL;
    if (enif_get_resource(Env, BackupEngineTerm, m_BackupEngine_RESOURCE, (void **)&ret_ptr))
    {
        // has close been requested?
        if (ret_ptr->m_CloseRequested)
        {
            // object already closing
            ret_ptr=NULL;
        }   // else
    }   // if
    return(ret_ptr);
}


void
BackupEngineObject::BackupEngineObjectResourceCleanup(
    ErlNifEnv * /*env*/,
    void * arg)
{
    BackupEngineObject * engine_ptr;

    engine_ptr=(BackupEngineObject *)arg;

    // YES, the destructor may already have been called
    InitiateCloseRequest(engine_ptr);

    // YES, the destructor may already have been called
    AwaitCloseAndDestructor(engine_ptr);

    return;

}

BackupEngineObject::BackupEngineObject(
    rocksdb::BackupEngine * BackupEnginePtr)
    : m_BackupEngine(BackupEnginePtr)
    {}


BackupEngineObject::~BackupEngineObject()
{

    // close the db
    delete m_BackupEngine;
    m_BackupEngine=NULL;
    return;

}   // BackupEngineObject::~BackupEngineObject


void
BackupEngineObject::Shutdown()
{
    RefDec();
    return;

}   // BackupEngineObject::Shutdown


} // namespace erocksdb


