// ********************************************************************** // // Copyright (c) 2003-2016 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** #include #include #include #include #include #include #include using namespace std; using namespace Freeze; using namespace Ice; namespace { // // Must be in sync with Parser.cpp // const int supports = 0; const int mandatory = 1; const int required = 2; const int never = 3; } // // createEvictor functions // Freeze::TransactionalEvictorPtr Freeze::createTransactionalEvictor(const ObjectAdapterPtr& adapter, const string& envName, const string& filename, const FacetTypeMap& facetTypes, const ServantInitializerPtr& initializer, const vector& indices, bool createDb) { return new TransactionalEvictorI(adapter, envName, 0, filename, facetTypes, initializer, indices, createDb); } TransactionalEvictorPtr Freeze::createTransactionalEvictor(const ObjectAdapterPtr& adapter, const string& envName, DbEnv& dbEnv, const string& filename, const FacetTypeMap& facetTypes, const ServantInitializerPtr& initializer, const vector& indices, bool createDb) { return new TransactionalEvictorI(adapter, envName, &dbEnv, filename, facetTypes, initializer, indices, createDb); } // // TransactionalEvictorI // Freeze::TransactionalEvictorI::TransactionalEvictorI(const ObjectAdapterPtr& adapter, const string& envName, DbEnv* dbEnv, const string& filename, const FacetTypeMap& facetTypes, const ServantInitializerPtr& initializer, const vector& indices, bool createDb) : EvictorI(adapter, envName, dbEnv, filename, facetTypes, initializer, indices, createDb), _currentEvictorSize(0) { class DispatchInterceptorAdapter : public Ice::DispatchInterceptor { public: DispatchInterceptorAdapter(const TransactionalEvictorIPtr& evictor) : _evictor(evictor) { } virtual DispatchStatus dispatch(Request& request) { return _evictor->dispatch(request); } private: TransactionalEvictorIPtr _evictor; }; _interceptor = new DispatchInterceptorAdapter(this); string propertyPrefix = string("Freeze.Evictor.") + envName + '.' + _filename; _rollbackOnUserException = _communicator->getProperties()-> getPropertyAsIntWithDefault(propertyPrefix + ".RollbackOnUserException", 0) > 0; } TransactionPtr Freeze::TransactionalEvictorI::getCurrentTransaction() const { DeactivateController::Guard deactivateGuard(_deactivateController); TransactionalEvictorContextPtr ctx = _dbEnv->getCurrent(); if(ctx == 0) { return 0; } else { return ctx->transaction(); } } void Freeze::TransactionalEvictorI::setCurrentTransaction(const TransactionPtr& tx) { DeactivateController::Guard deactivateGuard(_deactivateController); _dbEnv->setCurrentTransaction(tx); } ObjectPrx Freeze::TransactionalEvictorI::addFacet(const ObjectPtr& servant, const Identity& ident, const string& facet) { checkIdentity(ident); checkServant(servant); DeactivateController::Guard deactivateGuard(_deactivateController); ObjectStore* store = findStore(facet, _createDb); if(store == 0) { throw NotFoundException(__FILE__, __LINE__, "addFacet: could not open database for facet '" + facet + "'"); } Ice::Long currentTime = 0; if(store->keepStats()) { currentTime = IceUtil::Time::now(IceUtil::Time::Monotonic).toMilliSeconds(); } Statistics stats = { currentTime }; ObjectRecord rec = { servant, stats }; TransactionIPtr tx = beforeQuery(); if(store->keepStats()) { updateStats(rec.stats, currentTime); } if(!store->insert(ident, rec, tx)) { AlreadyRegisteredException ex(__FILE__, __LINE__); ex.kindOfObject = "servant"; ex.id = _communicator->identityToString(ident); if(!facet.empty()) { ex.id += " -f " + IceUtilInternal::escapeString(facet, ""); } throw ex; } ObjectPrx obj = _adapter->createProxy(ident); if(!facet.empty()) { obj = obj->ice_facet(facet); } return obj; } ObjectPtr Freeze::TransactionalEvictorI::removeFacet(const Identity& ident, const string& facet) { checkIdentity(ident); DeactivateController::Guard deactivateGuard(_deactivateController); ObjectPtr servant = 0; bool removed = false; ObjectStore* store = findStore(facet, false); if(store != 0) { TransactionalEvictorContextPtr ctx = _dbEnv->getCurrent(); TransactionIPtr tx = 0; if(ctx != 0) { tx = ctx->transaction(); if(tx == 0) { throw DatabaseException(__FILE__, __LINE__, "inactive transaction"); } } removed = store->remove(ident, tx); if(removed) { if(ctx != 0) { // // Remove from cache when transaction commits // servant = ctx->servantRemoved(ident, store); } else { // // Remove from cache immediately // servant = evict(ident, store); } } } if(!removed) { NotRegisteredException ex(__FILE__, __LINE__); ex.kindOfObject = "servant"; ex.id = _communicator->identityToString(ident); if(!facet.empty()) { ex.id += " -f " + IceUtilInternal::escapeString(facet, ""); } throw ex; } if(_trace >= 1) { Trace out(_communicator->getLogger(), "Freeze.Evictor"); out << "removed object \"" << _communicator->identityToString(ident) << "\""; if(!facet.empty()) { out << " with facet \"" << facet << "\""; } out << " from Db \"" << _filename << "\""; } return servant; } bool Freeze::TransactionalEvictorI::hasFacet(const Identity& ident, const string& facet) { DeactivateController::Guard deactivateGuard(_deactivateController); checkIdentity(ident); ObjectStore* store = findStore(facet, false); if(store == 0) { return false; } TransactionIPtr tx = beforeQuery(); if(tx == 0) { TransactionalEvictorElementPtr element = store->getIfPinned(ident); if(element != 0) { return true; } return store->dbHasObject(ident, 0); } else { return store->dbHasObject(ident, tx); } } bool Freeze::TransactionalEvictorI::hasAnotherFacet(const Identity& ident, const string& facet) { DeactivateController::Guard deactivateGuard(_deactivateController); // // If the object exists in another store, throw FacetNotExistException // instead of returning 0 (== ObjectNotExistException) // StoreMap storeMapCopy; { Lock sync(*this); storeMapCopy = _storeMap; } TransactionIPtr tx = beforeQuery(); for(StoreMap::iterator p = storeMapCopy.begin(); p != storeMapCopy.end(); ++p) { // // Do not check again the given facet // if((*p).first != facet) { ObjectStore* store = (*p).second; if(tx == 0 && store->getIfPinned(ident) != 0) { return true; } if(store->dbHasObject(ident, tx)) { return true; } } } return false; } ObjectPtr Freeze::TransactionalEvictorI::locateImpl(const Current&, LocalObjectPtr&) { return _interceptor; } void Freeze::TransactionalEvictorI::finished(const Current&, const ObjectPtr&, const LocalObjectPtr&) { // // Nothing to do // } DispatchStatus Freeze::TransactionalEvictorI::dispatch(Request& request) { class CtxHolder { public: #ifndef NDEBUG CtxHolder(bool ownCtx, const TransactionalEvictorContextPtr& ctx, const SharedDbEnvPtr& dbEnv) : _ownCtx(ownCtx), _ctx(ctx), _dbEnv(dbEnv) #else CtxHolder(bool ownCtx, const TransactionalEvictorContextPtr& ctx) : _ownCtx(ownCtx), _ctx(ctx) #endif { } ~CtxHolder() ICE_NOEXCEPT_FALSE { if(_ownCtx) { try { _ctx->commit(); } catch(...) { assert(_dbEnv->getCurrent() == 0); throw; } assert(_dbEnv->getCurrent() == 0); } } private: const bool _ownCtx; const TransactionalEvictorContextPtr _ctx; #ifndef NDEBUG const SharedDbEnvPtr& _dbEnv; #endif }; DeactivateController::Guard deactivateGuard(_deactivateController); const Current& current = request.getCurrent(); ObjectStore* store = findStore(current.facet, false); if(store == 0) { servantNotFound(__FILE__, __LINE__, current); } TransactionalEvictorContextPtr ctx = _dbEnv->getCurrent(); ObjectPtr sample = store->sampleServant(); ObjectPtr cachedServant = 0; TransactionalEvictorContext::ServantHolder servantHolder; if(sample == 0) { if(ctx != 0) { try { servantHolder.init(ctx, current, store); } catch(const DeadlockException& dx) { assert(dx.tx == ctx->transaction()); ctx->deadlockException(); throw TransactionalEvictorDeadlockException(__FILE__, __LINE__, dx.tx); } sample = servantHolder.servant(); } else { // // find / load read-only servant // cachedServant = loadCachedServant(current.id, store); if(cachedServant == 0) { servantNotFound(__FILE__, __LINE__, current); } sample = cachedServant; } } assert(sample != 0); int operationAttributes = sample->ice_operationAttributes(current.operation); if(operationAttributes < 0) { throw OperationNotExistException(__FILE__, __LINE__); } bool readOnly = (operationAttributes & 0x1) == 0; int txMode = (operationAttributes & 0x6) >> 1; bool ownCtx = false; // // Establish the proper context // switch(txMode) { case never: { assert(readOnly); if(ctx != 0) { throw DatabaseException(__FILE__, __LINE__, "transaction rejected by 'never' metadata"); } break; } case supports: { assert(readOnly); break; } case mandatory: { if(ctx == 0) { throw DatabaseException(__FILE__, __LINE__, "operation with a mandatory transaction"); } break; } case required: { if(ctx == 0) { ownCtx = true; } break; } default: { assert(0); throw OperationNotExistException(__FILE__, __LINE__); } } if(ctx == 0 && !ownCtx) { // // Read-only dispatch // assert(readOnly); if(cachedServant == 0) { cachedServant = loadCachedServant(current.id, store); if(cachedServant == 0) { servantNotFound(__FILE__, __LINE__, current); } } return cachedServant->ice_dispatch(request); } else { // // Create a new transaction; retry on DeadlockException // bool tryAgain = false; do { TransactionPtr tx; try { if(ownCtx) { ctx = _dbEnv->createCurrent(); } #ifndef NDEBUG CtxHolder ctxHolder(ownCtx, ctx, _dbEnv); #else CtxHolder ctxHolder(ownCtx, ctx); #endif tx = ctx->transaction(); try { TransactionalEvictorContext::ServantHolder sh; if(servantHolder.initialized()) { // // Adopt it // sh.adopt(servantHolder); } else { sh.init(ctx, current, store); } if(sh.servant() == 0) { servantNotFound(__FILE__, __LINE__, current); } if(!readOnly) { sh.markReadWrite(); } try { DispatchStatus dispatchStatus = sh.servant()->ice_dispatch(request, ctx); if(dispatchStatus == DispatchUserException && _rollbackOnUserException) { ctx->rollback(); } if(dispatchStatus == DispatchAsync) { // // May throw DeadlockException or TransactionalEvictorDeadlockException // ctx->checkDeadlockException(); if(ctx->clearUserException() && _rollbackOnUserException) { ctx->rollback(); } } return dispatchStatus; } catch(...) { // // Important: this rollback() ensures that servant holder destructor won't perform // any database operation, and hence will not throw. // ctx->rollback(); throw; } // // servant holder destructor runs here and may throw (if !rolled back) // } catch(const DeadlockException& dx) { if(dx.tx == tx) { ctx->deadlockException(); } throw; } catch(...) { if(ownCtx) { ctx->rollback(); } throw; } // // commit occurs here (when ownCtx) // } catch(const DeadlockException& dx) { if(ownCtx && dx.tx == tx) { tryAgain = true; } else { throw TransactionalEvictorDeadlockException(__FILE__, __LINE__, dx.tx); } } catch(const TransactionalEvictorDeadlockException& dx) { if(ownCtx && dx.tx == tx) { tryAgain = true; } else { throw; } } } while(tryAgain); } // // Can't be reached // assert(0); throw OperationNotExistException(__FILE__, __LINE__); } void Freeze::TransactionalEvictorI::deactivate(const string&) { if(_deactivateController.deactivate()) { { Lock sync(*this); // // Set the evictor size to zero, meaning that we will evict // everything possible. // _evictorSize = 0; evict(); } // // Break cycle // _interceptor = 0; try { closeDbEnv(); } catch(...) { _deactivateController.deactivationComplete(); throw; } _deactivateController.deactivationComplete(); } } Freeze::TransactionalEvictorI::~TransactionalEvictorI() { // // Because of the cycle with _interceptor, this can only run if deactivated() // } Freeze::TransactionIPtr Freeze::TransactionalEvictorI::beforeQuery() { TransactionalEvictorContextPtr ctx = _dbEnv->getCurrent(); TransactionIPtr tx = 0; if(ctx != 0) { tx = ctx->transaction(); if(tx == 0) { throw DatabaseException(__FILE__, __LINE__,"inactive transaction"); } } return tx; } void Freeze::TransactionalEvictorI::evict() { // // Must be called with this locked // while(_currentEvictorSize > _evictorSize) { // // Evict, no matter what! // evict(*_evictorList.rbegin()); } } ObjectPtr Freeze::TransactionalEvictorI::loadCachedServant(const Identity& ident, ObjectStore* store) { for(;;) { TransactionalEvictorElementPtr element = store->pin(ident); if(element == 0) { return 0; } Lock sync(*this); if(element->stale()) { // // try again // continue; } fixEvictPosition(element); // // if _evictorSize is 0, I may evict myself ... no big deal // evict(); return element->servant(); } } ObjectPtr Freeze::TransactionalEvictorI::evict(const Identity& ident, ObjectStore* store) { // // Important: we can't wait for the DB (even indirectly) with 'this' locked // TransactionalEvictorElementPtr element = store->getIfPinned(ident, true); if(element != 0) { Lock sync(*this); if(!element->_stale) { evict(element); return element->servant(); } } return 0; } void Freeze::TransactionalEvictorI::evict(const TransactionalEvictorElementPtr& element) { // // Must be called with this locked! // assert(!element->_stale); element->_stale = true; element->_store.unpin(element->_cachePosition); if(element->_inEvictor) { element->_inEvictor = false; _evictorList.erase(element->_evictPosition); _currentEvictorSize--; } } void Freeze::TransactionalEvictorI::fixEvictPosition(const TransactionalEvictorElementPtr& element) { // // Must be called with this locked! // assert(!element->_stale); if(element->_inEvictor) { _evictorList.erase(element->_evictPosition); } else { // // New object // _currentEvictorSize++; element->_inEvictor = true; } _evictorList.push_front(element); element->_evictPosition = _evictorList.begin(); } void Freeze::TransactionalEvictorI::servantNotFound(const char* file, int line, const Current& current) { if(_trace >= 2) { Trace out(_communicator->getLogger(), "Freeze.Evictor"); out << "could not find \"" << _communicator->identityToString(current.id) << "\" with facet \"" << current.facet + "\""; } if(hasAnotherFacet(current.id, current.facet)) { throw FacetNotExistException(file, line, current.id, current.facet, current.operation); } else { throw ObjectNotExistException(file, line, current.id, current.facet, current.operation); } } Freeze::TransactionalEvictorElement::TransactionalEvictorElement(ObjectRecord& r, ObjectStore& s) : _servant(r.servant), _store(s), _stale(true), _inEvictor(false) { } Freeze::TransactionalEvictorElement::~TransactionalEvictorElement() { } void Freeze::TransactionalEvictorElement::init(ObjectStore::Position p) { _stale = false; _cachePosition = p; }