//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed 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. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "MMFilesTransactionCollection.h" #include "Basics/Exceptions.h" #include "Logger/Logger.h" #include "MMFiles/MMFilesDocumentOperation.h" #include "StorageEngine/TransactionState.h" #include "Utils/TransactionMethods.h" #include "Utils/TransactionHints.h" #include "VocBase/LogicalCollection.h" #include "VocBase/modes.h" using namespace arangodb; MMFilesTransactionCollection::MMFilesTransactionCollection(TransactionState* trx, TRI_voc_cid_t cid, AccessMode::Type accessType, int nestingLevel) : TransactionCollection(trx, cid), _operations(nullptr), _originalRevision(0), _nestingLevel(nestingLevel), _compactionLocked(false), _waitForSync(false), _accessType(accessType), _lockType(AccessMode::Type::NONE) {} MMFilesTransactionCollection::~MMFilesTransactionCollection() {} /// @brief request a main-level lock for a collection int MMFilesTransactionCollection::lock() { return lock(_accessType, 0); } /// @brief request a lock for a collection int MMFilesTransactionCollection::lock(AccessMode::Type accessType, int nestingLevel) { if (isWrite(accessType) && !isWrite(_accessType)) { // wrong lock type return TRI_ERROR_INTERNAL; } if (isLocked()) { // already locked return TRI_ERROR_NO_ERROR; } return doLock(accessType, nestingLevel); } /// @brief request an unlock for a collection int MMFilesTransactionCollection::unlock(AccessMode::Type accessType, int nestingLevel) { if (isWrite(accessType) && !isWrite(_accessType)) { // wrong lock type: write-unlock requested but collection is read-only return TRI_ERROR_INTERNAL; } if (!isLocked()) { // already unlocked return TRI_ERROR_NO_ERROR; } return doUnlock(accessType, nestingLevel); } /// @brief check if a collection is locked in a specific mode in a transaction bool MMFilesTransactionCollection::isLocked(AccessMode::Type accessType, int nestingLevel) const { if (isWrite(accessType) && !isWrite(_accessType)) { // wrong lock type LOG_TOPIC(WARN, arangodb::Logger::FIXME) << "logic error. checking wrong lock type"; return false; } return isLocked(); } /// @brief check whether a collection is locked at all bool MMFilesTransactionCollection::isLocked() const { return (_lockType != AccessMode::Type::NONE); } /// @brief whether or not any write operations for the collection happened bool MMFilesTransactionCollection::hasOperations() const { return (_operations != nullptr && !_operations->empty()); } void MMFilesTransactionCollection::addOperation(MMFilesDocumentOperation* operation) { if (_operations == nullptr) { _operations = new std::vector; _operations->reserve(16); } _operations->push_back(operation); } void MMFilesTransactionCollection::freeOperations(TransactionMethods* activeTrx, bool mustRollback) { if (!hasOperations()) { return; } bool const isSingleOperationTransaction = _transaction->_hints.has(TransactionHints::Hint::SINGLE_OPERATION); if (mustRollback) { // revert all operations for (auto it = _operations->rbegin(); it != _operations->rend(); ++it) { MMFilesDocumentOperation* op = (*it); try { op->revert(activeTrx); } catch (...) { } delete op; } } else { // no rollback. simply delete all operations for (auto it = _operations->rbegin(); it != _operations->rend(); ++it) { delete (*it); } } if (mustRollback) { _collection->setRevision(_originalRevision, true); } else if (!_collection->isVolatile() && !isSingleOperationTransaction) { // only count logfileEntries if the collection is durable _collection->increaseUncollectedLogfileEntries(_operations->size()); } delete _operations; _operations = nullptr; } bool MMFilesTransactionCollection::canAccess(AccessMode::Type accessType) const { if (_collection == nullptr) { if (!_transaction->_hints.has(TransactionHints::Hint::LOCK_NEVER) || !_transaction->_hints.has(TransactionHints::Hint::NO_USAGE_LOCK)) { // not opened. probably a mistake made by the caller return false; } // ok } // check if access type matches if (AccessMode::isWriteOrExclusive(accessType) && !AccessMode::isWriteOrExclusive(_accessType)) { // type doesn't match. probably also a mistake by the caller return false; } return true; } int MMFilesTransactionCollection::updateUsage(AccessMode::Type accessType, int nestingLevel) { if (AccessMode::isWriteOrExclusive(accessType) && !AccessMode::isWriteOrExclusive(_accessType)) { if (nestingLevel > 0) { // trying to write access a collection that is only marked with // read-access return TRI_ERROR_TRANSACTION_UNREGISTERED_COLLECTION; } TRI_ASSERT(nestingLevel == 0); // upgrade collection type to write-access _accessType = accessType; } if (nestingLevel < _nestingLevel) { _nestingLevel = nestingLevel; } // all correct return TRI_ERROR_NO_ERROR; } int MMFilesTransactionCollection::use(int nestingLevel) { if (_nestingLevel != nestingLevel) { // only process our own collections return TRI_ERROR_NO_ERROR; } if (_collection == nullptr) { // open the collection if (!_transaction->_hints.has(TransactionHints::Hint::LOCK_NEVER) && !_transaction->_hints.has(TransactionHints::Hint::NO_USAGE_LOCK)) { // use and usage-lock TRI_vocbase_col_status_e status; LOG_TRX(_transaction, nestingLevel) << "using collection " << _cid; _collection = _transaction->_vocbase->useCollection(_cid, status); } else { // use without usage-lock (lock already set externally) _collection = _transaction->_vocbase->lookupCollection(_cid); if (_collection == nullptr) { return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND; } } if (_collection == nullptr) { // something went wrong int res = TRI_errno(); if (res == TRI_ERROR_NO_ERROR) { // must return an error res = TRI_ERROR_INTERNAL; } return res; } if (AccessMode::isWriteOrExclusive(_accessType) && TRI_GetOperationModeServer() == TRI_VOCBASE_MODE_NO_CREATE && !LogicalCollection::IsSystemName(_collection->name())) { return TRI_ERROR_ARANGO_READ_ONLY; } // store the waitForSync property _waitForSync = _collection->waitForSync(); } TRI_ASSERT(_collection != nullptr); if (nestingLevel == 0 && AccessMode::isWriteOrExclusive(_accessType)) { // read-lock the compaction lock if (!_transaction->_hints.has(TransactionHints::Hint::NO_COMPACTION_LOCK)) { if (!_compactionLocked) { _collection->preventCompaction(); _compactionLocked = true; } } } if (AccessMode::isWriteOrExclusive(_accessType) && _originalRevision == 0) { // store original revision at transaction start _originalRevision = _collection->revision(); } bool shouldLock = _transaction->_hints.has(TransactionHints::Hint::LOCK_ENTIRELY); if (!shouldLock) { shouldLock = (AccessMode::isWriteOrExclusive(_accessType) && !_transaction->_hints.has(TransactionHints::Hint::SINGLE_OPERATION)); } if (shouldLock && !isLocked()) { // r/w lock the collection int res = doLock(_accessType, nestingLevel); if (res != TRI_ERROR_NO_ERROR) { return res; } } return TRI_ERROR_NO_ERROR; } void MMFilesTransactionCollection::unuse(int nestingLevel) { if (isLocked() && (nestingLevel == 0 || _nestingLevel == nestingLevel)) { // unlock our own r/w locks doUnlock(_accessType, nestingLevel); } // the top level transaction releases all collections if (nestingLevel == 0 && _collection != nullptr) { if (!_transaction->_hints.has(TransactionHints::Hint::NO_COMPACTION_LOCK)) { if (AccessMode::isWriteOrExclusive(_accessType) && _compactionLocked) { // read-unlock the compaction lock _collection->allowCompaction(); _compactionLocked = false; } } _lockType = AccessMode::Type::NONE; } } void MMFilesTransactionCollection::release() { // the top level transaction releases all collections if (_collection != nullptr) { // unuse collection, remove usage-lock LOG_TRX(_transaction, 0) << "unusing collection " << _cid; _transaction->_vocbase->releaseCollection(_collection); _collection = nullptr; } } /// @brief lock a collection int MMFilesTransactionCollection::doLock(AccessMode::Type type, int nestingLevel) { if (_transaction->_hints.has(TransactionHints::Hint::LOCK_NEVER)) { // never lock return TRI_ERROR_NO_ERROR; } TRI_ASSERT(_collection != nullptr); if (TransactionMethods::_makeNolockHeaders != nullptr) { std::string collName(_collection->name()); auto it = TransactionMethods::_makeNolockHeaders->find(collName); if (it != TransactionMethods::_makeNolockHeaders->end()) { // do not lock by command // LOCKING-DEBUG // std::cout << "LockCollection blocked: " << collName << std::endl; return TRI_ERROR_NO_ERROR; } } TRI_ASSERT(!isLocked()); LogicalCollection* collection = _collection; TRI_ASSERT(collection != nullptr); double timeout = _transaction->_timeout; if (_transaction->_hints.has(TransactionHints::Hint::TRY_LOCK)) { // give up early if we cannot acquire the lock instantly timeout = 0.00000001; } bool const useDeadlockDetector = !_transaction->_hints.has(TransactionHints::Hint::SINGLE_OPERATION); int res; if (!isWrite(type)) { LOG_TRX(_transaction, nestingLevel) << "read-locking collection " << _cid; res = collection->beginReadTimed(useDeadlockDetector, timeout); } else { // WRITE or EXCLUSIVE LOG_TRX(_transaction, nestingLevel) << "write-locking collection " << _cid; res = collection->beginWriteTimed(useDeadlockDetector, timeout); } if (res == TRI_ERROR_NO_ERROR) { _lockType = type; } return res; } /// @brief unlock a collection int MMFilesTransactionCollection::doUnlock(AccessMode::Type type, int nestingLevel) { if (_transaction->_hints.has(TransactionHints::Hint::LOCK_NEVER)) { // never unlock return TRI_ERROR_NO_ERROR; } TRI_ASSERT(_collection != nullptr); if (TransactionMethods::_makeNolockHeaders != nullptr) { std::string collName(_collection->name()); auto it = TransactionMethods::_makeNolockHeaders->find(collName); if (it != TransactionMethods::_makeNolockHeaders->end()) { // do not lock by command // LOCKING-DEBUG // std::cout << "UnlockCollection blocked: " << collName << std::endl; return TRI_ERROR_NO_ERROR; } } TRI_ASSERT(isLocked()); if (_nestingLevel < nestingLevel) { // only process our own collections return TRI_ERROR_NO_ERROR; } if (!isWrite(type) && isWrite(_lockType)) { // do not remove a write-lock if a read-unlock was requested! return TRI_ERROR_NO_ERROR; } else if (isWrite(type) && !isWrite(_lockType)) { // we should never try to write-unlock a collection that we have only // read-locked LOG_TOPIC(ERR, arangodb::Logger::FIXME) << "logic error in UnlockCollection"; TRI_ASSERT(false); return TRI_ERROR_INTERNAL; } bool const useDeadlockDetector = !_transaction->_hints.has(TransactionHints::Hint::SINGLE_OPERATION); LogicalCollection* collection = _collection; TRI_ASSERT(collection != nullptr); if (!isWrite(_lockType)) { LOG_TRX(_transaction, nestingLevel) << "read-unlocking collection " << _cid; collection->endRead(useDeadlockDetector); } else { // WRITE or EXCLUSIVE LOG_TRX(_transaction, nestingLevel) << "write-unlocking collection " << _cid; collection->endWrite(useDeadlockDetector); } _lockType = AccessMode::Type::NONE; return TRI_ERROR_NO_ERROR; } bool MMFilesTransactionCollection::isWrite(AccessMode::Type type) const { return (type == AccessMode::Type::WRITE || type == AccessMode::Type::EXCLUSIVE); }