mirror of https://gitee.com/bigwinds/arangodb
2215 lines
68 KiB
C++
2215 lines
68 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Write-ahead log logfile manager
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014 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
|
|
/// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
|
|
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "LogfileManager.h"
|
|
#include "Basics/files.h"
|
|
#include "Basics/hashes.h"
|
|
#include "Basics/json.h"
|
|
#include "Basics/logging.h"
|
|
#include "Basics/Exceptions.h"
|
|
#include "Basics/FileUtils.h"
|
|
#include "Basics/JsonHelper.h"
|
|
#include "Basics/MutexLocker.h"
|
|
#include "Basics/ReadLocker.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/WriteLocker.h"
|
|
#include "Basics/memory-map.h"
|
|
#include "VocBase/server.h"
|
|
#include "Wal/AllocatorThread.h"
|
|
#include "Wal/CollectorThread.h"
|
|
#include "Wal/RecoverState.h"
|
|
#include "Wal/RemoverThread.h"
|
|
#include "Wal/Slots.h"
|
|
#include "Wal/SynchroniserThread.h"
|
|
|
|
using namespace triagens::wal;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief the logfile manager singleton
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static LogfileManager* Instance = nullptr;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- helper functions
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief minimum value for --wal.throttle-when-pending
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint64_t MinThrottleWhenPending () {
|
|
return 1024 * 1024;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief minimum value for --wal.sync-interval
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint64_t MinSyncInterval () {
|
|
return 5;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief minimum value for --wal.logfile-size
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint32_t MinFileSize () {
|
|
#ifdef TRI_ENABLE_MAINTAINER_MODE
|
|
// this allows testing with smaller logfile-sizes
|
|
return 1 * 1024 * 1024;
|
|
#else
|
|
return 8 * 1024 * 1024;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the maximum size of a logfile entry
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint32_t MaxEntrySize () {
|
|
return 2 << 30; // 2 GB
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief minimum number of slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint32_t MinSlots () {
|
|
return 1024 * 8;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief maximum number of slots
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline uint32_t MaxSlots () {
|
|
return 1024 * 1024 * 16;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- class LogfileManager
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- constructors and destructors
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief create the logfile manager
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LogfileManager::LogfileManager (TRI_server_t* server,
|
|
std::string* databasePath)
|
|
: ApplicationFeature("logfile-manager"),
|
|
_server(server),
|
|
_databasePath(databasePath),
|
|
_directory(),
|
|
_recoverState(nullptr),
|
|
_filesize(32 * 1024 * 1024),
|
|
_reserveLogfiles(4),
|
|
_historicLogfiles(10),
|
|
_maxOpenLogfiles(0),
|
|
_numberOfSlots(1048576),
|
|
_syncInterval(100),
|
|
_maxThrottleWait(15000),
|
|
_throttleWhenPending(0),
|
|
_allowOversizeEntries(true),
|
|
_ignoreLogfileErrors(false),
|
|
_ignoreRecoveryErrors(false),
|
|
_suppressShapeInformation(false),
|
|
_allowWrites(false), // start in read-only mode
|
|
_hasFoundLastTick(false),
|
|
_inRecovery(true),
|
|
_startCalled(false),
|
|
_logfilesLock(),
|
|
_logfiles(),
|
|
_slots(nullptr),
|
|
_synchroniserThread(nullptr),
|
|
_allocatorThread(nullptr),
|
|
_collectorThread(nullptr),
|
|
_removerThread(nullptr),
|
|
_lastOpenedId(0),
|
|
_lastCollectedId(0),
|
|
_lastSealedId(0),
|
|
_shutdownFileLock(),
|
|
_transactionsLock(),
|
|
_transactions(),
|
|
_failedTransactions(),
|
|
_droppedCollections(),
|
|
_droppedDatabases(),
|
|
_idLock(),
|
|
_writeThrottled(0),
|
|
_filenameRegex(),
|
|
_shutdown(0) {
|
|
|
|
LOG_TRACE("creating WAL logfile manager");
|
|
TRI_ASSERT(! _allowWrites);
|
|
|
|
int res = regcomp(&_filenameRegex, "^logfile-([0-9][0-9]*)\\.db$", REG_EXTENDED);
|
|
|
|
if (res != 0) {
|
|
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "could not compile regex");
|
|
}
|
|
|
|
_transactions.reserve(32);
|
|
_failedTransactions.reserve(32);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroy the logfile manager
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LogfileManager::~LogfileManager () {
|
|
LOG_TRACE("shutting down WAL logfile manager");
|
|
|
|
stop();
|
|
|
|
regfree(&_filenameRegex);
|
|
|
|
if (_recoverState != nullptr) {
|
|
delete _recoverState;
|
|
_recoverState = nullptr;
|
|
}
|
|
|
|
if (_slots != nullptr) {
|
|
delete _slots;
|
|
_slots = nullptr;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- public methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the logfile manager instance
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LogfileManager* LogfileManager::instance () {
|
|
TRI_ASSERT(Instance != nullptr);
|
|
return Instance;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialise the logfile manager instance
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::initialise (std::string* path,
|
|
TRI_server_t* server) {
|
|
TRI_ASSERT(Instance == nullptr);
|
|
|
|
Instance = new LogfileManager(server, path);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- ApplicationFeature methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setupOptions (std::map<std::string, triagens::basics::ProgramOptionsDescription>& options) {
|
|
options["Write-ahead log options:help-wal"]
|
|
("wal.allow-oversize-entries", &_allowOversizeEntries, "allow entries that are bigger than --wal.logfile-size")
|
|
("wal.directory", &_directory, "logfile directory")
|
|
("wal.historic-logfiles", &_historicLogfiles, "maximum number of historic logfiles to keep after collection")
|
|
("wal.ignore-logfile-errors", &_ignoreLogfileErrors, "ignore logfile errors. this will read recoverable data from corrupted logfiles but ignore any unrecoverable data")
|
|
("wal.ignore-recovery-errors", &_ignoreRecoveryErrors, "continue recovery even if re-applying operations fails")
|
|
("wal.logfile-size", &_filesize, "size of each logfile (in bytes)")
|
|
("wal.open-logfiles", &_maxOpenLogfiles, "maximum number of parallel open logfiles")
|
|
("wal.reserve-logfiles", &_reserveLogfiles, "maximum number of reserve logfiles to maintain")
|
|
("wal.slots", &_numberOfSlots, "number of logfile slots to use")
|
|
("wal.suppress-shape-information", &_suppressShapeInformation, "do not write shape information for markers (saves a lot of disk space, but effectively disables using the write-ahead log for replication)")
|
|
("wal.sync-interval", &_syncInterval, "interval for automatic, non-requested disk syncs (in milliseconds)")
|
|
("wal.throttle-when-pending", &_throttleWhenPending, "throttle writes when at least this many operations are waiting for collection (set to 0 to deactivate write-throttling)")
|
|
("wal.throttle-wait", &_maxThrottleWait, "maximum wait time per operation when write-throttled (in milliseconds)")
|
|
;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::prepare () {
|
|
static bool Prepared = false;
|
|
|
|
if (Prepared) {
|
|
return true;
|
|
}
|
|
|
|
Prepared = true;
|
|
|
|
if (_directory.empty()) {
|
|
// use global configuration variable
|
|
_directory = (*_databasePath);
|
|
|
|
if (! basics::FileUtils::isDirectory(_directory)) {
|
|
std::string systemErrorStr;
|
|
long errorNo;
|
|
|
|
int res = TRI_CreateRecursiveDirectory(_directory.c_str(), errorNo, systemErrorStr);
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_FATAL_AND_EXIT("unable to create database directory: %s",
|
|
systemErrorStr.c_str());
|
|
}
|
|
else {
|
|
LOG_INFO("created database directory '%s'.",
|
|
_directory.c_str());
|
|
}
|
|
}
|
|
|
|
// append "/journals"
|
|
if (_directory[_directory.size() - 1] != TRI_DIR_SEPARATOR_CHAR) {
|
|
// append a trailing slash to directory name
|
|
_directory.push_back(TRI_DIR_SEPARATOR_CHAR);
|
|
}
|
|
_directory.append("journals");
|
|
}
|
|
|
|
if (_directory.empty()) {
|
|
LOG_FATAL_AND_EXIT("no directory specified for WAL logfiles. Please use the --wal.directory option");
|
|
}
|
|
|
|
if (_directory[_directory.size() - 1] != TRI_DIR_SEPARATOR_CHAR) {
|
|
// append a trailing slash to directory name
|
|
_directory.push_back(TRI_DIR_SEPARATOR_CHAR);
|
|
}
|
|
|
|
if (_filesize < MinFileSize()) {
|
|
// minimum filesize per logfile
|
|
LOG_FATAL_AND_EXIT("invalid value for --wal.logfile-size. Please use a value of at least %lu", (unsigned long) MinFileSize());
|
|
}
|
|
|
|
_filesize = (uint32_t) (((_filesize + PageSize - 1) / PageSize) * PageSize);
|
|
|
|
if (_numberOfSlots < MinSlots() || _numberOfSlots > MaxSlots()) {
|
|
// invalid number of slots
|
|
LOG_FATAL_AND_EXIT("invalid value for --wal.slots. Please use a value between %lu and %lu", (unsigned long) MinSlots(), (unsigned long) MaxSlots());
|
|
}
|
|
|
|
if (_throttleWhenPending > 0 && _throttleWhenPending < MinThrottleWhenPending()) {
|
|
LOG_FATAL_AND_EXIT("invalid value for --wal.throttle-when-pending. Please use a value of at least %llu", (unsigned long long) MinThrottleWhenPending());
|
|
}
|
|
|
|
if (_syncInterval < MinSyncInterval()) {
|
|
LOG_FATAL_AND_EXIT("invalid value for --wal.sync-interval. Please use a value of at least %llu", (unsigned long long) MinSyncInterval());
|
|
}
|
|
|
|
// sync interval is specified in milliseconds by the user, but internally
|
|
// we use microseconds
|
|
_syncInterval = _syncInterval * 1000;
|
|
|
|
// initialise some objects
|
|
_slots = new Slots(this, _numberOfSlots, 0);
|
|
_recoverState = new RecoverState(_server, _ignoreRecoveryErrors);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::start () {
|
|
static bool started = false;
|
|
|
|
if (started) {
|
|
// we were already started
|
|
return true;
|
|
}
|
|
|
|
TRI_ASSERT(! _allowWrites);
|
|
|
|
int res = inventory();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not create WAL logfile inventory: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
std::string const shutdownFile = shutdownFilename();
|
|
bool const shutdownFileExists = basics::FileUtils::exists(shutdownFile);
|
|
|
|
if (shutdownFileExists) {
|
|
LOG_TRACE("shutdown file found");
|
|
|
|
res = readShutdownInfo();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not open shutdown file '%s': %s",
|
|
shutdownFile.c_str(),
|
|
TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
LOG_TRACE("no shutdown file found");
|
|
}
|
|
|
|
res = inspectLogfiles();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not inspect WAL logfiles: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
started = true;
|
|
|
|
LOG_TRACE("WAL logfile manager configuration: historic logfiles: %lu, reserve logfiles: %lu, filesize: %lu, sync interval: %lu",
|
|
(unsigned long) _historicLogfiles,
|
|
(unsigned long) _reserveLogfiles,
|
|
(unsigned long) _filesize,
|
|
(unsigned long) _syncInterval);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::open () {
|
|
static bool opened = false;
|
|
|
|
if (opened) {
|
|
// we were already started
|
|
return true;
|
|
}
|
|
|
|
opened = true;
|
|
_startCalled = true;
|
|
|
|
int res = runRecovery();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("unable to finish WAL recovery: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
// note all failed transactions that we found plus the list
|
|
// of collections and databases that we can ignore
|
|
{
|
|
WRITE_LOCKER(_transactionsLock);
|
|
|
|
_failedTransactions.reserve(_recoverState->failedTransactions.size());
|
|
|
|
for (auto const& it : _recoverState->failedTransactions) {
|
|
_failedTransactions.emplace(it.first);
|
|
}
|
|
|
|
_droppedDatabases = _recoverState->droppedDatabases;
|
|
_droppedCollections = _recoverState->droppedCollections;
|
|
}
|
|
|
|
|
|
{
|
|
// set every open logfile to a status of sealed
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
for (auto& it : _logfiles) {
|
|
Logfile* logfile = it.second;
|
|
|
|
if (logfile == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
Logfile::StatusType status = logfile->status();
|
|
|
|
if (status == Logfile::StatusType::OPEN) {
|
|
// set all logfiles to sealed status so they can be collected
|
|
|
|
// we don't care about the previous status here
|
|
logfile->forceStatus(Logfile::StatusType::SEALED);
|
|
|
|
MUTEX_LOCKER(_idLock);
|
|
|
|
if (logfile->id() > _lastSealedId) {
|
|
_lastSealedId = logfile->id();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// now start allocator and synchroniser
|
|
res = startAllocatorThread();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not start WAL allocator thread: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
res = startSynchroniserThread();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not start WAL synchroniser thread: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
// from now on, we allow writes to the logfile
|
|
allowWrites(true);
|
|
|
|
// explicitly abort any open transactions found in the logs
|
|
res = _recoverState->abortOpenTransactions();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not abort open transactions: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
// remove all empty logfiles
|
|
_recoverState->removeEmptyLogfiles();
|
|
|
|
// now fill secondary indexes of all collections used in the recovery
|
|
_recoverState->fillIndexes();
|
|
|
|
// remove usage locks for databases and collections
|
|
_recoverState->releaseResources();
|
|
|
|
// write the current state into the shutdown file
|
|
writeShutdownInfo(false);
|
|
|
|
// finished recovery
|
|
_inRecovery = false;
|
|
|
|
|
|
res = startCollectorThread();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not start WAL collector thread: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
TRI_ASSERT(_collectorThread != nullptr);
|
|
|
|
res = startRemoverThread();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not start WAL remover thread: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
// tell the allocator that the recovery is over now
|
|
_allocatorThread->recoveryDone();
|
|
|
|
|
|
// unload all collections to reset statistics, start compactor threads etc.
|
|
res = TRI_InitDatabasesServer(_server);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not initialise databases: %s", TRI_errno_string(res));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::close () {
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// {@inheritDoc}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::stop () {
|
|
if (! _startCalled) {
|
|
return;
|
|
}
|
|
|
|
if (_shutdown > 0) {
|
|
return;
|
|
}
|
|
|
|
_shutdown = 1;
|
|
|
|
LOG_TRACE("shutting down WAL");
|
|
|
|
// set WAL to read-only mode
|
|
allowWrites(false);
|
|
|
|
// do a final flush at shutdown
|
|
this->flush(true, true, false);
|
|
|
|
// stop threads
|
|
LOG_TRACE("stopping remover thread");
|
|
stopRemoverThread();
|
|
|
|
LOG_TRACE("stopping collector thread");
|
|
stopCollectorThread();
|
|
|
|
LOG_TRACE("stopping allocator thread");
|
|
stopAllocatorThread();
|
|
|
|
LOG_TRACE("stopping synchroniser thread");
|
|
stopSynchroniserThread();
|
|
|
|
// close all open logfiles
|
|
LOG_TRACE("closing logfiles");
|
|
closeLogfiles();
|
|
|
|
TRI_IF_FAILURE("LogfileManagerStop") {
|
|
// intentionally kill the server
|
|
TRI_SegfaultDebugging("LogfileManagerStop");
|
|
}
|
|
|
|
int res = writeShutdownInfo(true);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
LOG_ERROR("could not write WAL shutdown info: %s", TRI_errno_string(res));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- public methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief registers a transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::registerTransaction (TRI_voc_tid_t transactionId) {
|
|
auto lastCollectedId = _lastCollectedId.load();
|
|
auto lastSealedId = _lastSealedId.load();
|
|
|
|
TRI_IF_FAILURE("LogfileManagerRegisterTransactionOom") {
|
|
// intentionally fail here
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
try {
|
|
auto p = std::make_pair(lastCollectedId, lastSealedId);
|
|
|
|
WRITE_LOCKER(_transactionsLock);
|
|
|
|
// insert into currently running list of transactions
|
|
_transactions.emplace(transactionId, std::move(p));
|
|
TRI_ASSERT_EXPENSIVE(lastCollectedId <= lastSealedId);
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
catch (...) {
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief unregisters a transaction
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::unregisterTransaction (TRI_voc_tid_t transactionId,
|
|
bool markAsFailed) {
|
|
WRITE_LOCKER(_transactionsLock);
|
|
|
|
_transactions.erase(transactionId);
|
|
|
|
if (markAsFailed) {
|
|
_failedTransactions.emplace(transactionId);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the set of failed transactions
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::unordered_set<TRI_voc_tid_t> LogfileManager::getFailedTransactions () {
|
|
std::unordered_set<TRI_voc_tid_t> failedTransactions;
|
|
|
|
{
|
|
READ_LOCKER(_transactionsLock);
|
|
failedTransactions = _failedTransactions;
|
|
}
|
|
|
|
return failedTransactions;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the set of dropped collections
|
|
/// this is used during recovery and not used afterwards
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::unordered_set<TRI_voc_cid_t> LogfileManager::getDroppedCollections () {
|
|
std::unordered_set<TRI_voc_cid_t> droppedCollections;
|
|
|
|
{
|
|
READ_LOCKER(_logfilesLock);
|
|
droppedCollections = _droppedCollections;
|
|
}
|
|
|
|
return droppedCollections;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the set of dropped databases
|
|
/// this is used during recovery and not used afterwards
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::unordered_set<TRI_voc_tick_t> LogfileManager::getDroppedDatabases () {
|
|
std::unordered_set<TRI_voc_tick_t> droppedDatabases;
|
|
|
|
{
|
|
READ_LOCKER(_logfilesLock);
|
|
droppedDatabases = _droppedDatabases;
|
|
}
|
|
|
|
return droppedDatabases;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief unregister a list of failed transactions
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::unregisterFailedTransactions (std::unordered_set<TRI_voc_tid_t> const& failedTransactions) {
|
|
WRITE_LOCKER(_transactionsLock);
|
|
|
|
std::for_each(failedTransactions.begin(), failedTransactions.end(), [&] (TRI_voc_tid_t id) {
|
|
_failedTransactions.erase(id);
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not it is currently allowed to create an additional
|
|
/// logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::logfileCreationAllowed (uint32_t size) {
|
|
if (size + Logfile::overhead() > filesize()) {
|
|
// oversize entry. this is always allowed because otherwise everything would
|
|
// lock
|
|
return true;
|
|
}
|
|
|
|
if (_maxOpenLogfiles == 0) {
|
|
return true;
|
|
}
|
|
|
|
uint32_t numberOfLogfiles = 0;
|
|
|
|
// note: this information could also be cached instead of being recalculated
|
|
// everytime
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
for (auto it = _logfiles.begin(); it != _logfiles.end(); ++it) {
|
|
Logfile* logfile = (*it).second;
|
|
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
if (logfile->status() == Logfile::StatusType::OPEN ||
|
|
logfile->status() == Logfile::StatusType::SEAL_REQUESTED) {
|
|
++numberOfLogfiles;
|
|
}
|
|
}
|
|
|
|
return (numberOfLogfiles <= _maxOpenLogfiles);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief whether or not there are reserve logfiles
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::hasReserveLogfiles () {
|
|
uint32_t numberOfLogfiles = 0;
|
|
|
|
// note: this information could also be cached instead of being recalculated
|
|
// everytime
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
// reverse-scan the logfiles map
|
|
for (auto it = _logfiles.rbegin(); it != _logfiles.rend(); ++it) {
|
|
Logfile* logfile = (*it).second;
|
|
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
if (logfile->freeSize() > 0 && ! logfile->isSealed()) {
|
|
if (++numberOfLogfiles >= reserveLogfiles()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief signal that a sync operation is required
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::signalSync () {
|
|
_synchroniserThread->signalSync();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief allocate space in a logfile for later writing
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SlotInfo LogfileManager::allocate (uint32_t size) {
|
|
if (! _allowWrites) {
|
|
// no writes allowed
|
|
return SlotInfo(TRI_ERROR_ARANGO_READ_ONLY);
|
|
}
|
|
|
|
if (size > MaxEntrySize()) {
|
|
// entry is too big
|
|
return SlotInfo(TRI_ERROR_ARANGO_DOCUMENT_TOO_LARGE);
|
|
}
|
|
|
|
if (size > _filesize && ! _allowOversizeEntries) {
|
|
// entry is too big for a logfile
|
|
return SlotInfo(TRI_ERROR_ARANGO_DOCUMENT_TOO_LARGE);
|
|
}
|
|
|
|
return _slots->nextUnused(size);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief allocate space in a logfile for later writing, version for legends
|
|
///
|
|
/// See explanations about legends in the corresponding allocateAndWrite
|
|
/// convenience function.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SlotInfo LogfileManager::allocate (uint32_t size,
|
|
TRI_voc_cid_t cid,
|
|
TRI_shape_sid_t sid,
|
|
uint32_t legendOffset,
|
|
void*& oldLegend) {
|
|
if (! _allowWrites) {
|
|
// no writes allowed
|
|
#ifdef TRI_ENABLE_MAINTAINER_MODE
|
|
TRI_ASSERT(false);
|
|
#endif
|
|
|
|
return SlotInfo(TRI_ERROR_ARANGO_READ_ONLY);
|
|
}
|
|
|
|
if (size > MaxEntrySize()) {
|
|
// entry is too big
|
|
return SlotInfo(TRI_ERROR_ARANGO_DOCUMENT_TOO_LARGE);
|
|
}
|
|
|
|
if (size > _filesize && ! _allowOversizeEntries) {
|
|
// entry is too big for a logfile
|
|
return SlotInfo(TRI_ERROR_ARANGO_DOCUMENT_TOO_LARGE);
|
|
}
|
|
|
|
return _slots->nextUnused(size, cid, sid, legendOffset, oldLegend);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief finalise a log entry
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::finalise (SlotInfo& slotInfo,
|
|
bool waitForSync) {
|
|
_slots->returnUsed(slotInfo, waitForSync);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief write data into the logfile
|
|
/// this is a convenience function that combines allocate, memcpy and finalise
|
|
///
|
|
/// We need this version with cid, sid, legendOffset and oldLegend because
|
|
/// there is a cache for each WAL file keeping track which legends are
|
|
/// already in it. The decision whether or not an additional legend is
|
|
/// needed therefore has to be taken in the allocation routine. This
|
|
/// version is only used to write document or edge markers. If a previously
|
|
/// written legend is found its address is returned in oldLegend such that
|
|
/// the new marker can point to it with a relative reference.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SlotInfoCopy LogfileManager::allocateAndWrite (void* src,
|
|
uint32_t size,
|
|
bool waitForSync,
|
|
TRI_voc_cid_t cid,
|
|
TRI_shape_sid_t sid,
|
|
uint32_t legendOffset,
|
|
void*& oldLegend) {
|
|
|
|
SlotInfo slotInfo = allocate(size, cid, sid, legendOffset, oldLegend);
|
|
|
|
if (slotInfo.errorCode != TRI_ERROR_NO_ERROR) {
|
|
return SlotInfoCopy(slotInfo.errorCode);
|
|
}
|
|
|
|
TRI_ASSERT(slotInfo.slot != nullptr);
|
|
|
|
try {
|
|
slotInfo.slot->fill(src, size);
|
|
|
|
// we must copy the slotinfo because finalise() will set its internal to 0 again
|
|
SlotInfoCopy copy(slotInfo.slot);
|
|
|
|
finalise(slotInfo, waitForSync);
|
|
return copy;
|
|
}
|
|
catch (...) {
|
|
// if we don't return the slot we'll run into serious problems later
|
|
finalise(slotInfo, false);
|
|
|
|
return SlotInfoCopy(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief write data into the logfile
|
|
/// this is a convenience function that combines allocate, memcpy and finalise
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SlotInfoCopy LogfileManager::allocateAndWrite (void* src,
|
|
uint32_t size,
|
|
bool waitForSync) {
|
|
|
|
SlotInfo slotInfo = allocate(size);
|
|
|
|
if (slotInfo.errorCode != TRI_ERROR_NO_ERROR) {
|
|
return SlotInfoCopy(slotInfo.errorCode);
|
|
}
|
|
|
|
TRI_ASSERT(slotInfo.slot != nullptr);
|
|
|
|
try {
|
|
slotInfo.slot->fill(src, size);
|
|
|
|
// we must copy the slotinfo because finalise() will set its internal to 0 again
|
|
SlotInfoCopy copy(slotInfo.slot);
|
|
|
|
finalise(slotInfo, waitForSync);
|
|
return copy;
|
|
}
|
|
catch (...) {
|
|
// if we don't return the slot we'll run into serious problems later
|
|
finalise(slotInfo, false);
|
|
|
|
return SlotInfoCopy(TRI_ERROR_INTERNAL);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief write data into the logfile
|
|
/// this is a convenience function that combines allocate, memcpy and finalise
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SlotInfoCopy LogfileManager::allocateAndWrite (Marker const& marker,
|
|
bool waitForSync) {
|
|
return allocateAndWrite(marker.mem(), marker.size(), waitForSync);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief finalise and seal the currently open logfile
|
|
/// this is useful to ensure that any open writes up to this point have made
|
|
/// it into a logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::flush (bool waitForSync,
|
|
bool waitForCollector,
|
|
bool writeShutdownFile) {
|
|
TRI_ASSERT(! _inRecovery);
|
|
|
|
Logfile::IdType lastOpenLogfileId;
|
|
Logfile::IdType lastSealedLogfileId;
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
lastOpenLogfileId = _lastOpenedId;
|
|
lastSealedLogfileId = _lastSealedId;
|
|
}
|
|
|
|
if (lastOpenLogfileId == 0) {
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
LOG_TRACE("about to flush active WAL logfile. currentLogfileId: %llu, waitForSync: %d, waitForCollector: %d",
|
|
(unsigned long long) lastOpenLogfileId,
|
|
(int) waitForSync,
|
|
(int) waitForCollector);
|
|
|
|
int res = _slots->flush(waitForSync);
|
|
|
|
if (res != TRI_ERROR_NO_ERROR &&
|
|
res != TRI_ERROR_ARANGO_DATAFILE_EMPTY) {
|
|
LOG_ERROR("unexpected error in WAL flush request: %s", TRI_errno_string(res));
|
|
return res;
|
|
}
|
|
|
|
if (waitForCollector) {
|
|
double maxWaitTime = 0.0; // this means wait forever
|
|
if (_shutdown == 1) {
|
|
maxWaitTime = 120.0;
|
|
}
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
// we need to wait for the collector...
|
|
this->waitForCollector(lastOpenLogfileId, maxWaitTime);
|
|
}
|
|
else if (res == TRI_ERROR_ARANGO_DATAFILE_EMPTY) {
|
|
// current logfile is empty and cannot be collected
|
|
// we need to wait for the collector to collect the previously sealed datafile
|
|
|
|
if (lastSealedLogfileId > 0) {
|
|
this->waitForCollector(lastSealedLogfileId, maxWaitTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (writeShutdownFile) {
|
|
// update the file with the last tick, last sealed etc.
|
|
return writeShutdownInfo(false);
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief re-inserts a logfile back into the inventory only
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::relinkLogfile (Logfile* logfile) {
|
|
Logfile::IdType const id = logfile->id();
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
_logfiles.emplace(id, logfile);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove a logfile from the inventory only
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::unlinkLogfile (Logfile* logfile) {
|
|
Logfile::IdType const id = logfile->id();
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it == _logfiles.end()) {
|
|
return false;
|
|
}
|
|
|
|
_logfiles.erase(it);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove a logfile from the inventory only
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::unlinkLogfile (Logfile::IdType id) {
|
|
WRITE_LOCKER(_logfilesLock);
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it == _logfiles.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
_logfiles.erase(it);
|
|
|
|
return (*it).second;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief removes logfiles that are allowed to be removed
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool LogfileManager::removeLogfiles () {
|
|
int iterations = 0;
|
|
bool worked = false;
|
|
|
|
while (++iterations < 6) {
|
|
Logfile* logfile = getRemovableLogfile();
|
|
|
|
if (logfile == nullptr) {
|
|
break;
|
|
}
|
|
|
|
removeLogfile(logfile);
|
|
worked = true;
|
|
}
|
|
|
|
return worked;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief sets the status of a logfile to open
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setLogfileOpen (Logfile* logfile) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
logfile->setStatus(Logfile::StatusType::OPEN);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief sets the status of a logfile to seal-requested
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setLogfileSealRequested (Logfile* logfile) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
logfile->setStatus(Logfile::StatusType::SEAL_REQUESTED);
|
|
}
|
|
|
|
signalSync();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief sets the status of a logfile to sealed
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setLogfileSealed (Logfile* logfile) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
setLogfileSealed(logfile->id());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief sets the status of a logfile to sealed
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setLogfileSealed (Logfile::IdType id) {
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it == _logfiles.end()) {
|
|
return;
|
|
}
|
|
|
|
(*it).second->setStatus(Logfile::StatusType::SEALED);
|
|
}
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
_lastSealedId = id;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the status of a logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile::StatusType LogfileManager::getLogfileStatus (Logfile::IdType id) {
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it == _logfiles.end()) {
|
|
return Logfile::StatusType::UNKNOWN;
|
|
}
|
|
|
|
return (*it).second->status();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the file descriptor of a logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::getLogfileDescriptor (Logfile::IdType id) {
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it == _logfiles.end()) {
|
|
// error
|
|
LOG_ERROR("could not find logfile %llu", (unsigned long long) id);
|
|
return -1;
|
|
}
|
|
|
|
Logfile* logfile = (*it).second;
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
return logfile->fd();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the current open region of a logfile
|
|
/// this uses the slots lock
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::getActiveLogfileRegion (Logfile* logfile,
|
|
char const*& begin,
|
|
char const*& end) {
|
|
_slots->getActiveLogfileRegion(logfile, begin, end);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get logfiles for a tick range
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::vector<Logfile*> LogfileManager::getLogfilesForTickRange (TRI_voc_tick_t minTick,
|
|
TRI_voc_tick_t maxTick,
|
|
bool& minTickIncluded) {
|
|
std::vector<Logfile*> temp;
|
|
std::vector<Logfile*> matching;
|
|
|
|
minTickIncluded = false;
|
|
|
|
// we need a two step logfile qualification procedure
|
|
// this is to avoid holding the lock on _logfilesLock and then acquiring the
|
|
// mutex on the _slots. If we hold both locks, we might deadlock with other
|
|
// threads
|
|
|
|
{
|
|
READ_LOCKER(_logfilesLock);
|
|
temp.reserve(_logfiles.size());
|
|
matching.reserve(_logfiles.size());
|
|
|
|
for (auto it = _logfiles.begin(); it != _logfiles.end(); ++it) {
|
|
Logfile* logfile = (*it).second;
|
|
|
|
if (logfile == nullptr || logfile->status() == Logfile::StatusType::EMPTY) {
|
|
continue;
|
|
}
|
|
|
|
// found a datafile
|
|
temp.emplace_back(logfile);
|
|
|
|
// mark it as being used so it isn't deleted
|
|
logfile->use();
|
|
}
|
|
}
|
|
|
|
// now go on without the lock
|
|
for (auto it = temp.begin(); it != temp.end(); ++it) {
|
|
Logfile* logfile = (*it);
|
|
|
|
TRI_voc_tick_t logMin;
|
|
TRI_voc_tick_t logMax;
|
|
_slots->getActiveTickRange(logfile, logMin, logMax);
|
|
|
|
if (logMin <= minTick && logMin > 0) {
|
|
minTickIncluded = true;
|
|
}
|
|
|
|
if (minTick > logMax || maxTick < logMin) {
|
|
// datafile is older than requested range
|
|
// or: datafile is newer than requested range
|
|
|
|
// release the logfile, so it can be deleted
|
|
logfile->release();
|
|
continue;
|
|
}
|
|
|
|
// finally copy all qualifying logfiles into the result
|
|
matching.push_back(logfile);
|
|
}
|
|
|
|
// all qualifying locks are marked as used now
|
|
return matching;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return logfiles for a tick range
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::returnLogfiles (std::vector<Logfile*> const& logfiles) {
|
|
for (auto& logfile : logfiles) {
|
|
logfile->release();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get a logfile by id
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::getLogfile (Logfile::IdType id) {
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it != _logfiles.end()) {
|
|
return (*it).second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get a logfile and its status by id
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::getLogfile (Logfile::IdType id,
|
|
Logfile::StatusType& status) {
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
auto it = _logfiles.find(id);
|
|
|
|
if (it != _logfiles.end()) {
|
|
status = (*it).second->status();
|
|
return (*it).second;
|
|
}
|
|
|
|
status = Logfile::StatusType::UNKNOWN;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get a logfile for writing. this may return nullptr
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::getWriteableLogfile (uint32_t size,
|
|
Logfile::StatusType& status) {
|
|
static const uint64_t SleepTime = 10 * 1000;
|
|
static const uint64_t MaxIterations = 1500;
|
|
size_t iterations = 0;
|
|
bool haveSignalled = false;
|
|
|
|
TRI_IF_FAILURE("LogfileManagerGetWriteableLogfile") {
|
|
// intentionally don't return a logfile
|
|
return nullptr;
|
|
}
|
|
|
|
while (++iterations < MaxIterations) {
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
auto it = _logfiles.begin();
|
|
|
|
while (it != _logfiles.end()) {
|
|
Logfile* logfile = (*it).second;
|
|
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
if (logfile->isWriteable(size)) {
|
|
// found a logfile, update the status variable and return the logfile
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
_lastOpenedId = logfile->id();
|
|
}
|
|
|
|
status = logfile->status();
|
|
return logfile;
|
|
}
|
|
|
|
if (logfile->status() == Logfile::StatusType::EMPTY &&
|
|
! logfile->isWriteable(size)) {
|
|
// we found an empty logfile, but the entry won't fit
|
|
|
|
// delete the logfile from the sequence of logfiles
|
|
_logfiles.erase(it++);
|
|
|
|
// and physically remove the file
|
|
// note: this will also delete the logfile object!
|
|
removeLogfile(logfile);
|
|
}
|
|
else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
// signal & sleep outside the lock
|
|
if (! haveSignalled) {
|
|
_allocatorThread->signal(size);
|
|
haveSignalled = true;
|
|
}
|
|
usleep(SleepTime);
|
|
}
|
|
|
|
LOG_WARNING("unable to acquire writeable WAL logfile after %llu ms", (unsigned long long) (MaxIterations * SleepTime) / 1000);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get a logfile to collect. this may return nullptr
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::getCollectableLogfile () {
|
|
// iterate over all active readers and find their minimum used logfile id
|
|
Logfile::IdType minId = UINT64_MAX;
|
|
|
|
{
|
|
READ_LOCKER(_transactionsLock);
|
|
|
|
// iterate over all active transactions and find their minimum used logfile id
|
|
for (auto const& it : _transactions) {
|
|
Logfile::IdType lastWrittenId = it.second.second;
|
|
|
|
if (lastWrittenId < minId) {
|
|
minId = lastWrittenId;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
for (auto& it : _logfiles) {
|
|
auto logfile = it.second;
|
|
|
|
if (logfile == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (logfile->id() <= minId &&
|
|
logfile->canBeCollected()) {
|
|
return logfile;
|
|
}
|
|
|
|
if (logfile->id() > minId) {
|
|
// abort early
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get a logfile to remove. this may return nullptr
|
|
/// if it returns a logfile, the logfile is removed from the list of available
|
|
/// logfiles
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile* LogfileManager::getRemovableLogfile () {
|
|
TRI_ASSERT(! _inRecovery);
|
|
|
|
Logfile::IdType minId = UINT64_MAX;
|
|
|
|
{
|
|
READ_LOCKER(_transactionsLock);
|
|
|
|
// iterate over all active readers and find their minimum used logfile id
|
|
for (auto const& it : _transactions) {
|
|
Logfile::IdType lastCollectedId = it.second.first;
|
|
|
|
if (lastCollectedId < minId) {
|
|
minId = lastCollectedId;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
uint32_t numberOfLogfiles = 0;
|
|
Logfile* first = nullptr;
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
for (auto& it : _logfiles) {
|
|
Logfile* logfile = it.second;
|
|
|
|
// find the first logfile that can be safely removed
|
|
if (logfile == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (logfile->id() <= minId &&
|
|
logfile->canBeRemoved()) {
|
|
if (first == nullptr) {
|
|
first = logfile;
|
|
}
|
|
|
|
if (++numberOfLogfiles > historicLogfiles()) {
|
|
TRI_ASSERT(first != nullptr);
|
|
_logfiles.erase(first->id());
|
|
|
|
TRI_ASSERT(_logfiles.find(first->id()) == _logfiles.end());
|
|
|
|
return first;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief increase the number of collect operations for a logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::increaseCollectQueueSize (Logfile* logfile) {
|
|
logfile->increaseCollectQueueSize();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief decrease the number of collect operations for a logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::decreaseCollectQueueSize (Logfile* logfile) {
|
|
logfile->decreaseCollectQueueSize();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief mark a file as being requested for collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setCollectionRequested (Logfile* logfile) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
if (logfile->status() == Logfile::StatusType::COLLECTION_REQUESTED) {
|
|
// the collector already asked for this file, but couldn't process it
|
|
// due to some exception
|
|
return;
|
|
}
|
|
|
|
logfile->setStatus(Logfile::StatusType::COLLECTION_REQUESTED);
|
|
}
|
|
|
|
if (! _inRecovery) {
|
|
// to start collection
|
|
_collectorThread->signal();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief mark a file as being done with collection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::setCollectionDone (Logfile* logfile) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
Logfile::IdType id = logfile->id();
|
|
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
logfile->setStatus(Logfile::StatusType::COLLECTED);
|
|
}
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
_lastCollectedId = id;
|
|
}
|
|
|
|
if (! _inRecovery) {
|
|
// to start removal of unneeded datafiles
|
|
_collectorThread->signal();
|
|
writeShutdownInfo(false);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief force the status of a specific logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::forceStatus (Logfile* logfile,
|
|
Logfile::StatusType status) {
|
|
TRI_ASSERT(logfile != nullptr);
|
|
|
|
{
|
|
WRITE_LOCKER(_logfilesLock);
|
|
logfile->forceStatus(status);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the current state
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LogfileManagerState LogfileManager::state () {
|
|
LogfileManagerState state;
|
|
|
|
// now fill the state
|
|
_slots->statistics(state.lastTick, state.lastDataTick, state.numEvents);
|
|
state.timeString = getTimeString();
|
|
|
|
return state;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the current available logfile ranges
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LogfileRanges LogfileManager::ranges () {
|
|
LogfileRanges result;
|
|
|
|
READ_LOCKER(_logfilesLock);
|
|
|
|
for (auto const& it : _logfiles) {
|
|
Logfile* logfile = it.second;
|
|
|
|
if (logfile == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
auto df = logfile->df();
|
|
if (df->_tickMin == 0 && df->_tickMax == 0) {
|
|
continue;
|
|
}
|
|
|
|
result.emplace_back(LogfileRange(it.first, logfile->filename(), logfile->statusText(), df->_tickMin, df->_tickMax));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- private methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief remove a logfile in the file system
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::removeLogfile (Logfile* logfile) {
|
|
// old filename
|
|
Logfile::IdType const id = logfile->id();
|
|
std::string const filename = logfileName(id);
|
|
|
|
LOG_TRACE("removing logfile '%s'", filename.c_str());
|
|
|
|
// now close the logfile
|
|
delete logfile;
|
|
|
|
int res = TRI_ERROR_NO_ERROR;
|
|
// now physically remove the file
|
|
|
|
if (! basics::FileUtils::remove(filename, &res)) {
|
|
LOG_ERROR("unable to remove logfile '%s': %s",
|
|
filename.c_str(),
|
|
TRI_errno_string(res));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief wait until a specific logfile has been collected
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::waitForCollector (Logfile::IdType logfileId,
|
|
double maxWaitTime) {
|
|
static const int64_t SingleWaitPeriod = 50 * 1000;
|
|
|
|
int64_t maxIterations = INT64_MAX; // wait forever
|
|
if (maxWaitTime > 0.0) {
|
|
// if specified, wait for a shorter period of time
|
|
maxIterations = static_cast<int64_t>(maxWaitTime * 1000000.0 / (double) SingleWaitPeriod);
|
|
LOG_TRACE("will wait for max. %f seconds for collector to finish", maxWaitTime);
|
|
}
|
|
|
|
LOG_TRACE("waiting for collector thread to collect logfile %llu", (unsigned long long) logfileId);
|
|
|
|
// wait for the collector thread to finish the collection
|
|
int64_t iterations = 0;
|
|
|
|
while (++iterations < maxIterations) {
|
|
if (_lastCollectedId >= logfileId) {
|
|
return;
|
|
}
|
|
|
|
LOG_TRACE("waiting for collector");
|
|
usleep(SingleWaitPeriod);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief run the recovery procedure
|
|
/// this is called after the logfiles have been scanned completely and
|
|
/// recovery state has been build. additionally, all databases have been
|
|
/// opened already so we can use collections
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::runRecovery () {
|
|
TRI_ASSERT(! _allowWrites);
|
|
|
|
if (! _recoverState->mustRecover()) {
|
|
// nothing to do
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
if (_ignoreRecoveryErrors) {
|
|
LOG_INFO("running WAL recovery (%d logfiles), ignoring recovery errors", (int) _recoverState->logfilesToProcess.size());
|
|
}
|
|
else {
|
|
LOG_INFO("running WAL recovery (%d logfiles)", (int) _recoverState->logfilesToProcess.size());
|
|
}
|
|
|
|
// now iterate over all logfiles that we found during recovery
|
|
// we can afford to iterate the files without _logfilesLock
|
|
// this is because all other threads competing for the lock are
|
|
// not active yet
|
|
{
|
|
int res = _recoverState->replayLogfiles();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (_recoverState->errorCount == 0) {
|
|
LOG_INFO("WAL recovery finished successfully");
|
|
}
|
|
else {
|
|
LOG_WARNING("WAL recovery finished, some errors ignored due to settings");
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief closes all logfiles
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::closeLogfiles () {
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
for (auto& it : _logfiles) {
|
|
Logfile* logfile = it.second;
|
|
|
|
if (logfile != nullptr) {
|
|
delete logfile;
|
|
}
|
|
}
|
|
|
|
_logfiles.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief reads the shutdown information
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::readShutdownInfo () {
|
|
std::string const filename = shutdownFilename();
|
|
|
|
std::unique_ptr<TRI_json_t> json(TRI_JsonFile(TRI_UNKNOWN_MEM_ZONE, filename.c_str(), nullptr));
|
|
|
|
if (json == nullptr) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
uint64_t lastTick = basics::JsonHelper::stringUInt64(json.get(), "tick");
|
|
TRI_UpdateTickServer(static_cast<TRI_voc_tick_t>(lastTick));
|
|
|
|
if (lastTick > 0) {
|
|
_hasFoundLastTick = true;
|
|
}
|
|
|
|
// read id of last collected logfile (maybe 0)
|
|
uint64_t lastCollectedId = basics::JsonHelper::stringUInt64(json.get(), "lastCollected");
|
|
|
|
// read if of last sealed logfile (maybe 0)
|
|
uint64_t lastSealedId = basics::JsonHelper::stringUInt64(json.get(), "lastSealed");
|
|
|
|
if (lastSealedId < lastCollectedId) {
|
|
// should not happen normally
|
|
lastSealedId = lastCollectedId;
|
|
}
|
|
|
|
std::string const shutdownTime(basics::JsonHelper::getStringValue(json.get(), "shutdownTime"));
|
|
if (shutdownTime.empty()) {
|
|
LOG_TRACE("no previous shutdown time found");
|
|
}
|
|
else {
|
|
LOG_TRACE("previous shutdown was at '%s'", shutdownTime.c_str());
|
|
}
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
_lastCollectedId = static_cast<Logfile::IdType>(lastCollectedId);
|
|
_lastSealedId = static_cast<Logfile::IdType>(lastSealedId);
|
|
|
|
LOG_TRACE("initial values for WAL logfile manager: tick: %llu, lastCollected: %llu, lastSealed: %llu",
|
|
(unsigned long long) lastTick,
|
|
(unsigned long long) _lastCollectedId,
|
|
(unsigned long long) _lastSealedId);
|
|
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief writes the shutdown information
|
|
/// this function is called at shutdown and at every logfile flush request
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::writeShutdownInfo (bool writeShutdownTime) {
|
|
TRI_IF_FAILURE("LogfileManagerWriteShutdown") {
|
|
return TRI_ERROR_DEBUG;
|
|
}
|
|
|
|
std::string const filename = shutdownFilename();
|
|
|
|
std::unique_ptr<TRI_json_t> json(TRI_CreateObjectJson(TRI_UNKNOWN_MEM_ZONE));
|
|
|
|
if (json == nullptr) {
|
|
LOG_ERROR("unable to write WAL state file '%s'", filename.c_str());
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// create local copies of the instance variables while holding the read lock
|
|
Logfile::IdType lastCollectedId;
|
|
Logfile::IdType lastSealedId;
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
lastCollectedId = _lastCollectedId;
|
|
lastSealedId = _lastSealedId;
|
|
}
|
|
|
|
std::string val;
|
|
|
|
val = basics::StringUtils::itoa(TRI_CurrentTickServer());
|
|
TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json.get(), "tick", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, val.c_str(), val.size()));
|
|
|
|
val = basics::StringUtils::itoa(lastCollectedId);
|
|
TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json.get(), "lastCollected", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, val.c_str(), val.size()));
|
|
|
|
val = basics::StringUtils::itoa(lastSealedId);
|
|
TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json.get(), "lastSealed", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, val.c_str(), val.size()));
|
|
|
|
if (writeShutdownTime) {
|
|
std::string const t(getTimeString());
|
|
TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json.get(), "shutdownTime", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, t.c_str(), t.size()));
|
|
}
|
|
|
|
bool ok;
|
|
{
|
|
// grab a lock so no two threads can write the shutdown info at the same time
|
|
MUTEX_LOCKER(_shutdownFileLock);
|
|
ok = TRI_SaveJson(filename.c_str(), json.get(), true);
|
|
}
|
|
|
|
if (! ok) {
|
|
LOG_ERROR("unable to write WAL state file '%s'", filename.c_str());
|
|
|
|
return TRI_ERROR_CANNOT_WRITE_FILE;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief start the synchroniser thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::startSynchroniserThread () {
|
|
_synchroniserThread = new SynchroniserThread(this, _syncInterval);
|
|
|
|
if (_synchroniserThread == nullptr) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
if (! _synchroniserThread->start()) {
|
|
delete _synchroniserThread;
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief stop the synchroniser thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::stopSynchroniserThread () {
|
|
if (_synchroniserThread != nullptr) {
|
|
LOG_TRACE("stopping WAL synchroniser thread");
|
|
|
|
_synchroniserThread->stop();
|
|
_synchroniserThread->shutdown();
|
|
|
|
delete _synchroniserThread;
|
|
_synchroniserThread = nullptr;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief start the allocator thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::startAllocatorThread () {
|
|
_allocatorThread = new AllocatorThread(this);
|
|
|
|
if (_allocatorThread == nullptr) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
if (! _allocatorThread->start()) {
|
|
delete _allocatorThread;
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief stop the allocator thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::stopAllocatorThread () {
|
|
if (_allocatorThread != nullptr) {
|
|
LOG_TRACE("stopping WAL allocator thread");
|
|
|
|
_allocatorThread->stop();
|
|
_allocatorThread->shutdown();
|
|
|
|
delete _allocatorThread;
|
|
_allocatorThread = nullptr;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief start the collector thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::startCollectorThread () {
|
|
_collectorThread = new CollectorThread(this, _server);
|
|
|
|
if (_collectorThread == nullptr) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
if (! _collectorThread->start()) {
|
|
delete _collectorThread;
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief stop the collector thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::stopCollectorThread () {
|
|
if (_collectorThread != nullptr) {
|
|
LOG_TRACE("stopping WAL collector thread");
|
|
|
|
_collectorThread->stop();
|
|
_collectorThread->shutdown();
|
|
|
|
delete _collectorThread;
|
|
_collectorThread = nullptr;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief start the remover thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::startRemoverThread () {
|
|
_removerThread = new RemoverThread(this);
|
|
|
|
if (_removerThread == nullptr) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
if (! _removerThread->start()) {
|
|
delete _removerThread;
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief stop the remover thread
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogfileManager::stopRemoverThread () {
|
|
if (_removerThread != nullptr) {
|
|
LOG_TRACE("stopping WAL remover thread");
|
|
|
|
_removerThread->stop();
|
|
_removerThread->shutdown();
|
|
|
|
delete _removerThread;
|
|
_removerThread = nullptr;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief check which logfiles are present in the log directory
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::inventory () {
|
|
int res = ensureDirectory();
|
|
|
|
if (res != TRI_ERROR_NO_ERROR) {
|
|
return res;
|
|
}
|
|
|
|
LOG_TRACE("scanning WAL directory: '%s'", _directory.c_str());
|
|
|
|
std::vector<std::string> files = basics::FileUtils::listFiles(_directory);
|
|
|
|
for (auto it = files.begin(); it != files.end(); ++it) {
|
|
regmatch_t matches[2];
|
|
std::string const file = (*it);
|
|
char const* s = file.c_str();
|
|
|
|
if (regexec(&_filenameRegex, s, sizeof(matches) / sizeof(matches[1]), matches, 0) == 0) {
|
|
Logfile::IdType const id = basics::StringUtils::uint64(s + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so);
|
|
|
|
if (id == 0) {
|
|
LOG_WARNING("encountered invalid id for logfile '%s'. ids must be > 0", file.c_str());
|
|
}
|
|
else {
|
|
// update global tick
|
|
TRI_UpdateTickServer(static_cast<TRI_voc_tick_t>(id));
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
_logfiles.emplace(id, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief inspect the logfiles in the log directory
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::inspectLogfiles () {
|
|
LOG_TRACE("inspecting WAL logfiles");
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
|
|
#ifdef TRI_ENABLE_MAINTAINER_MODE
|
|
// print an inventory
|
|
for (auto it = _logfiles.begin(); it != _logfiles.end(); ++it) {
|
|
Logfile* logfile = (*it).second;
|
|
|
|
if (logfile != nullptr) {
|
|
LOG_DEBUG("logfile %llu, filename '%s', status %s",
|
|
(unsigned long long) logfile->id(),
|
|
logfile->filename().c_str(),
|
|
logfile->statusText().c_str());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (auto it = _logfiles.begin(); it != _logfiles.end(); ) {
|
|
Logfile::IdType const id = (*it).first;
|
|
std::string const filename = logfileName(id);
|
|
|
|
TRI_ASSERT((*it).second == nullptr);
|
|
|
|
int res = Logfile::judge(filename);
|
|
|
|
if (res == TRI_ERROR_ARANGO_DATAFILE_EMPTY) {
|
|
_recoverState->emptyLogfiles.push_back(filename);
|
|
_logfiles.erase(it++);
|
|
continue;
|
|
}
|
|
|
|
bool const wasCollected = (id <= _lastCollectedId);
|
|
Logfile* logfile = Logfile::openExisting(filename, id, wasCollected, _ignoreLogfileErrors);
|
|
|
|
if (logfile == nullptr) {
|
|
// an error happened when opening a logfile
|
|
if (! _ignoreLogfileErrors) {
|
|
// we don't ignore errors, so we abort here
|
|
int res = TRI_errno();
|
|
|
|
if (res == TRI_ERROR_NO_ERROR) {
|
|
// must have an error!
|
|
res = TRI_ERROR_ARANGO_DATAFILE_UNREADABLE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
_logfiles.erase(it++);
|
|
continue;
|
|
}
|
|
|
|
if (logfile->status() == Logfile::StatusType::OPEN ||
|
|
logfile->status() == Logfile::StatusType::SEALED) {
|
|
_recoverState->logfilesToProcess.push_back(logfile);
|
|
}
|
|
|
|
LOG_TRACE("inspecting logfile %llu (%s)",
|
|
(unsigned long long) logfile->id(),
|
|
logfile->statusText().c_str());
|
|
|
|
// update the tick statistics
|
|
if (! TRI_IterateDatafile(logfile->df(), &RecoverState::InitialScanMarker, static_cast<void*>(_recoverState))) {
|
|
LOG_WARNING("WAL inspection failed when scanning logfile '%s'", logfile->filename().c_str());
|
|
return TRI_ERROR_ARANGO_RECOVERY;
|
|
}
|
|
|
|
LOG_TRACE("inspected logfile %llu (%s), tickMin: %llu, tickMax: %llu",
|
|
(unsigned long long) logfile->id(),
|
|
logfile->statusText().c_str(),
|
|
(unsigned long long) logfile->df()->_tickMin,
|
|
(unsigned long long) logfile->df()->_tickMax);
|
|
|
|
if (logfile->status() == Logfile::StatusType::SEALED) {
|
|
// If it is sealed, switch to random access:
|
|
TRI_datafile_t* df = logfile->df();
|
|
TRI_MMFileAdvise(df->_data, df->_maximalSize, TRI_MADVISE_RANDOM);
|
|
}
|
|
|
|
{
|
|
MUTEX_LOCKER(_idLock);
|
|
if (logfile->status() == Logfile::StatusType::SEALED &&
|
|
id > _lastSealedId) {
|
|
_lastSealedId = id;
|
|
}
|
|
|
|
if ((logfile->status() == Logfile::StatusType::SEALED || logfile->status() == Logfile::StatusType::OPEN) &&
|
|
id > _lastOpenedId) {
|
|
_lastOpenedId = id;
|
|
}
|
|
}
|
|
|
|
(*it).second = logfile;
|
|
++it;
|
|
}
|
|
|
|
|
|
// update the tick with the max tick we found in the WAL
|
|
TRI_UpdateTickServer(_recoverState->lastTick);
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief allocates a new reserve logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::createReserveLogfile (uint32_t size) {
|
|
Logfile::IdType const id = nextId();
|
|
std::string const filename = logfileName(id);
|
|
|
|
LOG_TRACE("creating empty logfile '%s' with size %lu",
|
|
filename.c_str(),
|
|
(unsigned long) size);
|
|
|
|
uint32_t realsize;
|
|
if (size > 0 && size > filesize()) {
|
|
// create a logfile with the requested size
|
|
realsize = size + Logfile::overhead();
|
|
}
|
|
else {
|
|
// create a logfile with default size
|
|
realsize = filesize();
|
|
}
|
|
|
|
Logfile* logfile = Logfile::createNew(filename.c_str(), id, realsize);
|
|
|
|
if (logfile == nullptr) {
|
|
int res = TRI_errno();
|
|
|
|
LOG_ERROR("unable to create logfile: %s", TRI_errno_string(res));
|
|
return res;
|
|
}
|
|
|
|
WRITE_LOCKER(_logfilesLock);
|
|
_logfiles.emplace(id, logfile);
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get an id for the next logfile
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
Logfile::IdType LogfileManager::nextId () {
|
|
return static_cast<Logfile::IdType>(TRI_NewTickServer());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief ensure the wal logfiles directory is actually there
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int LogfileManager::ensureDirectory () {
|
|
// strip directory separator from path
|
|
// this is required for Windows
|
|
std::string directory(_directory);
|
|
|
|
TRI_ASSERT(! directory.empty());
|
|
|
|
if (directory[directory.size() - 1] == TRI_DIR_SEPARATOR_CHAR) {
|
|
directory = directory.substr(0, directory.size() - 1);
|
|
}
|
|
|
|
if (! basics::FileUtils::isDirectory(directory)) {
|
|
LOG_INFO("WAL directory '%s' does not exist. creating it...", directory.c_str());
|
|
|
|
int res;
|
|
if (! basics::FileUtils::createDirectory(directory, &res)) {
|
|
LOG_ERROR("could not create WAL directory: '%s': %s", directory.c_str(), TRI_last_error());
|
|
return TRI_ERROR_SYS_ERROR;
|
|
}
|
|
}
|
|
|
|
if (! basics::FileUtils::isDirectory(directory)) {
|
|
LOG_ERROR("WAL directory '%s' does not exist", directory.c_str());
|
|
return TRI_ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the absolute name of the shutdown file
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string LogfileManager::shutdownFilename () const {
|
|
return (*_databasePath) + TRI_DIR_SEPARATOR_STR + std::string("SHUTDOWN");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return an absolute filename for a logfile id
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string LogfileManager::logfileName (Logfile::IdType id) const {
|
|
return _directory + std::string("logfile-") + basics::StringUtils::itoa(id) + std::string(".db");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief return the current time as a string
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string LogfileManager::getTimeString () {
|
|
char buffer[32];
|
|
size_t len;
|
|
time_t tt = time(0);
|
|
struct tm tb;
|
|
TRI_gmtime(tt, &tb);
|
|
len = strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &tb);
|
|
|
|
return std::string(buffer, len);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- END-OF-FILE
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Local Variables:
|
|
// mode: outline-minor
|
|
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}"
|
|
// End:
|