1
0
Fork 0
arangodb/arangod/MMFiles/MMFilesV8Functions.cpp

585 lines
20 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// 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 "MMFilesV8Functions.h"
#include "Aql/Functions.h"
#include "Basics/Exceptions.h"
#include "Cluster/ClusterMethods.h"
#include "Cluster/ServerState.h"
#include "MMFiles/MMFilesCollection.h"
#include "MMFiles/MMFilesEngine.h"
#include "MMFiles/MMFilesLogfileManager.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/PhysicalCollection.h"
#include "StorageEngine/StorageEngine.h"
#include "Transaction/V8Context.h"
#include "Utils/OperationOptions.h"
#include "Utils/SingleCollectionTransaction.h"
#include "V8/v8-conv.h"
#include "V8/v8-globals.h"
#include "V8/v8-utils.h"
#include "V8Server/v8-externals.h"
#include "V8Server/v8-vocbaseprivate.h"
#include "VocBase/LogicalCollection.h"
#include <v8.h>
using namespace arangodb;
/// @brief rotate the active journal of the collection
static void JS_RotateVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
PREVENT_EMBEDDED_TRANSACTION();
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
SingleCollectionTransaction trx(
transaction::V8Context::Create(collection->vocbase(), true),
collection,
AccessMode::Type::WRITE
);
Result res = trx.begin();
if (!res.ok()) {
TRI_V8_THROW_EXCEPTION(res);
}
OperationResult result = trx.rotateActiveJournal(collection->name(), OperationOptions());
res.reset(result.result);
trx.finish(res);
if (!res.ok()) {
res.reset(res.errorNumber(), std::string("could not rotate journal: ") + res.errorMessage());
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
/// @brief returns information about the datafiles
/// the collection must be unloaded.
static void JS_DatafilesVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
TRI_THROW_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(collection);
// TODO: move this into engine
StorageEngine* engine = EngineSelectorFeature::ENGINE;
TRI_vocbase_col_status_e status = collection->getStatusLocked();
if (status != TRI_VOC_COL_STATUS_UNLOADED &&
status != TRI_VOC_COL_STATUS_CORRUPTED) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_UNLOADED);
}
MMFilesEngineCollectionFiles structure = dynamic_cast<MMFilesEngine*>(engine)->scanCollectionDirectory(collection->getPhysical()->path());
// build result
v8::Handle<v8::Object> result = v8::Object::New(isolate);
// journals
v8::Handle<v8::Array> journals = v8::Array::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "journals"), journals);
uint32_t i = 0;
for (auto& it : structure.journals) {
journals->Set(i++, TRI_V8_STD_STRING(isolate, it));
}
// compactors
v8::Handle<v8::Array> compactors = v8::Array::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "compactors"), compactors);
i = 0;
for (auto& it : structure.compactors) {
compactors->Set(i++, TRI_V8_STD_STRING(isolate, it));
}
// datafiles
v8::Handle<v8::Array> datafiles = v8::Array::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "datafiles"), datafiles);
i = 0;
for (auto& it : structure.datafiles) {
datafiles->Set(i++, TRI_V8_STD_STRING(isolate, it));
}
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END
}
/// @brief returns information about the datafiles
/// Returns information about the datafiles. The collection must be unloaded.
static void JS_DatafileScanVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
if (args.Length() != 1) {
TRI_V8_THROW_EXCEPTION_USAGE("datafileScan(<path>)");
}
std::string path = TRI_ObjectToString(args[0]);
v8::Handle<v8::Object> result;
{
TRI_vocbase_col_status_e status = collection->getStatusLocked();
if (status != TRI_VOC_COL_STATUS_UNLOADED &&
status != TRI_VOC_COL_STATUS_CORRUPTED) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_UNLOADED);
}
DatafileScan scan = MMFilesDatafile::scan(path);
// build result
result = v8::Object::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "currentSize"),
v8::Number::New(isolate, scan.currentSize));
result->Set(TRI_V8_ASCII_STRING(isolate, "maximalSize"),
v8::Number::New(isolate, scan.maximalSize));
result->Set(TRI_V8_ASCII_STRING(isolate, "endPosition"),
v8::Number::New(isolate, scan.endPosition));
result->Set(TRI_V8_ASCII_STRING(isolate, "numberMarkers"),
v8::Number::New(isolate, scan.numberMarkers));
result->Set(TRI_V8_ASCII_STRING(isolate, "status"),
v8::Number::New(isolate, scan.status));
result->Set(TRI_V8_ASCII_STRING(isolate, "isSealed"),
v8::Boolean::New(isolate, scan.isSealed));
v8::Handle<v8::Array> entries = v8::Array::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "entries"), entries);
uint32_t i = 0;
for (auto const& entry : scan.entries) {
v8::Handle<v8::Object> o = v8::Object::New(isolate);
o->Set(TRI_V8_ASCII_STRING(isolate, "position"),
v8::Number::New(isolate, entry.position));
o->Set(TRI_V8_ASCII_STRING(isolate, "size"),
v8::Number::New(isolate, entry.size));
o->Set(TRI_V8_ASCII_STRING(isolate, "realSize"),
v8::Number::New(isolate, entry.realSize));
o->Set(TRI_V8_ASCII_STRING(isolate, "tick"), TRI_V8UInt64String<TRI_voc_tick_t>(isolate, entry.tick));
o->Set(TRI_V8_ASCII_STRING(isolate, "type"),
v8::Number::New(isolate, static_cast<int>(entry.type)));
o->Set(TRI_V8_ASCII_STRING(isolate, "status"),
v8::Number::New(isolate, static_cast<int>(entry.status)));
if (!entry.key.empty()) {
o->Set(TRI_V8_ASCII_STRING(isolate, "key"), TRI_V8_STD_STRING(isolate, entry.key));
}
if (entry.typeName != nullptr) {
o->Set(TRI_V8_ASCII_STRING(isolate, "typeName"),
TRI_V8_ASCII_STRING(isolate, entry.typeName));
}
if (!entry.diagnosis.empty()) {
o->Set(TRI_V8_ASCII_STRING(isolate, "diagnosis"),
TRI_V8_STD_STRING(isolate, entry.diagnosis));
}
entries->Set(i++, o);
}
}
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END
}
/// @brief tries to repair a datafile
static void JS_TryRepairDatafileVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
TRI_THROW_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(collection);
if (args.Length() != 1) {
TRI_V8_THROW_EXCEPTION_USAGE("tryRepairDatafile(<datafile>)");
}
std::string path = TRI_ObjectToString(args[0]);
TRI_vocbase_col_status_e status = collection->getStatusLocked();
if (status != TRI_VOC_COL_STATUS_UNLOADED &&
status != TRI_VOC_COL_STATUS_CORRUPTED) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_UNLOADED);
}
bool result = MMFilesDatafile::tryRepair(path);
if (result) {
TRI_V8_RETURN_TRUE();
}
TRI_V8_RETURN_FALSE();
TRI_V8_TRY_CATCH_END
}
/// @brief truncates a datafile
static void JS_TruncateDatafileVocbaseCol(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
arangodb::LogicalCollection* collection =
TRI_UnwrapClass<arangodb::LogicalCollection>(args.Holder(), WRP_VOCBASE_COL_TYPE);
if (collection == nullptr) {
TRI_V8_THROW_EXCEPTION_INTERNAL("cannot extract collection");
}
TRI_THROW_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(collection);
if (args.Length() != 2) {
TRI_V8_THROW_EXCEPTION_USAGE("truncateDatafile(<datafile>, <size>)");
}
std::string path = TRI_ObjectToString(args[0]);
size_t size = (size_t)TRI_ObjectToInt64(args[1]);
TRI_vocbase_col_status_e status = collection->getStatusLocked();
if (status != TRI_VOC_COL_STATUS_UNLOADED &&
status != TRI_VOC_COL_STATUS_CORRUPTED) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_COLLECTION_NOT_UNLOADED);
}
int res = MMFilesDatafile::truncate(path, static_cast<uint32_t>(size));
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION_MESSAGE(res, "cannot truncate datafile");
}
TRI_V8_RETURN_UNDEFINED();
TRI_V8_TRY_CATCH_END
}
static void JS_PropertiesWal(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (args.Length() > 1 || (args.Length() == 1 && !args[0]->IsObject())) {
TRI_V8_THROW_EXCEPTION_USAGE("properties(<object>)");
}
auto l = MMFilesLogfileManager::instance();
if (args.Length() == 1) {
// set the properties
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(args[0]);
if (object->Has(TRI_V8_ASCII_STRING(isolate, "allowOversizeEntries"))) {
bool value = TRI_ObjectToBoolean(
object->Get(TRI_V8_ASCII_STRING(isolate, "allowOversizeEntries")));
l->allowOversizeEntries(value);
}
if (object->Has(TRI_V8_ASCII_STRING(isolate, "logfileSize"))) {
uint32_t value = static_cast<uint32_t>(TRI_ObjectToUInt64(
object->Get(TRI_V8_ASCII_STRING(isolate, "logfileSize")), true));
l->filesize(value);
}
if (object->Has(TRI_V8_ASCII_STRING(isolate, "historicLogfiles"))) {
uint32_t value = static_cast<uint32_t>(TRI_ObjectToUInt64(
object->Get(TRI_V8_ASCII_STRING(isolate, "historicLogfiles")), true));
l->historicLogfiles(value);
}
if (object->Has(TRI_V8_ASCII_STRING(isolate, "reserveLogfiles"))) {
uint32_t value = static_cast<uint32_t>(TRI_ObjectToUInt64(
object->Get(TRI_V8_ASCII_STRING(isolate, "reserveLogfiles")), true));
l->reserveLogfiles(value);
}
if (object->Has(TRI_V8_ASCII_STRING(isolate, "throttleWait"))) {
uint64_t value = TRI_ObjectToUInt64(
object->Get(TRI_V8_ASCII_STRING(isolate, "throttleWait")), true);
l->maxThrottleWait(value);
}
if (object->Has(TRI_V8_ASCII_STRING(isolate, "throttleWhenPending"))) {
uint64_t value = TRI_ObjectToUInt64(
object->Get(TRI_V8_ASCII_STRING(isolate, "throttleWhenPending")), true);
l->throttleWhenPending(value);
}
}
v8::Handle<v8::Object> result = v8::Object::New(isolate);
result->Set(TRI_V8_ASCII_STRING(isolate, "allowOversizeEntries"),
v8::Boolean::New(isolate, l->allowOversizeEntries()));
result->Set(TRI_V8_ASCII_STRING(isolate, "logfileSize"),
v8::Number::New(isolate, l->filesize()));
result->Set(TRI_V8_ASCII_STRING(isolate, "historicLogfiles"),
v8::Number::New(isolate, l->historicLogfiles()));
result->Set(TRI_V8_ASCII_STRING(isolate, "reserveLogfiles"),
v8::Number::New(isolate, l->reserveLogfiles()));
result->Set(TRI_V8_ASCII_STRING(isolate, "syncInterval"),
v8::Number::New(isolate, (double)l->syncInterval()));
result->Set(TRI_V8_ASCII_STRING(isolate, "throttleWait"),
v8::Number::New(isolate, (double)l->maxThrottleWait()));
result->Set(TRI_V8_ASCII_STRING(isolate, "throttleWhenPending"),
v8::Number::New(isolate, (double)l->throttleWhenPending()));
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END
}
static void JS_FlushWal(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
bool waitForSync = false;
bool waitForCollector = false;
bool writeShutdownFile = false;
double maxWaitTime = -1.0;
if (args.Length() > 0) {
if (args[0]->IsObject()) {
v8::Handle<v8::Object> obj = args[0]->ToObject();
if (obj->Has(TRI_V8_ASCII_STRING(isolate, "waitForSync"))) {
waitForSync =
TRI_ObjectToBoolean(obj->Get(TRI_V8_ASCII_STRING(isolate, "waitForSync")));
}
if (obj->Has(TRI_V8_ASCII_STRING(isolate, "waitForCollector"))) {
waitForCollector = TRI_ObjectToBoolean(
obj->Get(TRI_V8_ASCII_STRING(isolate, "waitForCollector")));
}
if (obj->Has(TRI_V8_ASCII_STRING(isolate, "writeShutdownFile"))) {
writeShutdownFile = TRI_ObjectToBoolean(
obj->Get(TRI_V8_ASCII_STRING(isolate, "writeShutdownFile")));
}
if (obj->Has(TRI_V8_ASCII_STRING(isolate, "maxWaitTime"))) {
maxWaitTime = TRI_ObjectToDouble(
obj->Get(TRI_V8_ASCII_STRING(isolate, "maxWaitTime")));
}
} else {
waitForSync = TRI_ObjectToBoolean(args[0]);
if (args.Length() > 1) {
waitForCollector = TRI_ObjectToBoolean(args[1]);
if (args.Length() > 2) {
writeShutdownFile = TRI_ObjectToBoolean(args[2]);
if (args.Length() > 3) {
maxWaitTime = TRI_ObjectToDouble(args[3]);
}
}
}
}
}
int res;
if (ServerState::instance()->isCoordinator()) {
res = flushWalOnAllDBServers(waitForSync, waitForCollector, maxWaitTime);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_TRUE();
}
res = MMFilesLogfileManager::instance()->flush(
waitForSync, waitForCollector, writeShutdownFile, maxWaitTime);
if (res != TRI_ERROR_NO_ERROR) {
if (res == TRI_ERROR_LOCK_TIMEOUT) {
// improved diagnostic message for this special case
TRI_V8_THROW_EXCEPTION_MESSAGE(res, "timed out waiting for WAL flush operation");
}
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}
/// @brief wait for WAL collector to finish operations for the specified
/// collection
static void JS_WaitCollectorWal(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
if (ServerState::instance()->isCoordinator()) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
}
auto& vocbase = GetContextVocBase(isolate);
if (args.Length() < 1) {
TRI_V8_THROW_EXCEPTION_USAGE(
"WAL_WAITCOLLECTOR(<collection-id>, <timeout>)");
}
std::string const name = TRI_ObjectToString(args[0]);
auto col = vocbase.lookupCollection(name);
if (col == nullptr) {
TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
}
double timeout = 30.0;
if (args.Length() > 1) {
timeout = TRI_ObjectToDouble(args[1]);
}
int res = MMFilesLogfileManager::instance()->waitForCollectorQueue(
col->id(), timeout
);
if (res != TRI_ERROR_NO_ERROR) {
TRI_V8_THROW_EXCEPTION(res);
}
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}
/// @brief get information about the currently running transactions
static void JS_TransactionsWal(
v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
auto const& info =
MMFilesLogfileManager::instance()->runningTransactions();
v8::Handle<v8::Object> result = v8::Object::New(isolate);
result->ForceSet(
TRI_V8_ASCII_STRING(isolate, "runningTransactions"),
v8::Number::New(isolate, static_cast<double>(std::get<0>(info))));
// lastCollectedId
{
auto value = std::get<1>(info);
if (value == UINT64_MAX) {
result->ForceSet(TRI_V8_ASCII_STRING(isolate, "minLastCollected"),
v8::Null(isolate));
} else {
result->ForceSet(TRI_V8_ASCII_STRING(isolate, "minLastCollected"),
TRI_V8UInt64String<TRI_voc_tick_t>(isolate, static_cast<TRI_voc_tick_t>(value)));
}
}
// lastSealedId
{
auto value = std::get<2>(info);
if (value == UINT64_MAX) {
result->ForceSet(TRI_V8_ASCII_STRING(isolate, "minLastSealed"), v8::Null(isolate));
} else {
result->ForceSet(TRI_V8_ASCII_STRING(isolate, "minLastSealed"),
TRI_V8UInt64String<TRI_voc_tick_t>(isolate, static_cast<TRI_voc_tick_t>(value)));
}
}
TRI_V8_RETURN(result);
TRI_V8_TRY_CATCH_END
}
static void JS_WaitForEstimatorSync(v8::FunctionCallbackInfo<v8::Value> const& args) {
TRI_V8_TRY_CATCH_BEGIN(isolate);
v8::HandleScope scope(isolate);
EngineSelectorFeature::ENGINE->waitForEstimatorSync(std::chrono::seconds(10));
TRI_V8_RETURN_TRUE();
TRI_V8_TRY_CATCH_END
}
void MMFilesV8Functions::registerResources() {
ISOLATE;
v8::HandleScope scope(isolate);
TRI_GET_GLOBALS();
// patch ArangoCollection object
v8::Handle<v8::ObjectTemplate> rt = v8::Handle<v8::ObjectTemplate>::New(isolate, v8g->VocbaseColTempl);
TRI_ASSERT(!rt.IsEmpty());
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "datafiles"),
JS_DatafilesVocbaseCol, true);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "datafileScan"),
JS_DatafileScanVocbaseCol, true);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "rotate"),
JS_RotateVocbaseCol);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "truncateDatafile"),
JS_TruncateDatafileVocbaseCol, true);
TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "tryRepairDatafile"),
JS_TryRepairDatafileVocbaseCol, true);
// add global WAL handling functions
TRI_AddGlobalFunctionVocbase(
isolate, TRI_V8_ASCII_STRING(isolate, "WAL_FLUSH"), JS_FlushWal, true);
TRI_AddGlobalFunctionVocbase(isolate,
TRI_V8_ASCII_STRING(isolate, "WAL_WAITCOLLECTOR"),
JS_WaitCollectorWal, true);
TRI_AddGlobalFunctionVocbase(isolate,
TRI_V8_ASCII_STRING(isolate, "WAL_PROPERTIES"),
JS_PropertiesWal, true);
TRI_AddGlobalFunctionVocbase(isolate,
TRI_V8_ASCII_STRING(isolate, "WAL_TRANSACTIONS"),
JS_TransactionsWal, true);
TRI_AddGlobalFunctionVocbase(isolate,
TRI_V8_ASCII_STRING(isolate,
"WAIT_FOR_ESTIMATOR_SYNC"),
JS_WaitForEstimatorSync, true);
}