mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
1069 lines
26 KiB
C++
1069 lines
26 KiB
C++
/*-
|
|
* See the file LICENSE for redistribution information.
|
|
*
|
|
* Copyright (c) 2009, 2011 Oracle and/or its affiliates. All rights reserved.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <utility>
|
|
|
|
#include "dbstl_resource_manager.h"
|
|
#include "dbstl_exception.h"
|
|
#include "dbstl_dbc.h"
|
|
|
|
START_NS(dbstl)
|
|
|
|
typedef struct {
|
|
time_t tv_sec; /* seconds */
|
|
long tv_nsec; /* nanoseconds */
|
|
} db_timespec;
|
|
|
|
extern "C"{
|
|
void __os_id (DB_ENV *, pid_t *, db_threadid_t*);
|
|
void __os_gettime(ENV *env, db_timespec *tp, int monotonic);
|
|
}
|
|
|
|
using std::pair;
|
|
using std::make_pair;
|
|
|
|
// Static data member definitions.
|
|
map<Db*, size_t> ResourceManager::open_dbs_;
|
|
map<DbEnv*, size_t> ResourceManager::open_envs_;
|
|
set<DbstlGlobalInnerObject *> ResourceManager::glob_objs_;
|
|
set<Db *> ResourceManager::deldbs;
|
|
set<DbEnv *> ResourceManager::delenvs;
|
|
|
|
DbEnv * ResourceManager::mtx_env_ = NULL;
|
|
db_mutex_t ResourceManager::mtx_handle_ = 0;
|
|
db_mutex_t ResourceManager::mtx_globj_ = 0;
|
|
|
|
#ifdef TLS_DEFN_MODIFIER
|
|
template <Typename T> TLS_DEFN_MODIFIER T *TlsWrapper<T>::tinst_ = NULL;
|
|
#elif defined(HAVE_PTHREAD_TLS)
|
|
static pthread_once_t once_control_ = PTHREAD_ONCE_INIT;
|
|
template <Typename T>
|
|
pthread_key_t TlsWrapper<T>::tls_key_;
|
|
|
|
template<Typename T>
|
|
void tls_init_once(void) {
|
|
pthread_key_create(&TlsWrapper<T>::tls_key_, NULL);
|
|
}
|
|
|
|
template<Typename T>
|
|
TlsWrapper<T>::TlsWrapper()
|
|
{
|
|
pthread_once(&once_control_, tls_init_once<T>);
|
|
}
|
|
#else
|
|
#error "No suitable thread-local storage model configured"
|
|
#endif
|
|
|
|
int ResourceManager::global_lock(db_mutex_t dbcontainer_mtx)
|
|
{
|
|
int ret;
|
|
|
|
ret = mtx_env_->mutex_lock(dbcontainer_mtx);
|
|
dbstl_assert(ret == 0);
|
|
return 0;
|
|
}
|
|
|
|
int ResourceManager::global_unlock(db_mutex_t dbcontainer_mtx)
|
|
{
|
|
int ret;
|
|
|
|
ret = mtx_env_->mutex_unlock(dbcontainer_mtx);
|
|
dbstl_assert(ret == 0);
|
|
return 0;
|
|
}
|
|
|
|
u_int32_t dbstl_strlen(const char *str)
|
|
{
|
|
return (u_int32_t)strlen(str);
|
|
}
|
|
|
|
void dbstl_strcpy(char *dest, const char *src, size_t num)
|
|
{
|
|
strncpy(dest, src, num);
|
|
}
|
|
|
|
int dbstl_strncmp(const char *s1, const char *s2, size_t num)
|
|
{
|
|
return strncmp(s1, s2, num);
|
|
}
|
|
|
|
int dbstl_wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t num)
|
|
{
|
|
return wcsncmp(s1, s2, num);
|
|
}
|
|
|
|
int dbstl_strcmp(const char *s1, const char *s2)
|
|
{
|
|
return strcmp(s1, s2);
|
|
}
|
|
|
|
int dbstl_wcscmp(const wchar_t *s1, const wchar_t *s2)
|
|
{
|
|
return wcscmp(s1, s2);
|
|
}
|
|
|
|
u_int32_t dbstl_wcslen(const wchar_t *str)
|
|
{
|
|
return (u_int32_t)wcslen(str);
|
|
}
|
|
|
|
void dbstl_wcscpy(wchar_t *dest, const wchar_t *src, size_t num)
|
|
{
|
|
wcsncpy(dest, src, num);
|
|
}
|
|
|
|
// This function should be called in a single thread inside a process, before
|
|
// any use of dbstl. We don't want to rely on platform dependent mutex API,
|
|
// so we defer the synchronization to users.
|
|
void ResourceManager::global_startup()
|
|
{
|
|
int ret;
|
|
db_timespec tnow;
|
|
|
|
if (mtx_env_ == NULL) {
|
|
mtx_env_ = new DbEnv(DB_CXX_NO_EXCEPTIONS);
|
|
// Set cache size to 32k, to save space.
|
|
BDBOP(mtx_env_->set_cachesize(0, 32 * 1024, 1), ret);
|
|
BDBOP(mtx_env_->mutex_set_max(DBSTL_MAX_MTX_ENV_MUTEX), ret);
|
|
BDBOP2(mtx_env_->open(NULL, DB_PRIVATE | DB_CREATE, 0777),
|
|
ret, mtx_env_->close(0));
|
|
BDBOP2(mtx_env_->mutex_alloc(DB_MUTEX_PROCESS_ONLY,
|
|
&mtx_handle_), ret, mtx_env_->mutex_free(mtx_handle_));
|
|
BDBOP2(mtx_env_->mutex_alloc(DB_MUTEX_PROCESS_ONLY,
|
|
&mtx_globj_), ret, mtx_env_->mutex_free(mtx_globj_));
|
|
__os_gettime(NULL, &tnow, 0);
|
|
srand((unsigned int)tnow.tv_sec);
|
|
}
|
|
|
|
}
|
|
|
|
ResourceManager::ResourceManager(void)
|
|
{
|
|
|
|
// Initialize process wide dbstl settings. If there are multiple
|
|
// threads, the global_startup should be called in a single thread
|
|
// before any use of dbstl.
|
|
global_startup();
|
|
}
|
|
|
|
void ResourceManager::close_db(Db *pdb)
|
|
{
|
|
bool berase = false;
|
|
|
|
if (pdb == NULL)
|
|
return;
|
|
db_csr_map_t::iterator itr = all_csrs_.find(pdb);
|
|
if (itr == all_csrs_.end())
|
|
return;
|
|
|
|
this->close_db_cursors(pdb);
|
|
|
|
delete all_csrs_[pdb];
|
|
all_csrs_.erase(itr);
|
|
pdb->close(0);
|
|
set<Db *>::iterator itrdb = deldbs.find(pdb);
|
|
// If new'ed by open_db, delete it.
|
|
if (itrdb != deldbs.end()) {
|
|
delete *itrdb;
|
|
berase = true;
|
|
}
|
|
|
|
global_lock(mtx_handle_);
|
|
open_dbs_.erase(pdb);
|
|
if (berase)
|
|
deldbs.erase(itrdb);
|
|
|
|
global_unlock(mtx_handle_);
|
|
}
|
|
|
|
void ResourceManager::close_all_db_envs()
|
|
{
|
|
u_int32_t oflags;
|
|
int ret;
|
|
size_t txnstk_sz;
|
|
|
|
global_lock(mtx_handle_);
|
|
for (map<DbEnv*, size_t>::iterator i = open_envs_.begin();
|
|
i != open_envs_.end(); ++i) {
|
|
BDBOP(i->first->get_open_flags(&oflags), ret);
|
|
txnstk_sz = env_txns_[i->first].size();
|
|
if (oflags & DB_INIT_CDB) {
|
|
assert(txnstk_sz == 1);
|
|
BDBOP(env_txns_[i->first].top()->commit(0), ret);
|
|
} else
|
|
assert(txnstk_sz == 0);
|
|
|
|
i->first->close(0);
|
|
}
|
|
|
|
// Delete DbEnv objects new'ed by dbstl.
|
|
set<DbEnv *>::iterator itr2 = delenvs.begin();
|
|
for (; itr2 != delenvs.end(); ++itr2)
|
|
delete *itr2;
|
|
|
|
delenvs.clear();
|
|
env_txns_.clear();
|
|
open_envs_.clear();
|
|
global_unlock(mtx_handle_);
|
|
}
|
|
|
|
void ResourceManager::close_db_env(DbEnv *penv)
|
|
{
|
|
u_int32_t oflags;
|
|
int ret;
|
|
size_t txnstk_sz;
|
|
bool berase = false;
|
|
|
|
if (penv == NULL)
|
|
return;
|
|
map<DbEnv *, txnstk_t>::iterator itr = env_txns_.find(penv);
|
|
if (itr == env_txns_.end())
|
|
return;
|
|
BDBOP(penv->get_open_flags(&oflags), ret);
|
|
txnstk_sz = itr->second.size();
|
|
if (oflags & DB_INIT_CDB) {
|
|
assert(txnstk_sz == 1);
|
|
BDBOP(itr->second.top()->commit(0), ret);
|
|
} else
|
|
assert(txnstk_sz == 0);
|
|
env_txns_.erase(itr);
|
|
penv->close(0);
|
|
|
|
set<DbEnv *>::iterator itrdb = delenvs.find(penv);
|
|
// If new'ed by open_db, delete it.
|
|
if (itrdb != delenvs.end()) {
|
|
delete penv;
|
|
berase = true;
|
|
}
|
|
|
|
global_lock(mtx_handle_);
|
|
open_envs_.erase(penv);
|
|
if (berase)
|
|
delenvs.erase(itrdb);
|
|
global_unlock(mtx_handle_);
|
|
}
|
|
|
|
void ResourceManager::close_all_dbs()
|
|
{
|
|
map<Db *, size_t>::iterator itr;
|
|
set<Db *>::iterator itr2;
|
|
Db *pdb;
|
|
|
|
global_lock(mtx_handle_);
|
|
for (itr = open_dbs_.begin(); itr != open_dbs_.end(); ++itr) {
|
|
pdb = itr->first;
|
|
this->close_db_cursors(pdb);
|
|
|
|
delete all_csrs_[pdb];
|
|
all_csrs_.erase(pdb);
|
|
pdb->close(0);
|
|
}
|
|
|
|
// Delete Db objects new'ed by dbstl.
|
|
for (itr2 = deldbs.begin(); itr2 != deldbs.end(); ++itr2)
|
|
delete *itr2;
|
|
|
|
deldbs.clear();
|
|
open_dbs_.clear();
|
|
|
|
global_unlock(mtx_handle_);
|
|
}
|
|
|
|
ResourceManager::~ResourceManager(void)
|
|
{
|
|
u_int32_t oflags;
|
|
int ret;
|
|
set<Db *> dbs2del;
|
|
set<DbEnv *> envs2del;
|
|
|
|
global_lock(mtx_handle_);
|
|
|
|
for (map<Db*, size_t>::iterator i = open_dbs_.begin();
|
|
i != open_dbs_.end(); ++i) {
|
|
this->close_db_cursors(i->first);
|
|
(i->second)--;
|
|
if (i->second == 0) {
|
|
|
|
delete all_csrs_[i->first]; // Delete the cursor set.
|
|
all_csrs_.erase(i->first);
|
|
i->first->close(0);
|
|
|
|
set<Db *>::iterator itrdb = deldbs.find(i->first);
|
|
// If new'ed by open_db, delete it.
|
|
if (itrdb != deldbs.end()) {
|
|
delete *itrdb;
|
|
deldbs.erase(itrdb);
|
|
}
|
|
dbs2del.insert(i->first);
|
|
}
|
|
}
|
|
|
|
for (map<DbEnv*, size_t>::iterator i = open_envs_.begin();
|
|
i != open_envs_.end(); ++i) {
|
|
BDBOP(i->first->get_open_flags(&oflags), ret);
|
|
if (oflags & DB_INIT_CDB) {
|
|
assert(env_txns_[i->first].size() == 1);
|
|
BDBOP(env_txns_[i->first].top()->commit(0), ret);
|
|
env_txns_[i->first].pop();
|
|
}
|
|
|
|
(i->second)--;
|
|
if (i->second == 0) {
|
|
assert(env_txns_[i->first].size() == 0);
|
|
i->first->close(0);
|
|
set<DbEnv *>::iterator itrdb = delenvs.find(i->first);
|
|
// If new'ed by open_db, delete it.
|
|
if (itrdb != delenvs.end()) {
|
|
delete *itrdb;
|
|
delenvs.erase(itrdb);
|
|
}
|
|
envs2del.insert(i->first);
|
|
}
|
|
}
|
|
|
|
// Erase db/env entries that are just closed.
|
|
for (set<Db *>::iterator i = dbs2del.begin(); i != dbs2del.end(); ++i)
|
|
open_dbs_.erase(*i);
|
|
for (set<DbEnv *>::iterator i = envs2del.begin();
|
|
i != envs2del.end(); ++i)
|
|
open_envs_.erase(*i);
|
|
|
|
global_unlock(mtx_handle_);
|
|
|
|
for (db_csr_map_t::iterator itr3 = all_csrs_.begin();
|
|
itr3 != all_csrs_.end(); ++itr3)
|
|
{
|
|
// Delete the cursor set. Above code may not have a chance to
|
|
// delete this set because the db(itr3->first) was already
|
|
// closed by another thread.
|
|
delete itr3->second;
|
|
}
|
|
// Don't bother to clear all_csrs_ since it is being destructed.
|
|
|
|
// Don't handle transactions, leave them alone, because autocommit
|
|
// transactions must have been committed/aborted, and outside transactions
|
|
// should be handled by user code, and if they are not handled yet,
|
|
// the DbEnv::close will fail.
|
|
// Only handle the transaction for CDS mode---an DB_TXN* handle is opened
|
|
// at environment registration/creation by cdsgroup_begin, so we need to commit
|
|
// that transaction.
|
|
//
|
|
}
|
|
|
|
Db* ResourceManager::open_db (
|
|
DbEnv*penv, const char* filename, DBTYPE dbtype,
|
|
u_int32_t oflags, u_int32_t set_flags1, int mode, DbTxn* txn,
|
|
u_int32_t cflags, const char* dbname)
|
|
{
|
|
int ret, ci = 0;
|
|
u_int32_t envf = 0, envoflags = 0;
|
|
DbTxn *ptxn = NULL;
|
|
Db *pdb = new Db(penv, cflags | DB_CXX_NO_EXCEPTIONS);
|
|
|
|
if (penv) {
|
|
BDBOP(penv->get_open_flags(&envoflags), ret);
|
|
BDBOP(penv->get_flags(&envf), ret);
|
|
}
|
|
if (set_flags1)
|
|
BDBOP(pdb->set_flags(set_flags1), ret);
|
|
// If no transaction is specified and we really need one, begin a
|
|
// transaction and commit it before return, we don't commit
|
|
// passed-in transaction.
|
|
//
|
|
if (penv && ((envf & DB_AUTO_COMMIT) ||
|
|
(envoflags & DB_INIT_TXN)) && txn == 0){
|
|
ptxn = current_txn(penv);
|
|
BDBOP2(penv->txn_begin(ptxn, &txn, 0), ret, txn->abort());
|
|
ci = 1;
|
|
}
|
|
if (txn == NULL)
|
|
BDBOP2(pdb->open(txn, filename, dbname,
|
|
dbtype, oflags, mode),
|
|
ret, (pdb->close(0)));
|
|
else
|
|
BDBOP2(pdb->open(txn, filename, dbname,
|
|
dbtype, oflags, mode),
|
|
ret, (pdb->close(0), txn->abort()));
|
|
if (ci && txn)
|
|
BDBOP(txn->commit(0), ret);
|
|
global_lock(mtx_handle_);
|
|
open_dbs_.insert(make_pair(pdb, 1u));
|
|
pair<set<Db *>::iterator, bool> delinsret = deldbs.insert(pdb);
|
|
assert(delinsret.second);
|
|
global_unlock(mtx_handle_);
|
|
csrset_t *mycsrs = new csrset_t();
|
|
all_csrs_.insert(make_pair(pdb, mycsrs));
|
|
|
|
return pdb;
|
|
}
|
|
|
|
// Only called if the user does not supply an environment handle.
|
|
DbEnv* ResourceManager::open_env(const char* env_home, u_int32_t set_flags1,
|
|
u_int32_t oflags, u_int32_t cachesize, int mode, u_int32_t cflags)
|
|
{
|
|
int ret;
|
|
|
|
DbEnv *penv = new DbEnv(cflags | DB_CXX_NO_EXCEPTIONS);
|
|
if (set_flags1)
|
|
BDBOP(penv->set_flags(set_flags1, 1), ret);
|
|
BDBOP(penv->set_cachesize(0, cachesize, 1), ret);
|
|
BDBOP(penv->set_lk_max_lockers(2000), ret);
|
|
BDBOP(penv->set_lk_max_locks(2000), ret);
|
|
BDBOP(penv->set_lk_max_objects(2000), ret);
|
|
BDBOP2(penv->open(env_home, oflags, mode), ret, penv->close(0));
|
|
|
|
stack<DbTxn*> stk;
|
|
DbTxn *ptxn = NULL;
|
|
if (oflags & DB_INIT_CDB) {
|
|
BDBOP2(penv->cdsgroup_begin(&ptxn), ret, ptxn->commit(0));
|
|
stk.push(ptxn);
|
|
}
|
|
|
|
env_txns_.insert(make_pair(penv, stk));
|
|
global_lock(mtx_handle_);
|
|
open_envs_.insert(make_pair(penv, 1u));
|
|
delenvs.insert(penv);
|
|
global_unlock(mtx_handle_);
|
|
return penv;
|
|
}
|
|
|
|
DbTxn* ResourceManager::current_txn(DbEnv*env)
|
|
{
|
|
if (env_txns_.count(env) <= 0)
|
|
return NULL;
|
|
|
|
stack<DbTxn*> &pstk = env_txns_[env];
|
|
return pstk.size() != 0 ? pstk.top() : NULL;
|
|
}
|
|
|
|
void ResourceManager::thread_exit()
|
|
{
|
|
ResourceManager *pinst;
|
|
|
|
pinst = ResourceManager::instance();
|
|
if (pinst == NULL) // Already deleted.
|
|
return;
|
|
|
|
global_lock(mtx_globj_);
|
|
glob_objs_.erase(pinst);
|
|
global_unlock(mtx_globj_);
|
|
TlsWrapper<ResourceManager>::set_tls_obj(NULL);
|
|
|
|
// Can't put this line between the lock and unlock pair above,
|
|
// because the destructor also locks the same mutex, there
|
|
// would be a self lock.
|
|
delete pinst;
|
|
}
|
|
|
|
void ResourceManager::set_global_callbacks()
|
|
{
|
|
DbstlElemTraits<char> * cstarinst =
|
|
DbstlElemTraits<char>::instance();
|
|
cstarinst->set_sequence_len_function(dbstl_strlen);
|
|
cstarinst->set_sequence_copy_function(dbstl_strcpy);
|
|
cstarinst->set_sequence_compare_function(dbstl_strcmp);
|
|
cstarinst->set_sequence_n_compare_function(dbstl_strncmp);
|
|
|
|
DbstlElemTraits<wchar_t> *wcstarinst =
|
|
DbstlElemTraits<wchar_t>::instance();
|
|
wcstarinst->set_sequence_copy_function(dbstl_wcscpy);
|
|
wcstarinst->set_sequence_len_function(dbstl_wcslen);
|
|
wcstarinst->set_sequence_compare_function(dbstl_wcscmp);
|
|
wcstarinst->set_sequence_n_compare_function(dbstl_wcsncmp);
|
|
}
|
|
|
|
ResourceManager* ResourceManager::instance()
|
|
{
|
|
ResourceManager *pinst = NULL;
|
|
#ifdef HAVE_PTHREAD_TLS
|
|
// Initialize the tls key.
|
|
pthread_once(&once_control_, tls_init_once<ResourceManager>);
|
|
#endif
|
|
|
|
if ((pinst = TlsWrapper<ResourceManager>::get_tls_obj()) == NULL){
|
|
TlsWrapper<ResourceManager>::set_tls_obj(
|
|
pinst = new ResourceManager());
|
|
register_global_object(pinst);
|
|
set_global_callbacks();
|
|
}
|
|
return pinst;
|
|
}
|
|
|
|
int ResourceManager::open_cursor(DbCursorBase *dcbcsr,
|
|
Db *pdb, int flags)
|
|
{
|
|
u_int32_t oflags = 0;
|
|
int ret;
|
|
|
|
if (!pdb || !dcbcsr)
|
|
return 0;
|
|
|
|
csrset_t::iterator csitr;
|
|
Dbc* csr = NULL;
|
|
dcbcsr->set_owner_db(pdb);
|
|
|
|
DbTxn *ptxn = NULL;
|
|
DbTxn *ptxn2 = this->current_txn(pdb->get_env());
|
|
if (ptxn2) {
|
|
ptxn = ptxn2;
|
|
dcbcsr->set_owner_txn(ptxn);
|
|
}
|
|
|
|
if (pdb->get_env() != NULL){
|
|
ret = pdb->get_env()->get_open_flags(&oflags);
|
|
dbstl_assert(ret == 0);
|
|
}
|
|
|
|
// Call Dbc->cursor only if there is no active open cursor in the
|
|
// current thread, otherwise duplicate one from the existing cursor
|
|
// and use the locks already held in this thread.
|
|
//
|
|
csrset_t *pcsrset = NULL;
|
|
db_csr_map_t::iterator itrpcsrset = all_csrs_.find(pdb);
|
|
if (itrpcsrset == all_csrs_.end()) { // No such pair in current thread.
|
|
pcsrset = new csrset_t;
|
|
pair<db_csr_map_t::iterator, bool> insret0 =
|
|
all_csrs_.insert(make_pair(pdb, pcsrset));
|
|
assert(insret0.second);
|
|
} else
|
|
pcsrset = itrpcsrset->second;
|
|
|
|
assert(pcsrset != NULL);
|
|
if (pcsrset->size() == 0) {
|
|
newcursor:
|
|
BDBOP2(pdb->cursor(ptxn, &csr, flags), ret,
|
|
((csr != NULL ? csr->close() : 1),
|
|
this->abort_txn(pdb->get_env())));
|
|
} else {
|
|
// We have some open cursors, so try to dup from one. If we are
|
|
// in CDS mode, and trying to open a write cursor, we should
|
|
// duplicate from a write cursor.
|
|
csitr = pcsrset->begin();
|
|
Dbc *csr22 = (*csitr)->get_cursor();
|
|
assert(csr22 != NULL);
|
|
assert(!((oflags & DB_INIT_TXN) && (flags & DB_WRITECURSOR)));
|
|
// If opening a CDS write cursor, must find a write cursor
|
|
// to duplicate from.
|
|
if (((flags & DB_WRITECURSOR) != 0)) {
|
|
for (;csitr != pcsrset->end(); ++csitr) {
|
|
csr22 = (*csitr)->get_cursor();
|
|
if (((DBC*)csr22)->flags & DBC_WRITECURSOR) {
|
|
// No need to abortTxn on fail in CDS.
|
|
BDBOP2(csr22->dup(&csr, DB_POSITION),
|
|
ret, csr->close());
|
|
goto done;
|
|
}
|
|
}
|
|
goto newcursor; // No write cursor, create a new one.
|
|
|
|
} else if (((oflags & DB_INIT_TXN) == 0) ||
|
|
pdb->get_transactional() == 0) {
|
|
// We are opening a DS or CDS read cursor, or
|
|
// opening a cursor in
|
|
// a transactional environment from a database not
|
|
// transactionally created.
|
|
BDBOP2(csr22->dup(&csr, DB_POSITION), ret,
|
|
(csr->close(), this->abort_txn(pdb->get_env())));
|
|
goto done;
|
|
} else {
|
|
// We are opening a transactional cursor, duplicate
|
|
// from a transactional one.
|
|
// We don't remove (close) the non-transactional ones,
|
|
// they are in use.
|
|
// Hold the locks already held in this thread,
|
|
// so need DB_POSITION flag.
|
|
//
|
|
DbTxn *ptxn3 = NULL;
|
|
DbCursorBase *dcbcursor = NULL;
|
|
csrset_t::iterator itr3, itr4;
|
|
int got_rg = 0;
|
|
|
|
// Opening a cursor in a transactional environment
|
|
// with no transaction specified. This should not
|
|
// happen in the first place.
|
|
if (ptxn == NULL)
|
|
THROW(InvalidArgumentException, ("DbTxn*",
|
|
"Opening a cursor in a transactional environment but no transaction \
|
|
is started specified"));
|
|
// When we check that there must be a valid transaction
|
|
// handle ptxn when opening a cursor in a
|
|
// transactional environment, the following code
|
|
// to delete cursors with no transaction
|
|
// is not required and never reached,
|
|
// but we will leave it there.
|
|
for (;csitr != pcsrset->end();) {
|
|
dcbcursor = *csitr;
|
|
ptxn3 = dcbcursor->get_owner_txn();
|
|
if (ptxn3 == NULL) {
|
|
BDBOP(dcbcursor->close(), ret);
|
|
if (!got_rg){
|
|
got_rg++;
|
|
itr3 = csitr;
|
|
}
|
|
} else if (got_rg) {
|
|
got_rg = 0;
|
|
itr4 = csitr;
|
|
pcsrset->erase(itr3, itr4);
|
|
csitr = pcsrset->begin();
|
|
continue;
|
|
}
|
|
|
|
if (ptxn3 == ptxn) {
|
|
csr22 = dcbcursor->get_cursor();
|
|
BDBOP2(csr22->dup(&csr, DB_POSITION),
|
|
ret, (csr->close(), this->
|
|
abort_txn(pdb->get_env())));
|
|
goto done;
|
|
}
|
|
++csitr;
|
|
|
|
}
|
|
if (got_rg) {
|
|
pcsrset->erase(itr3, pcsrset->end());
|
|
got_rg = 0;
|
|
}
|
|
|
|
goto newcursor;
|
|
|
|
} // else oflags & DB_INIT_TXN
|
|
} // else pcsrset->size()
|
|
done:
|
|
// Insert into current thread's db-cursor map and txn_csrs_ map,
|
|
// for later duplication.
|
|
//
|
|
dcbcsr->set_cursor(csr);
|
|
this->add_cursor(pdb, dcbcsr);
|
|
return 0;
|
|
}
|
|
|
|
void ResourceManager::add_cursor(Db* dbp, DbCursorBase* dcbcsr)
|
|
{
|
|
if (!dbp || !dcbcsr)
|
|
return;
|
|
assert(dcbcsr->get_cursor() != NULL);
|
|
|
|
(all_csrs_[dbp])->insert(dcbcsr);
|
|
// Register to txncsrs_, we suppose current transaction is the context
|
|
// of this operation.
|
|
//
|
|
this->add_txn_cursor(dcbcsr, dbp->get_env());
|
|
}
|
|
|
|
// Close dbp's all open cursors opened in current thread, do not close
|
|
// those of dbp opened in other threads, Db::truncate requires dbp's
|
|
// all cursors of all threads should be closed, and it is user's duty
|
|
// to make sure other threads all close dbp's cursor, because if we
|
|
// close them here, it is also an error---multi-threaded access to the same
|
|
// Dbc* cursor should be serialized, we can't serialize with user code
|
|
// anyway.
|
|
//
|
|
size_t ResourceManager::close_db_cursors(Db* dbp1)
|
|
{
|
|
int ret;
|
|
Db* dbp;
|
|
DbTxn *ptxn, *ptxn2;
|
|
csrset_t *pcset_txn;
|
|
|
|
if (dbp1 == NULL)
|
|
return 0;
|
|
|
|
dbp = dbp1;
|
|
db_csr_map_t::iterator itr0;
|
|
csrset_t::iterator itr;
|
|
|
|
itr0 = all_csrs_.find(dbp1);
|
|
if (itr0 == all_csrs_.end())
|
|
return 0;
|
|
|
|
csrset_t *pcset = itr0->second;
|
|
|
|
pcset_txn = NULL;
|
|
ptxn2 = ptxn = NULL;
|
|
size_t txncsr_sz = txn_csrs_.size();
|
|
|
|
for (itr = pcset->begin(), ret = 0; itr != pcset->end();
|
|
++itr, ret++) {
|
|
|
|
BDBOP((*itr)->close(), ret);
|
|
if (txncsr_sz > 0) {
|
|
if (pcset_txn == NULL || ptxn !=
|
|
(ptxn2 = (*itr)->get_owner_txn())) {
|
|
ptxn = ptxn2 ? ptxn2 : (*itr)->get_owner_txn();
|
|
if (ptxn != NULL)
|
|
pcset_txn = txn_csrs_[ptxn];
|
|
}
|
|
if (pcset_txn)
|
|
pcset_txn->erase(*itr);
|
|
}
|
|
|
|
}
|
|
|
|
// Don't delete the pcset or itr0 because this dbp1 may be used
|
|
// by other containers in this thread.
|
|
pcset->clear();
|
|
// We don't delete the DbCursorBase object, it is still
|
|
// referenced by others.
|
|
return ret;
|
|
}
|
|
|
|
// Close the cursor of csr and remove the entry containing csr from
|
|
// txn_csrs_ and all_csrs_.
|
|
int ResourceManager::remove_cursor(DbCursorBase*csr,
|
|
bool remove_from_txncsrs)
|
|
{
|
|
int ret;
|
|
|
|
if (csr == NULL)
|
|
return 0;
|
|
BDBOP(csr->close(), ret);
|
|
|
|
if (remove_from_txncsrs) {
|
|
DbTxn *ptxn = csr->get_owner_txn();
|
|
if (ptxn != NULL) {
|
|
txncsr_t::iterator itr = txn_csrs_.find(ptxn);
|
|
if (itr != txn_csrs_.end())
|
|
itr->second->erase(csr);
|
|
}
|
|
}
|
|
|
|
Db *pdb = csr->get_owner_db();
|
|
if (pdb != NULL)
|
|
all_csrs_[pdb]->erase(csr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Remove cursors opened in transaction txn's context, should be called before
|
|
* commiting/aborting a transaction.
|
|
* Note that here we should remove the cursor from all_csrs_ too,
|
|
* by calling remove_cursor() function.
|
|
*/
|
|
void ResourceManager::remove_txn_cursor(DbTxn* txn)
|
|
{
|
|
int ret;
|
|
|
|
if (!txn)
|
|
return;
|
|
|
|
txncsr_t::iterator itr0;
|
|
csrset_t::iterator itr;
|
|
itr0 = txn_csrs_.find(txn);
|
|
if (itr0 == txn_csrs_.end())
|
|
return; // No cursor opened in this txn.
|
|
|
|
csrset_t *pcsrs = itr0->second;
|
|
DbCursorBase *csr;
|
|
|
|
// Remove(close and remove from csr registry) cursors
|
|
// opened in the transaction txn's context.
|
|
for (itr = pcsrs->begin(); itr != pcsrs->end(); ++itr) {
|
|
// This cursor should be closed now and removed
|
|
// from csr registry.
|
|
csr = *itr;
|
|
BDBOP(csr->close(), ret);
|
|
all_csrs_[csr->get_owner_db()]->erase(csr);
|
|
}
|
|
|
|
delete pcsrs;
|
|
// Erase csrs belonging to txn.
|
|
txn_csrs_.erase(itr0);
|
|
}
|
|
|
|
// Begin a new transaction from the specified environment env.
|
|
// When outtxn is non-zero, it supports nested txn,
|
|
// so the new transaction is started as a child transaction of the
|
|
// current one, and we push it into env1's transaction stack;
|
|
// Otherwise, we are starting an internal transaction for autocommit,
|
|
// no new transaction will be started, but current transaction's reference
|
|
// count will be incremented.
|
|
DbTxn* ResourceManager::begin_txn(u_int32_t flags, DbEnv*env1, int outtxn)
|
|
{
|
|
DbEnv *env = env1;
|
|
DbTxn *ptxn, *txn = NULL;
|
|
int ret;
|
|
|
|
if (!env1)
|
|
return NULL;
|
|
|
|
assert(env_txns_.count(env1) > 0);
|
|
|
|
stack<DbTxn*>&stk = env_txns_[env1];
|
|
|
|
// Not an outside transaction, so if there is transaction in stack,
|
|
// use it and increment its reference count.
|
|
if (outtxn == 0) {
|
|
// We have a transaction in stack, increment its reference
|
|
// count.
|
|
if (stk.size() > 0) {
|
|
txn = stk.top();
|
|
// The txn was created externally, now we internally
|
|
// use it, so the reference count is 2.
|
|
map<DbTxn *, size_t>::iterator itr12;
|
|
if ((itr12 = txn_count_.find(txn)) == txn_count_.end())
|
|
txn_count_.insert(make_pair(txn, 2u));
|
|
else
|
|
txn_count_[txn]++;
|
|
} else {
|
|
// Empty stack, create a transaction and set reference count to 1.
|
|
BDBOP(env->txn_begin(NULL, &txn, flags), ret);
|
|
stk.push(txn);
|
|
txn_count_[txn] = 1;// the first to use it
|
|
txn_csrs_.insert(make_pair(txn, new csrset_t()));
|
|
}
|
|
|
|
} else { // Creating a transaction by user, used outside of dbstl.
|
|
ptxn = stk.size() > 0 ? stk.top() : NULL;
|
|
|
|
BDBOP(env->txn_begin(ptxn, &txn, flags), ret);
|
|
|
|
// txn now is the current txn
|
|
stk.push(txn);
|
|
txn_csrs_.insert(make_pair(txn, new csrset_t()));
|
|
}
|
|
|
|
return txn;
|
|
}
|
|
|
|
void ResourceManager::commit_txn(DbEnv *env, u_int32_t flags)
|
|
{
|
|
int ret;
|
|
DbTxn *ptxn;
|
|
|
|
if (!env)
|
|
return;
|
|
|
|
assert(env_txns_.count(env) > 0);
|
|
stack<DbTxn*> &stk = env_txns_[env];
|
|
ptxn = stk.top();
|
|
assert(ptxn != NULL);
|
|
size_t txncnt = txn_count_[ptxn];
|
|
|
|
if (txncnt > 1) // used internally
|
|
txn_count_[ptxn]--;
|
|
else {
|
|
txn_count_.erase(ptxn);
|
|
this->remove_txn_cursor(ptxn);
|
|
stk.pop();
|
|
BDBOP(ptxn->commit(flags), ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void ResourceManager::commit_txn(DbEnv *env, DbTxn *txn, u_int32_t flags)
|
|
{
|
|
DbTxn *ptxn = NULL;
|
|
int ret;
|
|
|
|
if (env == NULL || txn == NULL)
|
|
return;
|
|
|
|
stack<DbTxn*> &stk = env_txns_[env];
|
|
while (stk.size() > 0 && (ptxn = stk.top()) != txn) {
|
|
stk.pop();
|
|
txn_count_.erase(ptxn);// may be in the txn_count_ map
|
|
this->remove_txn_cursor(ptxn);
|
|
// Child txns could be committed by parent txn, but c++ API
|
|
// can't delete the new'ed child txns when committing the
|
|
// parent txn, so have to commit them explicitly.
|
|
ptxn->commit(flags);
|
|
}
|
|
if (stk.size() == 0)
|
|
THROW(InvalidArgumentException, (
|
|
"No such transaction created by dbstl"));
|
|
else {
|
|
stk.pop();
|
|
txn_count_.erase(txn);// may be in the txn_count_ map
|
|
this->remove_txn_cursor(txn);
|
|
if (ptxn){
|
|
BDBOP(ptxn->commit(flags), ret);
|
|
} else // could never happen
|
|
THROW(InvalidArgumentException, (
|
|
"No such transaction created by dbstl"));
|
|
|
|
}
|
|
}
|
|
|
|
void ResourceManager::abort_txn(DbEnv *env, DbTxn *txn)
|
|
{
|
|
int ret;
|
|
DbTxn *ptxn = NULL;
|
|
u_int32_t oflags;
|
|
|
|
if (env == NULL || txn == NULL)
|
|
return;
|
|
|
|
BDBOP (env->get_open_flags(&oflags), ret);
|
|
stack<DbTxn*> &stk = env_txns_[env];
|
|
while (stk.size() > 0 && (ptxn = stk.top()) != txn) {
|
|
txn_count_.erase(ptxn);// may be in the txn_count_ map
|
|
this->remove_txn_cursor(ptxn);
|
|
stk.pop();
|
|
// Child txns could be aborted by parent txn, but c++ API
|
|
// can't delete the new'ed child txns when aborting the
|
|
// parent txn, so have to abort them explicitly.
|
|
ptxn->abort();
|
|
}
|
|
if (stk.size() == 0)
|
|
THROW(InvalidArgumentException, (
|
|
"No such transaction created by dbstl"));
|
|
else {
|
|
stk.pop();
|
|
txn_count_.erase(txn);// may be in the txn_count_ map
|
|
this->remove_txn_cursor(txn);
|
|
if (ptxn){
|
|
if ((oflags & DB_INIT_CDB) == 0)
|
|
BDBOP(ptxn->abort(), ret);
|
|
} else // could never happen
|
|
THROW(InvalidArgumentException, (
|
|
"No such transaction created by dbstl"));
|
|
|
|
}
|
|
}
|
|
|
|
// Abort current txn, close/remove its cursors and reference count.
|
|
void ResourceManager::abort_txn(DbEnv*env)
|
|
{
|
|
int ret;
|
|
DbTxn *ptxn;
|
|
u_int32_t oflags;
|
|
|
|
if (!env)
|
|
return;
|
|
env_txns_t::iterator itr(env_txns_.find(env));
|
|
if (itr == env_txns_.end())
|
|
return;
|
|
|
|
stack<DbTxn*> &stk = itr->second;
|
|
if (stk.size() == 0)
|
|
return;
|
|
ptxn = stk.top();
|
|
if (ptxn == NULL)
|
|
return;
|
|
this->remove_txn_cursor(ptxn);
|
|
BDBOP (env->get_open_flags(&oflags), ret);
|
|
|
|
// Transactions handles created via cdsgroup_begin can not be aborted
|
|
// because they are not really transactions, they just borrow the
|
|
// DB_TXN structure to store a locker id.
|
|
if ((oflags & DB_INIT_CDB) == 0)
|
|
BDBOP(ptxn->abort(), ret);
|
|
txn_count_.erase(ptxn);
|
|
stk.pop();
|
|
}
|
|
|
|
DbTxn* ResourceManager::set_current_txn_handle(DbEnv *env, DbTxn *newtxn)
|
|
{
|
|
assert(env_txns_.count(env) > 0);
|
|
stack<DbTxn*> &stk = env_txns_[env];
|
|
DbTxn *ptxn = stk.top();
|
|
stk.pop();
|
|
stk.push(newtxn);
|
|
return ptxn;
|
|
}
|
|
|
|
void ResourceManager::add_txn_cursor(DbCursorBase *dcbcsr, DbEnv *env)
|
|
{
|
|
if (!env || !dcbcsr)
|
|
return;
|
|
|
|
DbTxn *ptxn = this->current_txn(env);
|
|
if (ptxn == NULL)
|
|
return;
|
|
|
|
u_int32_t oflags;
|
|
int ret;
|
|
|
|
BDBOP(env->get_open_flags(&oflags), ret);
|
|
if ((oflags & DB_INIT_TXN) == 0)
|
|
return;
|
|
|
|
txncsr_t::iterator itr;
|
|
csrset_t *pset;
|
|
|
|
itr = txn_csrs_.find(ptxn);
|
|
pair<txncsr_t::iterator, bool> insret;
|
|
|
|
if (itr == txn_csrs_.end()) {
|
|
insret = txn_csrs_.insert(make_pair(ptxn, new csrset_t()));
|
|
assert(insret.second);
|
|
itr = insret.first;
|
|
}
|
|
pset = itr->second;
|
|
pset->insert(dcbcsr);
|
|
}
|
|
|
|
void ResourceManager::register_db(Db*pdb1)
|
|
{
|
|
if (!pdb1)
|
|
return;
|
|
global_lock(mtx_handle_);
|
|
if (open_dbs_.count(pdb1) == 0)
|
|
open_dbs_.insert(make_pair(pdb1, 1u));
|
|
else
|
|
open_dbs_[pdb1]++;
|
|
global_unlock(mtx_handle_);
|
|
csrset_t *pcsrset = new csrset_t();
|
|
pair<db_csr_map_t::iterator, bool> insret = all_csrs_.insert(
|
|
make_pair(pdb1, pcsrset));
|
|
if (!insret.second)
|
|
delete pcsrset;
|
|
|
|
}
|
|
|
|
void ResourceManager::register_db_env(DbEnv*env1)
|
|
{
|
|
u_int32_t oflags = 0;
|
|
DbTxn *ptxn = NULL;
|
|
int ret;
|
|
|
|
if (!env1)
|
|
return;
|
|
|
|
stack<DbTxn*> stk;
|
|
BDBOP(env1->get_open_flags(&oflags), ret);
|
|
|
|
if (oflags & DB_INIT_CDB) {
|
|
env1->cdsgroup_begin(&ptxn);
|
|
stk.push(ptxn);
|
|
}
|
|
|
|
env_txns_.insert(make_pair(env1, stk));
|
|
|
|
global_lock(mtx_handle_);
|
|
if (open_envs_.count(env1) == 0)
|
|
open_envs_.insert(make_pair(env1, 1u));
|
|
else
|
|
open_envs_[env1]++;
|
|
global_unlock(mtx_handle_);
|
|
}
|
|
|
|
// Delete registered DbstlGlobalInnerObject objects.
|
|
void ResourceManager::global_exit()
|
|
{
|
|
set<DbstlGlobalInnerObject *>::iterator itr;
|
|
global_lock(mtx_globj_);
|
|
for (itr = glob_objs_.begin(); itr != glob_objs_.end(); ++itr)
|
|
delete *itr;
|
|
global_unlock(mtx_globj_);
|
|
|
|
mtx_env_->mutex_free(mtx_globj_);
|
|
mtx_env_->mutex_free(mtx_handle_);
|
|
delete mtx_env_;
|
|
}
|
|
|
|
void ResourceManager::register_global_object(DbstlGlobalInnerObject *gio)
|
|
{
|
|
global_lock(mtx_globj_);
|
|
glob_objs_.insert(gio);
|
|
global_unlock(mtx_globj_);
|
|
}
|
|
|
|
END_NS
|