From 1b8d79e6890d1229e5d1676729841ade944dbb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Thu, 13 Feb 2025 14:06:43 +0100 Subject: [PATCH 01/35] Refactor runHandlerStateMachine into a coroutine --- arangod/Aql/RestAqlHandler.cpp | 26 +-- arangod/Aql/RestAqlHandler.h | 3 +- arangod/GeneralServer/RestHandler.cpp | 170 ++++++++---------- arangod/GeneralServer/RestHandler.h | 27 +-- .../RestHandler/RestVocbaseBaseHandler.cpp | 14 +- arangod/RestHandler/RestVocbaseBaseHandler.h | 6 +- lib/Async/CMakeLists.txt | 8 +- lib/Async/include/Async/SuspensionSemaphore.h | 83 +++++++++ lib/Async/src/SuspensionSemaphore.cpp | 21 +++ 9 files changed, 211 insertions(+), 147 deletions(-) create mode 100644 lib/Async/include/Async/SuspensionSemaphore.h create mode 100644 lib/Async/src/SuspensionSemaphore.cpp diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index ce07d1359144..67886b7c2a7d 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -68,11 +68,7 @@ RestAqlHandler::RestAqlHandler(ArangodServer& server, GeneralRequest* request, TRI_ASSERT(_queryRegistry != nullptr); } -RestAqlHandler::~RestAqlHandler() { - if (_logContextQueryIdEntry) { - LogContext::Current::popEntry(_logContextQueryIdEntry); - } -} +RestAqlHandler::~RestAqlHandler() {} // POST method for /_api/aql/setup (internal) // Only available on DBServers in the Cluster. @@ -158,9 +154,6 @@ futures::Future RestAqlHandler::setupClusterQuery() { _logContextQueryIdValue = LogContext::makeValue() .with(clusterQueryId) .share(); - TRI_ASSERT(_logContextQueryIdEntry == nullptr); - _logContextQueryIdEntry = - LogContext::Current::pushValues(_logContextQueryIdValue); VPackSlice lockInfoSlice = querySlice.get("lockInfo"); @@ -417,9 +410,6 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, _logContextQueryIdValue = LogContext::makeValue() .with(idString) .share(); - TRI_ASSERT(_logContextQueryIdEntry == nullptr); - _logContextQueryIdEntry = - LogContext::Current::pushValues(_logContextQueryIdValue); } if (!_engine) { // the PUT verb @@ -474,14 +464,13 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, return RestStatus::DONE; } - -void RestAqlHandler::prepareExecute(bool isContinue) { - RestVocbaseBaseHandler::prepareExecute(isContinue); +auto RestAqlHandler::prepareExecute(bool isContinue) + -> std::vector> { + auto vector = RestVocbaseBaseHandler::prepareExecute(isContinue); if (_logContextQueryIdValue != nullptr) { - TRI_ASSERT(_logContextQueryIdEntry == nullptr); - _logContextQueryIdEntry = - LogContext::Current::pushValues(_logContextQueryIdValue); + vector.emplace_back(_logContextQueryIdValue); } + return vector; } // executes the handler @@ -607,9 +596,6 @@ void RestAqlHandler::shutdownExecute(bool isFinalized) noexcept { << "Ignoring exception during rest handler shutdown: " << ex.what(); } - if (_logContextQueryIdEntry) { - LogContext::Current::popEntry(_logContextQueryIdEntry); - } RestVocbaseBaseHandler::shutdownExecute(isFinalized); } diff --git a/arangod/Aql/RestAqlHandler.h b/arangod/Aql/RestAqlHandler.h index 91bedf286dd2..1037c70d53d3 100644 --- a/arangod/Aql/RestAqlHandler.h +++ b/arangod/Aql/RestAqlHandler.h @@ -52,7 +52,8 @@ class RestAqlHandler : public RestVocbaseBaseHandler { RequestLane lane() const override final; RestStatus execute() override; RestStatus continueExecute() override; - void prepareExecute(bool isContinue) override; + [[nodiscard]] auto prepareExecute(bool isContinue) + -> std::vector> override; void shutdownExecute(bool isFinalized) noexcept override; class Route { diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index c836e6632e01..0ce1ab3b3027 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -25,9 +25,8 @@ #include "ApplicationFeatures/ApplicationServer.h" #include "Auth/TokenCache.h" -#include "Basics/RecursiveLocker.h" -#include "Basics/debugging.h" #include "Basics/dtrace-wrapper.h" +#include "Basics/error.h" #include "Cluster/ClusterFeature.h" #include "Cluster/ClusterInfo.h" #include "Cluster/ServerState.h" @@ -47,6 +46,7 @@ #include "VocBase/Identifiers/TransactionId.h" #include "VocBase/ticks.h" +#include #include #include #include @@ -412,77 +412,49 @@ void RestHandler::handleExceptionPtr(std::exception_ptr eptr) noexcept try { // can do here to signal this problem. } -void RestHandler::runHandlerStateMachine() { - // _executionMutex has to be locked here - TRI_ASSERT(_sendResponseCallback); - - while (true) { - switch (_state) { - case HandlerState::PREPARE: - prepareEngine(); - break; - - case HandlerState::EXECUTE: { - executeEngine(/*isContinue*/ false); - if (_state == HandlerState::PAUSED) { - shutdownExecute(false); - LOG_TOPIC("23a33", DEBUG, Logger::COMMUNICATION) - << "Pausing rest handler execution " << this; - return; // stop state machine - } - break; - } - - case HandlerState::CONTINUED: { - executeEngine(/*isContinue*/ true); - if (_state == HandlerState::PAUSED) { - shutdownExecute(/*isFinalized*/ false); - LOG_TOPIC("23727", DEBUG, Logger::COMMUNICATION) - << "Pausing rest handler execution " << this; - return; // stop state machine - } - break; - } - - case HandlerState::PAUSED: - LOG_TOPIC("ae26f", DEBUG, Logger::COMMUNICATION) - << "Resuming rest handler execution " << this; - _state = HandlerState::CONTINUED; - break; +auto RestHandler::runHandlerStateMachine() -> futures::Future { + auto fail = [&]() { + TRI_ASSERT(_state == HandlerState::FAILED); + _statistics.SET_REQUEST_END(); + // Callback may stealStatistics! + _sendResponseCallback(this); - case HandlerState::FINALIZE: - _statistics.SET_REQUEST_END(); - - // shutdownExecute is noexcept - shutdownExecute(true); // may not be moved down + shutdownExecute(false); + }; - _state = HandlerState::DONE; + TRI_ASSERT(_state == HandlerState::PREPARE); + auto logScope = prepareEngine(); + // TODO put logScope into scopeGuard + std::vector scopeGuard; + if (_state == HandlerState::FAILED) { + co_return fail(); + } + TRI_ASSERT(_state == HandlerState::EXECUTE); + co_await executeEngine(); + if (_state == HandlerState::FAILED) { + co_return fail(); + } - // compress response if required - compressResponse(); - // Callback may stealStatistics! - _sendResponseCallback(this); - break; + TRI_ASSERT(_state == HandlerState::FINALIZE); + _statistics.SET_REQUEST_END(); - case HandlerState::FAILED: - _statistics.SET_REQUEST_END(); - // Callback may stealStatistics! - _sendResponseCallback(this); + // shutdownExecute is noexcept + shutdownExecute(true); // may not be moved down - shutdownExecute(false); - return; + _state = HandlerState::DONE; - case HandlerState::DONE: - return; - } - } + // compress response if required + compressResponse(); + // Callback may stealStatistics! + _sendResponseCallback(this); } // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- -void RestHandler::prepareEngine() { +auto RestHandler::prepareEngine() + -> std::vector> { // set end immediately so we do not get negative statistics _statistics.SET_REQUEST_START_END(); @@ -491,13 +463,13 @@ void RestHandler::prepareEngine() { Exception err(TRI_ERROR_REQUEST_CANCELED); handleError(err); - return; + return {}; } try { - prepareExecute(false); + auto logScope = prepareExecute(false); _state = HandlerState::EXECUTE; - return; + return logScope; } catch (Exception const& ex) { handleError(ex); } catch (std::exception const& ex) { @@ -509,47 +481,28 @@ void RestHandler::prepareEngine() { } _state = HandlerState::FAILED; + return {}; } -void RestHandler::prepareExecute(bool isContinue) { - _logContextEntry = LogContext::Current::pushValues(_logContextScopeValues); +auto RestHandler::prepareExecute(bool isContinue) + -> std::vector> { + return {_logContextScopeValues}; } -void RestHandler::shutdownExecute(bool isFinalized) noexcept { - LogContext::Current::popEntry(_logContextEntry); -} +void RestHandler::shutdownExecute(bool isFinalized) noexcept {} -/// Execute the rest handler state machine. Retry the wakeup, -/// returns true if _state == PAUSED, false otherwise -bool RestHandler::wakeupHandler() { - std::lock_guard lock{_executionMutex}; - if (_state == HandlerState::PAUSED) { - runHandlerStateMachine(); - } - return _state == HandlerState::PAUSED; -} +/// For older RestHandlers, that implement execute() and continueExecute() with +/// WAITING instead of executeAsync(). Calling wakeupHandler() will continue the +/// execution by calling continueExecute(). +bool RestHandler::wakeupHandler() { return _suspensionSemaphore.notify(); } -void RestHandler::executeEngine(bool isContinue) { +auto RestHandler::executeEngine() -> async { DTRACE_PROBE1(arangod, RestHandlerExecuteEngine, this); ExecContextScope scope( basics::downCast(_request->requestContext())); try { - RestStatus result = RestStatus::DONE; - if (isContinue) { - // only need to run prepareExecute() again when we are continuing - // otherwise prepareExecute() was already run in the PREPARE phase - prepareExecute(true); - result = continueExecute(); - } else { - result = execute(); - } - - if (result == RestStatus::WAITING) { - _state = HandlerState::PAUSED; // wait for someone to continue the state - // machine - return; - } + co_await executeAsync(); if (_response == nullptr) { Exception err(TRI_ERROR_INTERNAL, "no response received from handler"); @@ -557,7 +510,7 @@ void RestHandler::executeEngine(bool isContinue) { } _state = HandlerState::FINALIZE; - return; + co_return; } catch (Exception const& ex) { #ifdef ARANGODB_ENABLE_MAINTAINER_MODE LOG_TOPIC("11928", WARN, arangodb::Logger::FIXME) @@ -791,16 +744,33 @@ void RestHandler::resetResponse(rest::ResponseCode code) { _response->reset(code); } +// Fallback implementation for old RestHandlers that implement execute() and +// continueExecute() instead of executeAsync(). futures::Future RestHandler::executeAsync() { - THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); + auto state = execute(); + + while (state == RestStatus::WAITING) { + // Get the number of wakeups. We call continueExecute() up to that many + // times before suspending again. + auto n = co_await _suspensionSemaphore.await(); + for (auto i = 0; i < n && state == RestStatus::WAITING; ++i) { + state = continueExecute(); + } + } } -RestStatus RestHandler::execute() { return waitForFuture(executeAsync()); } +RestStatus RestHandler::execute() { + TRI_ASSERT(false); + THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); +} void RestHandler::runHandler( std::function responseCallback) { - TRI_ASSERT(_state == HandlerState::PREPARE); _sendResponseCallback = std::move(responseCallback); - std::lock_guard guard(_executionMutex); - runHandlerStateMachine(); + + runHandlerStateMachine().thenFinal( + [self = shared_from_this()](auto&& tryResult) noexcept { + // TODO handle exceptions + std::move(tryResult).throwIfFailed(); + }); } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index c90b00204676..7a8d12120ee5 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -23,6 +23,7 @@ #pragma once +#include "Async/SuspensionSemaphore.h" #include "Basics/ResultT.h" #include "Futures/Unit.h" #include "GeneralServer/RequestLane.h" @@ -50,6 +51,9 @@ template class Future; } // namespace futures +template +struct async; + class GeneralRequest; class RequestStatistics; class Result; @@ -102,10 +106,10 @@ class RestHandler : public std::enable_shared_from_this { void setIsAsyncRequest() noexcept { _isAsyncRequest = true; } /// Execute the rest handler state machine - void runHandler(std::function responseCallback); + virtual void runHandler( + std::function responseCallback); - /// Execute the rest handler state machine. Retry the wakeup, - /// returns true if _state == PAUSED, false otherwise + /// Continue execution of a suspended (via WAITING) rest handler state machine bool wakeupHandler(); /// @brief forwards the request to the appropriate server @@ -122,7 +126,8 @@ class RestHandler : public std::enable_shared_from_this { RequestLane determineRequestLane(); - virtual void prepareExecute(bool isContinue); + [[nodiscard]] virtual auto prepareExecute(bool isContinue) + -> std::vector>; virtual RestStatus execute(); virtual futures::Future executeAsync(); virtual RestStatus continueExecute() { return RestStatus::DONE; } @@ -175,15 +180,12 @@ class RestHandler : public std::enable_shared_from_this { /// handler state machine HandlerState state() const { return _state; } - private: - void runHandlerStateMachine(); + auto runHandlerStateMachine() -> futures::Future; - void prepareEngine(); + [[nodiscard]] auto prepareEngine() + -> std::vector>; /// @brief Executes the RestHandler - /// May set the state to PAUSED, FINALIZE or FAILED - /// If isContinue == true it will call continueExecute() - /// otherwise execute() will be called - void executeEngine(bool isContinue); + auto executeEngine() -> async; void compressResponse(); protected: @@ -205,6 +207,8 @@ class RestHandler : public std::enable_shared_from_this { mutable std::atomic_uint8_t _executionCounter{0}; mutable RestStatus _followupRestStatus; + SuspensionSemaphore _suspensionSemaphore; + std::function _sendResponseCallback; uint64_t _handlerId; @@ -222,7 +226,6 @@ class RestHandler : public std::enable_shared_from_this { RequestLane _lane; std::shared_ptr _logContextScopeValues; - LogContext::EntryPtr _logContextEntry; protected: metrics::GaugeCounterGuard _currentRequestsSizeTracker; diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.cpp b/arangod/RestHandler/RestVocbaseBaseHandler.cpp index 1327a2f960be..fe3be4b0f68b 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.cpp +++ b/arangod/RestHandler/RestVocbaseBaseHandler.cpp @@ -216,15 +216,11 @@ RestVocbaseBaseHandler::RestVocbaseBaseHandler(ArangodServer& server, TRI_ASSERT(request->requestContext()); } -void RestVocbaseBaseHandler::prepareExecute(bool isContinue) { - RestHandler::prepareExecute(isContinue); - _logContextVocbaseEntry = - LogContext::Current::pushValues(_scopeVocbaseValues); -} - -void RestVocbaseBaseHandler::shutdownExecute(bool isFinalized) noexcept { - LogContext::Current::popEntry(_logContextVocbaseEntry); - RestHandler::shutdownExecute(isFinalized); +auto RestVocbaseBaseHandler::prepareExecute(bool isContinue) + -> std::vector> { + auto values = RestHandler::prepareExecute(isContinue); + values.emplace_back(_scopeVocbaseValues); + return values; } RestVocbaseBaseHandler::~RestVocbaseBaseHandler() = default; diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.h b/arangod/RestHandler/RestVocbaseBaseHandler.h index 2a47b4d51759..41ee7f534a25 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.h +++ b/arangod/RestHandler/RestVocbaseBaseHandler.h @@ -129,9 +129,8 @@ class RestVocbaseBaseHandler : public RestBaseHandler { _context.cancel(); } - void prepareExecute(bool isContinue) override; - - void shutdownExecute(bool isFinalized) noexcept override; + [[nodiscard]] auto prepareExecute(bool isContinue) + -> std::vector> override; protected: /// @brief returns the short id of the server which should handle this request @@ -206,7 +205,6 @@ class RestVocbaseBaseHandler : public RestBaseHandler { private: std::shared_ptr _scopeVocbaseValues; - LogContext::EntryPtr _logContextVocbaseEntry; }; } // namespace arangodb diff --git a/lib/Async/CMakeLists.txt b/lib/Async/CMakeLists.txt index 865fc6bbb965..c88d03fc3f5b 100644 --- a/lib/Async/CMakeLists.txt +++ b/lib/Async/CMakeLists.txt @@ -1,4 +1,10 @@ -add_library(arango_async INTERFACE) +add_library(arango_async STATIC + include/Async/async.h + include/Async/coro-utils.h + include/Async/expected.h + include/Async/SuspensionSemaphore.h + src/SuspensionSemaphore.cpp +) target_include_directories(arango_async INTERFACE include diff --git a/lib/Async/include/Async/SuspensionSemaphore.h b/lib/Async/include/Async/SuspensionSemaphore.h new file mode 100644 index 000000000000..4ba28ddc6189 --- /dev/null +++ b/lib/Async/include/Async/SuspensionSemaphore.h @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2024-2024 ArangoDB 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 Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +namespace arangodb { + +class Scheduler; + +// SuspensionSemaphore::await() returns an awaitable that suspends until +// notify() is called, and which returns the number of notifies. +struct SuspensionSemaphore { + // returns true if still suspended + bool notify() { + auto counter = _counter.load(); + do { + if (counter == -1) { + auto res = _counter.compare_exchange_weak(counter, 1); + if (res) { + // NOTE This doesn't need to be posted on the scheduler, as notify() + // will be called by SharedQueryState::queueHandler(), which already + // posted it before calling. + _c.resume(); + return false; + } + } else { + auto res = _counter.compare_exchange_weak(counter, counter + 1); + if (res) { + return true; + } + } + } while (true); + } + + auto await() { + struct Awaitable { + bool await_ready() const noexcept { + return _semaphore->_counter.load(std::memory_order_relaxed) > 0; + } + [[nodiscard]] std::int64_t await_resume() const noexcept { + return _semaphore->_counter.exchange(0); + } + bool await_suspend(std::coroutine_handle<> c) { + _semaphore->_c = c; + auto counter = std::int64_t{}; + return _semaphore->_counter.compare_exchange_strong(counter, -1); + } + + SuspensionSemaphore* _semaphore; + }; + + return Awaitable{this}; + } + + std::atomic _counter; + std::coroutine_handle<> _c; + Scheduler* _scheduler; +}; +} // namespace arangodb diff --git a/lib/Async/src/SuspensionSemaphore.cpp b/lib/Async/src/SuspensionSemaphore.cpp new file mode 100644 index 000000000000..c00ab4b3e048 --- /dev/null +++ b/lib/Async/src/SuspensionSemaphore.cpp @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2024-2024 ArangoDB 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 Tobias Gödderz +//////////////////////////////////////////////////////////////////////////////// From b0d551439d8d05e664b6c71326a8a748a7cd735d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 7 Mar 2025 16:47:20 +0100 Subject: [PATCH 02/35] Add LogContext scope to runHandlerStateMachine --- arangod/GeneralServer/RestHandler.cpp | 6 +++--- lib/Logger/LogContext.h | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 0ce1ab3b3027..64036c99524c 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -423,9 +423,9 @@ auto RestHandler::runHandlerStateMachine() -> futures::Future { }; TRI_ASSERT(_state == HandlerState::PREPARE); - auto logScope = prepareEngine(); - // TODO put logScope into scopeGuard - std::vector scopeGuard; + auto logContextValues = prepareEngine(); + auto const logScopeGuard = + LogContext::Accessor::ScopedValue(std::move(logContextValues)); if (_state == HandlerState::FAILED) { co_return fail(); } diff --git a/lib/Logger/LogContext.h b/lib/Logger/LogContext.h index 7ef89bf5ef12..47e89d43196b 100644 --- a/lib/Logger/LogContext.h +++ b/lib/Logger/LogContext.h @@ -35,6 +35,7 @@ #include #include #include +#include #include "Basics/debugging.h" @@ -569,6 +570,12 @@ struct LogContext::Accessor::ScopedValue { explicit ScopedValue(std::shared_ptr v) { appendEntry>(std::move(v)); } + explicit ScopedValue(std::vector>&& vs) { + for (auto&& v : vs) { + appendEntry>(std::move(v)); + } + vs.clear(); + } template explicit ScopedValue(ValueBuilder&& v) { From 0b4ff24438156e8cc708ef1e8be5170b6fd9bab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 7 Mar 2025 16:47:58 +0100 Subject: [PATCH 03/35] Keep LogContext in RestIndexHandler --- arangod/RestHandler/RestIndexHandler.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arangod/RestHandler/RestIndexHandler.cpp b/arangod/RestHandler/RestIndexHandler.cpp index 2adaeb90c664..9c44041ca62f 100644 --- a/arangod/RestHandler/RestIndexHandler.cpp +++ b/arangod/RestHandler/RestIndexHandler.cpp @@ -538,9 +538,10 @@ RestStatus RestIndexHandler::createIndex() { std::unique_lock locker(_mutex); // the following callback is executed in a background thread - auto cb = [this, self = shared_from_this(), - execContext = std::move(execContext), collection = std::move(coll), - body = std::move(indexInfo)] { + auto cb = withLogContext([this, self = shared_from_this(), + execContext = std::move(execContext), + collection = std::move(coll), + body = std::move(indexInfo)] { ExecContextScope scope(std::move(execContext)); { std::unique_lock locker(_mutex); @@ -576,7 +577,7 @@ RestStatus RestIndexHandler::createIndex() { // notify REST handler SchedulerFeature::SCHEDULER->queue(RequestLane::INTERNAL_LOW, [self]() { self->wakeupHandler(); }); - }; + }); // start background thread _createInBackgroundData.thread = std::make_unique(std::move(cb)); From 8180a6dae8bd7b90a81584293b68cb4b90d042b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Sat, 3 May 2025 14:39:53 +0200 Subject: [PATCH 04/35] Fixed some issues - Temporarily disabled LogContext (ScopedValue in rest handler state machine) - Made more stuff async - Fixed an issue by moving a coro from Future to async, because futures don't pass ExecContext as coros (just as an awaitable) - Fixed an issue by moving WAITING<->coro adapter further up the stack --- arangod/GeneralServer/RestHandler.cpp | 10 ++- arangod/GeneralServer/RestHandler.h | 30 +++++++-- arangod/RestHandler/RestCursorHandler.cpp | 66 ++++++++++++------- arangod/RestHandler/RestCursorHandler.h | 13 ++-- .../RestHandler/RestSimpleQueryHandler.cpp | 17 +++-- arangod/RestHandler/RestSimpleQueryHandler.h | 2 +- lib/Logger/LogContext.h | 14 ++-- 7 files changed, 104 insertions(+), 48 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 64036c99524c..97b7a671c420 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -424,8 +424,14 @@ auto RestHandler::runHandlerStateMachine() -> futures::Future { TRI_ASSERT(_state == HandlerState::PREPARE); auto logContextValues = prepareEngine(); - auto const logScopeGuard = - LogContext::Accessor::ScopedValue(std::move(logContextValues)); + // TODO This is currently broken. + // For one, because the ScopedValue constructor from a vector is broken. + // Second, because somewhere the along the line the LogContext gets + // changed, and we have one with a different tail before the destructor + // gets called. The latter is planned to being addressed in a different + // PR. + // auto const logScopeGuard = + // LogContext::Accessor::ScopedValue(std::move(logContextValues)); if (_state == HandlerState::FAILED) { co_return fail(); } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 7a8d12120ee5..3d0763a91b48 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -24,6 +24,7 @@ #pragma once #include "Async/SuspensionSemaphore.h" +#include "Async/async.h" #include "Basics/ResultT.h" #include "Futures/Unit.h" #include "GeneralServer/RequestLane.h" @@ -33,6 +34,7 @@ #include "Statistics/RequestStatistics.h" #include +#include #include #include #include @@ -51,14 +53,11 @@ template class Future; } // namespace futures -template -struct async; - class GeneralRequest; class RequestStatistics; class Result; -enum class RestStatus { DONE, WAITING, FAIL }; +enum class RestStatus { DONE, WAITING }; namespace rest { class RestHandler : public std::enable_shared_from_this { @@ -207,6 +206,29 @@ class RestHandler : public std::enable_shared_from_this { mutable std::atomic_uint8_t _executionCounter{0}; mutable RestStatus _followupRestStatus; + protected: + // TODO Move this in a separate header, side-by-side with SuspensionSemaphore? + // Note: _suspensionSemaphore.notify() must be called for this to resume. + // RestHandler::wakeupHandler() does that, and can be called e.g. by the + // SharedQueryState's wakeup handler (for AQL-related code). + template + requires requires(F f) { { f() } -> std::same_as; } + [[nodiscard]] auto waitingFunToCoro(F&& funArg) -> async { + auto fun = std::forward(funArg); + auto state = fun(); + + while (state == RestStatus::WAITING) { + // Get the number of wakeups. We call fun() up to that many + // times before suspending again. + auto n = co_await _suspensionSemaphore.await(); + for (auto i = 0; i < n && state == RestStatus::WAITING; ++i) { + state = fun(); + } + } + co_return; + } + + private: SuspensionSemaphore _suspensionSemaphore; std::function _sendResponseCallback; diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index 7f18c621234c..652217c5aec8 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -27,6 +27,7 @@ #include "Aql/Query.h" #include "Aql/QueryRegistry.h" #include "Aql/SharedQueryState.h" +#include "Async/async.h" #include "Basics/Exceptions.h" #include "Basics/ScopeGuard.h" #include "Basics/StaticStrings.h" @@ -73,31 +74,41 @@ RequestLane RestCursorHandler::lane() const { return RequestLane::CLIENT_AQL; } -RestStatus RestCursorHandler::execute() { +futures::Future RestCursorHandler::executeAsync() { // extract the sub-request type rest::RequestType const type = _request->requestType(); - if (type == rest::RequestType::POST) { if (_request->suffixes().size() == 0) { // POST /_api/cursor - return createQueryCursor(); + + co_await createQueryCursor(); + co_return; } else if (_request->suffixes().size() == 1) { // POST /_api/cursor/cursor-id - return modifyQueryCursor(); + + co_await waitingFunToCoro(std::bind(&std::decay_t::modifyQueryCursor, this)); + co_return; } // POST /_api/cursor/cursor-id/batch-id - return showLatestBatch(); + co_await showLatestBatch(); + co_return; } else if (type == rest::RequestType::PUT) { - return modifyQueryCursor(); + co_await waitingFunToCoro(std::bind(&std::decay_t::modifyQueryCursor, this)); + co_return; } else if (type == rest::RequestType::DELETE_REQ) { - return deleteQueryCursor(); + // TODO if this does not wait, it does not need to return RestStatus - + // and otherwise should be a coroutine. + auto status = deleteQueryCursor(); + TRI_ASSERT(status == RestStatus::DONE); + co_return; } generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } RestStatus RestCursorHandler::continueExecute() { + TRI_ASSERT(false); if (wasCanceled()) { generateError(rest::ResponseCode::GONE, TRI_ERROR_QUERY_KILLED); return RestStatus::DONE; @@ -172,7 +183,7 @@ void RestCursorHandler::cancel() { /// /// return If true, we need to continue processing, /// If false we are done (error or stream) -futures::Future RestCursorHandler::registerQueryOrCursor( +async RestCursorHandler::registerQueryOrCursor( velocypack::Slice slice, transaction::OperationOrigin operationOrigin) { TRI_ASSERT(_query == nullptr); @@ -240,7 +251,8 @@ futures::Future RestCursorHandler::registerQueryOrCursor( _cursor->setWakeupHandler(withLogContext( [self = shared_from_this()]() { return self->wakeupHandler(); })); - co_return generateCursorResult(rest::ResponseCode::CREATED); + co_await waitingFunToCoro(std::bind(&std::decay_t::generateCursorResult, this, rest::ResponseCode::CREATED)); + co_return RestStatus::DONE; } // non-stream case. Execute query, then build a cursor @@ -258,7 +270,10 @@ futures::Future RestCursorHandler::registerQueryOrCursor( } registerQuery(std::move(query)); - co_return processQuery(); + + co_await waitingFunToCoro(std::bind(&std::decay_t::processQuery, this)); + + co_return RestStatus::DONE; } /// @brief Process the query registered in _query. @@ -618,7 +633,7 @@ RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { if (_cursor->isRetriable()) { _cursor->setLastQueryBatchObject(builder.steal()); } - return RestStatus::FAIL; + return RestStatus::DONE; } generateResult(code, builder.slice(), std::move(ctx)); @@ -643,13 +658,13 @@ RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { return RestStatus::DONE; } -RestStatus RestCursorHandler::createQueryCursor() { +async RestCursorHandler::createQueryCursor() { std::vector const& suffixes = _request->suffixes(); if (!suffixes.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting POST /_api/cursor"); - return RestStatus::DONE; + co_return; } bool parseSuccess = false; @@ -657,28 +672,28 @@ RestStatus RestCursorHandler::createQueryCursor() { if (!parseSuccess) { // error message generated in parseVPackBody - return RestStatus::DONE; + co_return; } if (body.isEmptyObject()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_CORRUPTED_JSON); - return RestStatus::DONE; + co_return; } TRI_ASSERT(_query == nullptr); - return waitForFuture(registerQueryOrCursor( - body, transaction::OperationOriginAQL{"running AQL query"})); + co_await registerQueryOrCursor( + body, transaction::OperationOriginAQL{"running AQL query"}); + co_return; } - /// @brief shows the batch given by if it's the last cached batch /// response on a retry, and does't advance cursor -RestStatus RestCursorHandler::showLatestBatch() { +async RestCursorHandler::showLatestBatch() { std::vector const& suffixes = _request->suffixes(); if (suffixes.size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting POST /_api/cursor//"); - return RestStatus::DONE; + co_return; } uint64_t batchId = basics::StringUtils::uint64(suffixes[1]); @@ -687,7 +702,7 @@ RestStatus RestCursorHandler::showLatestBatch() { if (_cursor == nullptr) { // error response already built here - return RestStatus::DONE; + co_return; } _cursor->setWakeupHandler(withLogContext( @@ -698,13 +713,14 @@ RestStatus RestCursorHandler::showLatestBatch() { // if x == y + 1, advance the cursor and return the new batch // otherwise return error if (_cursor->isNextBatchId(batchId)) { - return generateCursorResult(rest::ResponseCode::OK); + co_await waitingFunToCoro(std::bind(&std::decay_t::generateCursorResult, this, rest::ResponseCode::OK)); + co_return; } if (!_cursor->isCurrentBatchId(batchId)) { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_CURSOR_NOT_FOUND, "batch id not found"); - return RestStatus::DONE; + co_return; } auto buffer = _cursor->getLastBatch(); @@ -714,7 +730,7 @@ RestStatus RestCursorHandler::showLatestBatch() { generateResult(rest::ResponseCode::OK, VPackSlice(buffer->data()), _cursor->context()); - return RestStatus::DONE; + co_return; } RestStatus RestCursorHandler::modifyQueryCursor() { diff --git a/arangod/RestHandler/RestCursorHandler.h b/arangod/RestHandler/RestCursorHandler.h index ac6e256add7d..8390bf5d5d70 100644 --- a/arangod/RestHandler/RestCursorHandler.h +++ b/arangod/RestHandler/RestCursorHandler.h @@ -34,6 +34,9 @@ #include #include +// TODO forward declare, move include to .cpp +#include + namespace arangodb { namespace velocypack { class Builder; @@ -58,7 +61,7 @@ class RestCursorHandler : public RestVocbaseBaseHandler { char const* name() const override { return "RestCursorHandler"; } RequestLane lane() const override final; - virtual RestStatus execute() override; + auto executeAsync() -> futures::Future override; virtual RestStatus continueExecute() override; void shutdownExecute(bool isFinalized) noexcept override; @@ -68,7 +71,7 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// @brief register the query either as streaming cursor or in _query /// the query is not executed here. /// this method is also used by derived classes - [[nodiscard]] futures::Future registerQueryOrCursor( + [[nodiscard]] async registerQueryOrCursor( velocypack::Slice body, transaction::OperationOrigin operationOrigin); /// @brief Process the query registered in _query. @@ -105,9 +108,9 @@ class RestCursorHandler : public RestVocbaseBaseHandler { RestStatus generateCursorResult(rest::ResponseCode code); /// @brief create a cursor and return the first results - RestStatus createQueryCursor(); + async createQueryCursor(); - /// @brief return the next results from an existing cursor + /// @brief return the next results from an existing cursor RestStatus modifyQueryCursor(); /// @brief dispose an existing cursor @@ -115,7 +118,7 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// @brief show last batch on retry if `allowRetry` flag is true, doesn't /// advance cursor - RestStatus showLatestBatch(); + async showLatestBatch(); /// @brief look up cursor by id. side-effect: populates _cursor in case cursor /// was found. in case cursor was not found, writes an error into the response diff --git a/arangod/RestHandler/RestSimpleQueryHandler.cpp b/arangod/RestHandler/RestSimpleQueryHandler.cpp index 6885c6faeee2..05516025962a 100644 --- a/arangod/RestHandler/RestSimpleQueryHandler.cpp +++ b/arangod/RestHandler/RestSimpleQueryHandler.cpp @@ -42,27 +42,34 @@ RestSimpleQueryHandler::RestSimpleQueryHandler( arangodb::aql::QueryRegistry* queryRegistry) : RestCursorHandler(server, request, response, queryRegistry) {} -RestStatus RestSimpleQueryHandler::execute() { +auto RestSimpleQueryHandler::executeAsync() -> futures::Future { // extract the sub-request type auto const type = _request->requestType(); + // TODO remove the RestStatus return value of all callees here std::string const& prefix = _request->requestPath(); if (type == rest::RequestType::PUT) { if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_PATH) { // all query - return waitForFuture(allDocuments()); + auto status = co_await allDocuments(); + TRI_ASSERT(status == RestStatus::DONE); + co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_KEYS_PATH) { // all-keys query - return waitForFuture(allDocumentKeys()); + auto status = co_await allDocumentKeys(); + TRI_ASSERT(status == RestStatus::DONE); + co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_BY_EXAMPLE) { // by-example query - return waitForFuture(byExample()); + auto status = co_await byExample(); + TRI_ASSERT(status == RestStatus::DONE); + co_return; } } generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } futures::Future RestSimpleQueryHandler::allDocuments() { diff --git a/arangod/RestHandler/RestSimpleQueryHandler.h b/arangod/RestHandler/RestSimpleQueryHandler.h index 61922e08f94a..21df3ba98c7f 100644 --- a/arangod/RestHandler/RestSimpleQueryHandler.h +++ b/arangod/RestHandler/RestSimpleQueryHandler.h @@ -36,7 +36,7 @@ class RestSimpleQueryHandler : public RestCursorHandler { arangodb::aql::QueryRegistry*); public: - RestStatus execute() override final; + auto executeAsync() -> futures::Future override final; char const* name() const override final { return "RestSimpleQueryHandler"; } private: diff --git a/lib/Logger/LogContext.h b/lib/Logger/LogContext.h index 47e89d43196b..dbf50f5e4f8d 100644 --- a/lib/Logger/LogContext.h +++ b/lib/Logger/LogContext.h @@ -570,12 +570,14 @@ struct LogContext::Accessor::ScopedValue { explicit ScopedValue(std::shared_ptr v) { appendEntry>(std::move(v)); } - explicit ScopedValue(std::vector>&& vs) { - for (auto&& v : vs) { - appendEntry>(std::move(v)); - } - vs.clear(); - } + // TODO This constructor is broken, as the destructor will still pop only a + // single entry! +// explicit ScopedValue(std::vector>&& vs) { +// for (auto&& v : vs) { +// appendEntry>(std::move(v)); +// } +// vs.clear(); +// } template explicit ScopedValue(ValueBuilder&& v) { From 23b1a70e84f5b2815ac2ea93ded6e22bdcb80262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Mon, 5 May 2025 14:06:50 +0200 Subject: [PATCH 05/35] Refactored RestSimple- and RestSimpleQueryHandler --- arangod/GeneralServer/RestHandler.cpp | 9 +- arangod/GeneralServer/RestHandler.h | 1 - arangod/RestHandler/RestCursorHandler.cpp | 129 +++++++----------- arangod/RestHandler/RestCursorHandler.h | 17 ++- arangod/RestHandler/RestSimpleHandler.cpp | 38 +++--- arangod/RestHandler/RestSimpleHandler.h | 8 +- .../RestHandler/RestSimpleQueryHandler.cpp | 32 ++--- arangod/RestHandler/RestSimpleQueryHandler.h | 6 +- 8 files changed, 101 insertions(+), 139 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 97b7a671c420..cc04edbbfaa1 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -755,13 +755,8 @@ void RestHandler::resetResponse(rest::ResponseCode code) { futures::Future RestHandler::executeAsync() { auto state = execute(); - while (state == RestStatus::WAITING) { - // Get the number of wakeups. We call continueExecute() up to that many - // times before suspending again. - auto n = co_await _suspensionSemaphore.await(); - for (auto i = 0; i < n && state == RestStatus::WAITING; ++i) { - state = continueExecute(); - } + if (state == RestStatus::WAITING) { + co_await waitingFunToCoro(std::bind(&std::decay_t::continueExecute, this)); } } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 3d0763a91b48..8f442c6d1b43 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -38,7 +38,6 @@ #include #include #include -#include namespace arangodb { namespace application_features { diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index 652217c5aec8..059982c2207c 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -86,14 +86,14 @@ futures::Future RestCursorHandler::executeAsync() { } else if (_request->suffixes().size() == 1) { // POST /_api/cursor/cursor-id - co_await waitingFunToCoro(std::bind(&std::decay_t::modifyQueryCursor, this)); + co_await modifyQueryCursor(); co_return; } // POST /_api/cursor/cursor-id/batch-id co_await showLatestBatch(); co_return; } else if (type == rest::RequestType::PUT) { - co_await waitingFunToCoro(std::bind(&std::decay_t::modifyQueryCursor, this)); + co_await modifyQueryCursor(); co_return; } else if (type == rest::RequestType::DELETE_REQ) { // TODO if this does not wait, it does not need to return RestStatus - @@ -107,48 +107,6 @@ futures::Future RestCursorHandler::executeAsync() { co_return; } -RestStatus RestCursorHandler::continueExecute() { - TRI_ASSERT(false); - if (wasCanceled()) { - generateError(rest::ResponseCode::GONE, TRI_ERROR_QUERY_KILLED); - return RestStatus::DONE; - } - - if (!_response->isResponseEmpty()) { - // an exception occurred in one of the suspension points - return RestStatus::DONE; - } - - // extract the sub-request type - rest::RequestType const type = _request->requestType(); - - if (_query != nullptr) { // non-stream query - if (type == rest::RequestType::POST || type == rest::RequestType::PUT) { - return processQuery(); - } - } else if (_cursor) { // stream cursor query - if (type == rest::RequestType::POST) { - if (_request->suffixes().size() == 0) { - // POST /_api/cursor - return generateCursorResult(rest::ResponseCode::CREATED); - } - // POST /_api/cursor/cursor-id - return generateCursorResult(ResponseCode::OK); - } else if (type == rest::RequestType::PUT) { - if (_request->requestPath() == SIMPLE_QUERY_ALL_PATH) { - // RestSimpleQueryHandler::allDocuments uses PUT for cursor creation - return generateCursorResult(ResponseCode::CREATED); - } - return generateCursorResult(ResponseCode::OK); - } - } - - // Other parts of the query cannot be paused - TRI_ASSERT(false) << requestToString(type) << " " << _request->fullUrl() - << " " << _request->parameters(); - return RestStatus::DONE; -} - void RestCursorHandler::shutdownExecute(bool isFinalized) noexcept { auto sg = arangodb::scopeGuard( [&]() noexcept { RestVocbaseBaseHandler::shutdownExecute(isFinalized); }); @@ -183,18 +141,18 @@ void RestCursorHandler::cancel() { /// /// return If true, we need to continue processing, /// If false we are done (error or stream) -async RestCursorHandler::registerQueryOrCursor( +async RestCursorHandler::registerQueryOrCursor( velocypack::Slice slice, transaction::OperationOrigin operationOrigin) { TRI_ASSERT(_query == nullptr); if (!slice.isObject()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_EMPTY); - co_return RestStatus::DONE; + co_return; } VPackSlice querySlice = slice.get("query"); if (!querySlice.isString() || querySlice.getStringLength() == 0) { generateError(rest::ResponseCode::BAD, TRI_ERROR_QUERY_EMPTY); - co_return RestStatus::DONE; + co_return; } VPackSlice bindVars = slice.get("bindVars"); @@ -202,7 +160,7 @@ async RestCursorHandler::registerQueryOrCursor( if (!bindVars.isObject() && !bindVars.isNull()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting object for "); - co_return RestStatus::DONE; + co_return; } } @@ -240,7 +198,7 @@ async RestCursorHandler::registerQueryOrCursor( if (count) { generateError(Result(TRI_ERROR_BAD_PARAMETER, "cannot use 'count' option for a streaming query")); - co_return RestStatus::DONE; + co_return; } CursorRepository* cursors = _vocbase.cursorRepository(); @@ -251,8 +209,8 @@ async RestCursorHandler::registerQueryOrCursor( _cursor->setWakeupHandler(withLogContext( [self = shared_from_this()]() { return self->wakeupHandler(); })); - co_await waitingFunToCoro(std::bind(&std::decay_t::generateCursorResult, this, rest::ResponseCode::CREATED)); - co_return RestStatus::DONE; + co_await generateCursorResult(rest::ResponseCode::CREATED); + co_return; } // non-stream case. Execute query, then build a cursor @@ -262,7 +220,7 @@ async RestCursorHandler::registerQueryOrCursor( TRI_ASSERT(ss != nullptr); if (ss == nullptr) { generateError(Result(TRI_ERROR_INTERNAL, "invalid query state")); - co_return RestStatus::DONE; + co_return; } ss->setWakeupHandler(withLogContext( @@ -271,15 +229,15 @@ async RestCursorHandler::registerQueryOrCursor( registerQuery(std::move(query)); - co_await waitingFunToCoro(std::bind(&std::decay_t::processQuery, this)); + co_await processQuery(); - co_return RestStatus::DONE; + co_return; } /// @brief Process the query registered in _query. /// The function is repeatable, so whenever we need to WAIT /// in AQL we can post a handler calling this function again. -RestStatus RestCursorHandler::processQuery() { +async RestCursorHandler::processQuery() { auto query = [this]() { std::unique_lock mutexLocker{_queryLock}; @@ -295,22 +253,24 @@ RestStatus RestCursorHandler::processQuery() { // always clean up auto guard = scopeGuard([this]() noexcept { unregisterQuery(); }); - // continue handler is registered earlier - auto state = query->execute(_queryResult); + co_await waitingFunToCoro([&]{ + auto state = query->execute(_queryResult); - if (state == aql::ExecutionState::WAITING) { - guard.cancel(); - return RestStatus::WAITING; - } - TRI_ASSERT(state == aql::ExecutionState::DONE); + if (state == aql::ExecutionState::WAITING) { + return RestStatus::WAITING; + } + TRI_ASSERT(state == aql::ExecutionState::DONE); + return RestStatus::DONE; + }); } // We cannot get into HASMORE here, or we would lose results. - return handleQueryResult(); + co_await handleQueryResult(); + co_return; } // non stream case, result is complete -RestStatus RestCursorHandler::handleQueryResult() { +async RestCursorHandler::handleQueryResult() { TRI_ASSERT(_query == nullptr); if (_queryResult.result.fail()) { if (_queryResult.result.is(TRI_ERROR_REQUEST_CANCELED) || @@ -406,7 +366,7 @@ RestStatus RestCursorHandler::handleQueryResult() { // trx.commit()) without the server code for freeing the resources and the // client code racing for who's first - return RestStatus::DONE; + co_return; } else { // result is bigger than batchSize, and a cursor will be created CursorRepository* cursors = _vocbase.cursorRepository(); @@ -417,7 +377,8 @@ RestStatus RestCursorHandler::handleQueryResult() { ttl, count, retriable); // throws if a coordinator soft shutdown is ongoing - return generateCursorResult(rest::ResponseCode::CREATED); + co_await generateCursorResult(rest::ResponseCode::CREATED); + co_return; } } @@ -603,7 +564,7 @@ void RestCursorHandler::buildOptions(velocypack::Slice slice) { /// @brief append the contents of the cursor into the response body /// this function will also take care of the cursor and return it to the /// registry if required -RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { +async RestCursorHandler::generateCursorResult(rest::ResponseCode code) { TRI_ASSERT(_cursor != nullptr); // dump might delete the cursor @@ -612,13 +573,20 @@ RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { VPackBuilder builder; builder.openObject(/*unindexed*/ true); - auto const [state, r] = _cursor->dump(builder); + auto r = Result(); - if (state == aql::ExecutionState::WAITING) { - builder.clear(); - TRI_ASSERT(r.ok()); - return RestStatus::WAITING; - } + co_await waitingFunToCoro([&](){ + auto const [state, result] = _cursor->dump(builder); + + if (state == aql::ExecutionState::WAITING) { + TRI_ASSERT(r.ok()); + return RestStatus::WAITING; + } + + r = result; + + return RestStatus::DONE; + }); if (_cursor->allowDirtyReads()) { setOutgoingDirtyReadsHeader(true); @@ -633,7 +601,7 @@ RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { if (_cursor->isRetriable()) { _cursor->setLastQueryBatchObject(builder.steal()); } - return RestStatus::DONE; + co_return; } generateResult(code, builder.slice(), std::move(ctx)); @@ -655,7 +623,7 @@ RestStatus RestCursorHandler::generateCursorResult(rest::ResponseCode code) { generateError(r); } - return RestStatus::DONE; + co_return; } async RestCursorHandler::createQueryCursor() { @@ -713,7 +681,7 @@ async RestCursorHandler::showLatestBatch() { // if x == y + 1, advance the cursor and return the new batch // otherwise return error if (_cursor->isNextBatchId(batchId)) { - co_await waitingFunToCoro(std::bind(&std::decay_t::generateCursorResult, this, rest::ResponseCode::OK)); + co_await generateCursorResult(rest::ResponseCode::OK); co_return; } @@ -733,13 +701,13 @@ async RestCursorHandler::showLatestBatch() { co_return; } -RestStatus RestCursorHandler::modifyQueryCursor() { +async RestCursorHandler::modifyQueryCursor() { std::vector const& suffixes = _request->suffixes(); if (suffixes.size() != 1) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting POST /_api/cursor/"); - return RestStatus::DONE; + co_return; } // the call to lookupCursor will populate _cursor if the cursor can be @@ -748,13 +716,14 @@ RestStatus RestCursorHandler::modifyQueryCursor() { lookupCursor(suffixes[0]); if (_cursor == nullptr) { - return RestStatus::DONE; + co_return; } _cursor->setWakeupHandler(withLogContext( [self = shared_from_this()]() { return self->wakeupHandler(); })); - return generateCursorResult(rest::ResponseCode::OK); + co_await generateCursorResult(rest::ResponseCode::OK); + co_return; } RestStatus RestCursorHandler::deleteQueryCursor() { diff --git a/arangod/RestHandler/RestCursorHandler.h b/arangod/RestHandler/RestCursorHandler.h index 8390bf5d5d70..25aa3933cddb 100644 --- a/arangod/RestHandler/RestCursorHandler.h +++ b/arangod/RestHandler/RestCursorHandler.h @@ -34,9 +34,6 @@ #include #include -// TODO forward declare, move include to .cpp -#include - namespace arangodb { namespace velocypack { class Builder; @@ -48,6 +45,9 @@ class QueryRegistry; struct QueryResult; } // namespace aql +template +struct async; + class Cursor; /// @brief cursor request handler @@ -62,7 +62,6 @@ class RestCursorHandler : public RestVocbaseBaseHandler { RequestLane lane() const override final; auto executeAsync() -> futures::Future override; - virtual RestStatus continueExecute() override; void shutdownExecute(bool isFinalized) noexcept override; void cancel() override final; @@ -71,13 +70,13 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// @brief register the query either as streaming cursor or in _query /// the query is not executed here. /// this method is also used by derived classes - [[nodiscard]] async registerQueryOrCursor( + async registerQueryOrCursor( velocypack::Slice body, transaction::OperationOrigin operationOrigin); /// @brief Process the query registered in _query. /// The function is repeatable, so whenever we need to WAIT /// in AQL we can post a handler calling this function again. - RestStatus processQuery(); + async processQuery(); /// @brief returns the short id of the server which should handle this request ResultT> forwardingTarget() override; @@ -89,7 +88,7 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// guaranteed /// to not be interrupted and is guaranteed to get a complete /// queryResult. - virtual RestStatus handleQueryResult(); + virtual async handleQueryResult(); private: /// @brief register the currently running query @@ -105,13 +104,13 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// @brief append the contents of the cursor into the response body /// this function will also take care of the cursor and return it to the /// registry if required - RestStatus generateCursorResult(rest::ResponseCode code); + async generateCursorResult(rest::ResponseCode code); /// @brief create a cursor and return the first results async createQueryCursor(); /// @brief return the next results from an existing cursor - RestStatus modifyQueryCursor(); + async modifyQueryCursor(); /// @brief dispose an existing cursor RestStatus deleteQueryCursor(); diff --git a/arangod/RestHandler/RestSimpleHandler.cpp b/arangod/RestHandler/RestSimpleHandler.cpp index 25d8ebcd0911..26a9b7db3849 100644 --- a/arangod/RestHandler/RestSimpleHandler.cpp +++ b/arangod/RestHandler/RestSimpleHandler.cpp @@ -45,7 +45,7 @@ RestSimpleHandler::RestSimpleHandler( : RestCursorHandler(server, request, response, queryRegistry), _silent(true) {} -RestStatus RestSimpleHandler::execute() { +auto RestSimpleHandler::executeAsync() -> futures::Future { // extract the request type auto const type = _request->requestType(); @@ -53,35 +53,37 @@ RestStatus RestSimpleHandler::execute() { bool parsingSuccess = false; VPackSlice body = this->parseVPackBody(parsingSuccess); if (!parsingSuccess) { - return RestStatus::DONE; + co_return; } if (!body.isObject()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting JSON object body"); - return RestStatus::DONE; + co_return; } std::string const& prefix = _request->requestPath(); if (prefix == RestVocbaseBaseHandler::SIMPLE_REMOVE_PATH) { - return waitForFuture(removeByKeys(body)); + co_await removeByKeys(body); + co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_LOOKUP_PATH) { - return waitForFuture(lookupByKeys(body)); + co_await lookupByKeys(body); + co_return; } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "unsupported value for "); } - return RestStatus::DONE; + co_return; } generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } -futures::Future RestSimpleHandler::removeByKeys( +async RestSimpleHandler::removeByKeys( VPackSlice const& slice) { TRI_ASSERT(slice.isObject()); std::string collectionName; @@ -91,7 +93,7 @@ futures::Future RestSimpleHandler::removeByKeys( if (!value.isString()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting string for "); - co_return RestStatus::DONE; + co_return; } collectionName = value.copyString(); @@ -110,7 +112,7 @@ futures::Future RestSimpleHandler::removeByKeys( if (!keys.isArray()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting array for "); - co_return RestStatus::DONE; + co_return; } bool waitForSync = false; @@ -162,7 +164,7 @@ futures::Future RestSimpleHandler::removeByKeys( transaction::OperationOriginREST{"removing documents by keys"}); } -RestStatus RestSimpleHandler::handleQueryResult() { +async RestSimpleHandler::handleQueryResult() { if (_queryResult.result.fail()) { if (_queryResult.result.is(TRI_ERROR_REQUEST_CANCELED) || (_queryResult.result.is(TRI_ERROR_QUERY_KILLED) && wasCanceled())) { @@ -171,7 +173,7 @@ RestStatus RestSimpleHandler::handleQueryResult() { } else { generateError(_queryResult.result); } - return RestStatus::DONE; + co_return; } // extract the request type @@ -181,10 +183,10 @@ RestStatus RestSimpleHandler::handleQueryResult() { if (type == rest::RequestType::PUT) { if (prefix == RestVocbaseBaseHandler::SIMPLE_REMOVE_PATH) { handleQueryResultRemoveByKeys(); - return RestStatus::DONE; + co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_LOOKUP_PATH) { handleQueryResultLookupByKeys(); - return RestStatus::DONE; + co_return; } } @@ -193,7 +195,7 @@ RestStatus RestSimpleHandler::handleQueryResult() { TRI_ASSERT(false); generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } void RestSimpleHandler::handleQueryResultRemoveByKeys() { @@ -248,7 +250,7 @@ void RestSimpleHandler::handleQueryResultLookupByKeys() { _queryResult.context); } -futures::Future RestSimpleHandler::lookupByKeys( +async RestSimpleHandler::lookupByKeys( VPackSlice const& slice) { if (response() == nullptr) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid response"); @@ -261,7 +263,7 @@ futures::Future RestSimpleHandler::lookupByKeys( if (!value.isString()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting string for "); - co_return RestStatus::DONE; + co_return; } collectionName = value.copyString(); @@ -282,7 +284,7 @@ futures::Future RestSimpleHandler::lookupByKeys( if (!keys.isArray()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting array for "); - co_return RestStatus::DONE; + co_return; } std::string const aql( diff --git a/arangod/RestHandler/RestSimpleHandler.h b/arangod/RestHandler/RestSimpleHandler.h index e8715e2f5c05..df874ea22d1a 100644 --- a/arangod/RestHandler/RestSimpleHandler.h +++ b/arangod/RestHandler/RestSimpleHandler.h @@ -36,7 +36,7 @@ class RestSimpleHandler : public RestCursorHandler { aql::QueryRegistry*); public: - RestStatus execute() override final; + auto executeAsync() -> futures::Future override final; char const* name() const override final { return "RestSimpleHandler"; } private: @@ -47,7 +47,7 @@ class RestSimpleHandler : public RestCursorHandler { /// queryResult. ////////////////////////////////////////////////////////////////////////////// - RestStatus handleQueryResult() override final; + async handleQueryResult() override final; ////////////////////////////////////////////////////////////////////////////// /// @brief handle result of a remove-by-keys query @@ -65,13 +65,13 @@ class RestSimpleHandler : public RestCursorHandler { /// @brief execute a batch remove operation ////////////////////////////////////////////////////////////////////////////// - futures::Future removeByKeys(VPackSlice const&); + async removeByKeys(VPackSlice const&); ////////////////////////////////////////////////////////////////////////////// /// @brief execute a batch lookup operation ////////////////////////////////////////////////////////////////////////////// - futures::Future lookupByKeys(VPackSlice const&); + async lookupByKeys(VPackSlice const&); private: ////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/RestHandler/RestSimpleQueryHandler.cpp b/arangod/RestHandler/RestSimpleQueryHandler.cpp index 05516025962a..e0197d655750 100644 --- a/arangod/RestHandler/RestSimpleQueryHandler.cpp +++ b/arangod/RestHandler/RestSimpleQueryHandler.cpp @@ -51,18 +51,15 @@ auto RestSimpleQueryHandler::executeAsync() -> futures::Future { if (type == rest::RequestType::PUT) { if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_PATH) { // all query - auto status = co_await allDocuments(); - TRI_ASSERT(status == RestStatus::DONE); + co_await allDocuments(); co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_KEYS_PATH) { // all-keys query - auto status = co_await allDocumentKeys(); - TRI_ASSERT(status == RestStatus::DONE); + co_await allDocumentKeys(); co_return; } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_BY_EXAMPLE) { // by-example query - auto status = co_await byExample(); - TRI_ASSERT(status == RestStatus::DONE); + co_await byExample(); co_return; } } @@ -72,12 +69,12 @@ auto RestSimpleQueryHandler::executeAsync() -> futures::Future { co_return; } -futures::Future RestSimpleQueryHandler::allDocuments() { +async RestSimpleQueryHandler::allDocuments() { bool parseSuccess = false; VPackSlice const body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - co_return RestStatus::DONE; + co_return; } std::string collectionName; @@ -93,7 +90,7 @@ futures::Future RestSimpleQueryHandler::allDocuments() { if (collectionName.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting string for "); - co_return RestStatus::DONE; + co_return; } auto col = _vocbase.lookupCollection(collectionName); @@ -155,20 +152,21 @@ futures::Future RestSimpleQueryHandler::allDocuments() { data.close(); // now run the actual query and handle the result - co_return co_await registerQueryOrCursor( + co_await registerQueryOrCursor( data.slice(), transaction::OperationOriginREST{"fetching all documents"}); + co_return; } ////////////////////////////////////////////////////////////////////////////// /// @brief return a cursor with all document keys from the collection ////////////////////////////////////////////////////////////////////////////// -futures::Future RestSimpleQueryHandler::allDocumentKeys() { +async RestSimpleQueryHandler::allDocumentKeys() { bool parseSuccess = false; VPackSlice const body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - co_return RestStatus::DONE; + co_return; } std::string collectionName; @@ -184,7 +182,7 @@ futures::Future RestSimpleQueryHandler::allDocumentKeys() { if (collectionName.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting string for "); - co_return RestStatus::DONE; + co_return; } auto col = _vocbase.lookupCollection(collectionName); @@ -267,17 +265,17 @@ static void buildExampleQuery(VPackBuilder& result, std::string const& cname, /// @brief return a cursor with all documents matching the example ////////////////////////////////////////////////////////////////////////////// -futures::Future RestSimpleQueryHandler::byExample() { +async RestSimpleQueryHandler::byExample() { bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - co_return RestStatus::DONE; + co_return; } if (!body.isObject() || !body.get("example").isObject()) { generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER); - co_return RestStatus::DONE; + co_return; } // velocypack will throw an exception for negative numbers @@ -300,7 +298,7 @@ futures::Future RestSimpleQueryHandler::byExample() { if (cname.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_TYPE_ERROR, "expecting string for "); - co_return RestStatus::DONE; + co_return; } auto col = _vocbase.lookupCollection(cname); diff --git a/arangod/RestHandler/RestSimpleQueryHandler.h b/arangod/RestHandler/RestSimpleQueryHandler.h index 21df3ba98c7f..2a79be0a384a 100644 --- a/arangod/RestHandler/RestSimpleQueryHandler.h +++ b/arangod/RestHandler/RestSimpleQueryHandler.h @@ -40,8 +40,8 @@ class RestSimpleQueryHandler : public RestCursorHandler { char const* name() const override final { return "RestSimpleQueryHandler"; } private: - futures::Future allDocuments(); - futures::Future allDocumentKeys(); - futures::Future byExample(); + async allDocuments(); + async allDocumentKeys(); + async byExample(); }; } // namespace arangodb From 316759886a6e615d3a8b547e9e7e746d04c9a897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 09:50:09 +0200 Subject: [PATCH 06/35] Refactored several RestHandlers to use executeAsync instead of execute+waitForFuture The following handlers are changed: - RestAgencyHandler - RestAdminLogHandler - RestDocumentHandler - RestGraphHandler - RestImportHandler - RestLogHandler - RestLogInternalHandler - RestMetricsHandler - RestReplicationHandler - RestTransactionHandler - RestUsageMetricsHandler --- arangod/Agency/RestAgencyHandler.cpp | 242 ++++--- arangod/Agency/RestAgencyHandler.h | 38 +- arangod/GeneralServer/RestHandler.h | 3 + arangod/RestHandler/RestAdminLogHandler.cpp | 86 ++- arangod/RestHandler/RestAdminLogHandler.h | 6 +- arangod/RestHandler/RestDocumentHandler.cpp | 58 +- arangod/RestHandler/RestDocumentHandler.h | 21 +- arangod/RestHandler/RestGraphHandler.cpp | 13 +- arangod/RestHandler/RestGraphHandler.h | 2 +- arangod/RestHandler/RestImportHandler.cpp | 13 +- arangod/RestHandler/RestImportHandler.h | 2 +- arangod/RestHandler/RestLogHandler.cpp | 592 ++++++++---------- arangod/RestHandler/RestLogHandler.h | 89 +-- .../RestHandler/RestLogInternalHandler.cpp | 51 +- arangod/RestHandler/RestLogInternalHandler.h | 6 +- arangod/RestHandler/RestMetricsHandler.cpp | 91 ++- arangod/RestHandler/RestMetricsHandler.h | 4 +- .../RestHandler/RestReplicationHandler.cpp | 47 +- arangod/RestHandler/RestReplicationHandler.h | 2 +- .../RestHandler/RestTransactionHandler.cpp | 13 +- arangod/RestHandler/RestTransactionHandler.h | 2 +- .../RestHandler/RestUsageMetricsHandler.cpp | 72 +-- arangod/RestHandler/RestUsageMetricsHandler.h | 4 +- 23 files changed, 696 insertions(+), 761 deletions(-) diff --git a/arangod/Agency/RestAgencyHandler.cpp b/arangod/Agency/RestAgencyHandler.cpp index 1ac8520a9c60..8877777fa1fa 100644 --- a/arangod/Agency/RestAgencyHandler.cpp +++ b/arangod/Agency/RestAgencyHandler.cpp @@ -33,6 +33,7 @@ #include "StorageEngine/EngineSelectorFeature.h" #include "Transaction/StandaloneContext.h" +#include #include #include @@ -53,29 +54,26 @@ RestAgencyHandler::RestAgencyHandler(ArangodServer& server, GeneralResponse* response, Agent* agent) : RestVocbaseBaseHandler(server, request, response), _agent(agent) {} -RestStatus RestAgencyHandler::reportErrorEmptyRequest() { +void RestAgencyHandler::reportErrorEmptyRequest() { LOG_TOPIC("46536", WARN, Logger::AGENCY) << "Empty request to public agency interface."; generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; } -RestStatus RestAgencyHandler::reportTooManySuffices() { +void RestAgencyHandler::reportTooManySuffices() { LOG_TOPIC("ef6ae", WARN, Logger::AGENCY) << "Too many suffixes. Agency public interface takes one path."; generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; } -RestStatus RestAgencyHandler::reportUnknownMethod() { +void RestAgencyHandler::reportUnknownMethod() { LOG_TOPIC("9b810", WARN, Logger::AGENCY) << "Public REST interface has no method " << _request->suffixes()[0]; generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; } -RestStatus RestAgencyHandler::reportMessage(rest::ResponseCode code, - std::string const& message) { +void RestAgencyHandler::reportMessage(rest::ResponseCode code, + std::string const& message) { LOG_TOPIC("8a454", DEBUG, Logger::AGENCY) << message; Builder body; { @@ -83,7 +81,6 @@ RestStatus RestAgencyHandler::reportMessage(rest::ResponseCode code, body.add("message", VPackValue(message)); } generateResult(code, body.slice()); - return RestStatus::DONE; } void RestAgencyHandler::redirectRequest(std::string const& leaderId) { @@ -99,7 +96,7 @@ void RestAgencyHandler::redirectRequest(std::string const& leaderId) { } } -RestStatus RestAgencyHandler::handleTransient() { +void RestAgencyHandler::handleTransient() { // Must be a POST request if (_request->requestType() != rest::RequestType::POST) { return reportMethodNotAllowed(); @@ -151,8 +148,6 @@ RestStatus RestAgencyHandler::handleTransient() { redirectRequest(ret.redirect); } } - - return RestStatus::DONE; } /// Poll from agency starting with start. @@ -160,80 +155,73 @@ RestStatus RestAgencyHandler::handleTransient() { /// { accepted:bool, result: { firstIndex, commitIndex, logs:[{index, log}] } } /// if !accepted, lost leadership /// else respond -RestStatus RestAgencyHandler::pollIndex(index_t const& start, - double const& timeout) { +auto RestAgencyHandler::pollIndex(index_t const& start, double const& timeout) + -> async { auto pollResult = _agent->poll(start, timeout); if (std::get<1>(pollResult)) { - return waitForFuture( - std::move(std::get<0>(pollResult)) - .thenValue([this, start](std::shared_ptr&& rb) { - VPackSlice res = rb->slice(); - - if (res.isObject() && res.hasKey("result")) { - if (res.hasKey(StaticStrings::Error)) { // leadership loss - generateError(rest::ResponseCode::SERVICE_UNAVAILABLE, - TRI_ERROR_HTTP_SERVICE_UNAVAILABLE, - "No leader"); - return; - } - - VPackSlice slice = res.get("result"); - - if (slice.hasKey("log")) { - VPackBuilder builder; - { - VPackObjectBuilder ob(&builder); - builder.add(StaticStrings::Error, VPackValue(false)); - builder.add("code", VPackValue(int(ResponseCode::OK))); - builder.add(VPackValue("result")); - VPackObjectBuilder r(&builder); - if (!slice.get("firstIndex").isNumber()) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, - "invalid first log index."); - return; - } else if (slice.get("firstIndex").getNumber() > - start) { - generateError( - rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, - "first log index is greater than requested."); - return; - } - uint64_t firstIndex = - slice.get("firstIndex").getNumber(), - i = 0; - - builder.add("commitIndex", slice.get("commitIndex")); - VPackSlice logs = slice.get("log"); - if (start <= firstIndex) { - builder.add("firstIndex", logs[i].get("index")); - } - builder.add(VPackValue("log")); - VPackArrayBuilder a(&builder); - if (start <= firstIndex) { - uint64_t i = start - firstIndex; - for (; i < logs.length(); ++i) { - builder.add(logs[i]); - } - } - } - generateResult(rest::ResponseCode::OK, - std::move(*builder.steal())); - } else { - generateResult(rest::ResponseCode::OK, - std::move(*rb->steal())); - } - } else { - generateError(rest::ResponseCode::SERVICE_UNAVAILABLE, - TRI_ERROR_HTTP_SERVICE_UNAVAILABLE, "No leader"); - } - }) - .thenError([this](std::exception const& e) { + auto rb = co_await std::move(std::get<0>(pollResult)); + + try { + VPackSlice res = rb->slice(); + + if (res.isObject() && res.hasKey("result")) { + if (res.hasKey(StaticStrings::Error)) { // leadership loss + generateError(rest::ResponseCode::SERVICE_UNAVAILABLE, + TRI_ERROR_HTTP_SERVICE_UNAVAILABLE, "No leader"); + co_return; + } + + VPackSlice slice = res.get("result"); + + if (slice.hasKey("log")) { + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + builder.add(StaticStrings::Error, VPackValue(false)); + builder.add("code", VPackValue(int(ResponseCode::OK))); + builder.add(VPackValue("result")); + VPackObjectBuilder r(&builder); + if (!slice.get("firstIndex").isNumber()) { + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR, + "invalid first log index."); + co_return; + } else if (slice.get("firstIndex").getNumber() > start) { generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + TRI_ERROR_HTTP_SERVER_ERROR, + "first log index is greater than requested."); + co_return; + } + uint64_t firstIndex = slice.get("firstIndex").getNumber(), + i = 0; + + builder.add("commitIndex", slice.get("commitIndex")); + VPackSlice logs = slice.get("log"); + if (start <= firstIndex) { + builder.add("firstIndex", logs[i].get("index")); + } + builder.add(VPackValue("log")); + VPackArrayBuilder a(&builder); + if (start <= firstIndex) { + uint64_t i = start - firstIndex; + for (; i < logs.length(); ++i) { + builder.add(logs[i]); + } + } + } + generateResult(rest::ResponseCode::OK, std::move(*builder.steal())); + } else { + generateResult(rest::ResponseCode::OK, std::move(*rb->steal())); + } + } else { + generateError(rest::ResponseCode::SERVICE_UNAVAILABLE, + TRI_ERROR_HTTP_SERVICE_UNAVAILABLE, "No leader"); + } + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR, e.what()); + } } else { auto const& leader = std::get<2>(pollResult); if (leader == NO_LEADER) { @@ -242,7 +230,7 @@ RestStatus RestAgencyHandler::pollIndex(index_t const& start, } else { redirectRequest(leader); } - return RestStatus::DONE; + co_return; } } @@ -267,10 +255,10 @@ static bool readValue(GeneralRequest const& req, char const* name, T& val) { } } // namespace -RestStatus RestAgencyHandler::handlePoll() { +auto RestAgencyHandler::handlePoll() -> async { // GET only if (_request->requestType() != rest::RequestType::GET) { - return reportMethodNotAllowed(); + co_return reportMethodNotAllowed(); } // WARNING //////////////////////////////////////////////////// @@ -278,12 +266,12 @@ RestStatus RestAgencyHandler::handlePoll() { auto const& leaderId = _agent->leaderID(); if (!_agent->leading()) { // Redirect to leader if (leaderId == NO_LEADER) { - return reportMessage(rest::ResponseCode::SERVICE_UNAVAILABLE, - "No leader"); + co_return reportMessage(rest::ResponseCode::SERVICE_UNAVAILABLE, + "No leader"); } else { TRI_ASSERT(leaderId != _agent->id()); redirectRequest(leaderId); - return RestStatus::DONE; + co_return; } } @@ -294,10 +282,10 @@ RestStatus RestAgencyHandler::handlePoll() { readValue(*_request, "index", index); readValue(*_request, "timeout", timeout); - return pollIndex(index, timeout); + co_return co_await pollIndex(index, timeout); } -RestStatus RestAgencyHandler::handleStores() { +void RestAgencyHandler::handleStores() { if (_request->requestType() == rest::RequestType::GET) { Builder body; { @@ -325,13 +313,13 @@ RestStatus RestAgencyHandler::handleStores() { } } generateResult(rest::ResponseCode::OK, body.slice()); - return RestStatus::DONE; + return; } return reportMethodNotAllowed(); } -RestStatus RestAgencyHandler::handleStore() { +void RestAgencyHandler::handleStore() { if (_request->requestType() == rest::RequestType::POST) { velocypack::Slice query = _request->payload(); arangodb::consensus::index_t index = 0; @@ -348,14 +336,13 @@ RestStatus RestAgencyHandler::handleStore() { } catch (...) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER); } - - return RestStatus::DONE; + return; } return reportMethodNotAllowed(); } -RestStatus RestAgencyHandler::handleWrite() { +void RestAgencyHandler::handleWrite() { using namespace std::chrono; if (_request->requestType() != rest::RequestType::POST) { return reportMethodNotAllowed(); @@ -480,11 +467,9 @@ RestStatus RestAgencyHandler::handleWrite() { redirectRequest(ret.redirect); } } - - return RestStatus::DONE; } -RestStatus RestAgencyHandler::handleTransact() { +void RestAgencyHandler::handleTransact() { if (_request->requestType() != rest::RequestType::POST) { return reportMethodNotAllowed(); } @@ -541,11 +526,9 @@ RestStatus RestAgencyHandler::handleTransact() { redirectRequest(ret.redirect); } } - - return RestStatus::DONE; } -RestStatus RestAgencyHandler::handleInquire() { +void RestAgencyHandler::handleInquire() { if (_request->requestType() != rest::RequestType::POST) { return reportMethodNotAllowed(); } @@ -558,7 +541,7 @@ RestStatus RestAgencyHandler::handleInquire() { } catch (std::exception const& ex) { LOG_TOPIC("78755", DEBUG, Logger::AGENCY) << ex.what(); generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER); - return RestStatus::DONE; + return; } // Leadership established? @@ -650,11 +633,9 @@ RestStatus RestAgencyHandler::handleInquire() { redirectRequest(ret.redirect); } } - - return RestStatus::DONE; } -RestStatus RestAgencyHandler::handleRead() { +void RestAgencyHandler::handleRead() { if (_request->requestType() == rest::RequestType::POST) { velocypack::Slice query; try { @@ -684,13 +665,13 @@ RestStatus RestAgencyHandler::handleRead() { redirectRequest(ret.redirect); } } - return RestStatus::DONE; + return; } return reportMethodNotAllowed(); } -RestStatus RestAgencyHandler::handleConfig() { +void RestAgencyHandler::handleConfig() { LOG_TOPIC("eda22", DEBUG, Logger::AGENCY) << "handleConfig start"; // Update endpoint of peer if (_request->requestType() == rest::RequestType::POST) { @@ -699,7 +680,7 @@ RestStatus RestAgencyHandler::handleConfig() { } catch (std::exception const& e) { generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_INTERNAL, e.what()); - return RestStatus::DONE; + return; } } else if (_request->requestType() == rest::RequestType::PUT) { try { @@ -707,7 +688,7 @@ RestStatus RestAgencyHandler::handleConfig() { } catch (std::exception const& e) { generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_INTERNAL, e.what()); - return RestStatus::DONE; + return; } } @@ -736,10 +717,9 @@ RestStatus RestAgencyHandler::handleConfig() { generateResult(rest::ResponseCode::OK, body.slice()); LOG_TOPIC("77891", DEBUG, Logger::AGENCY) << "handleConfig after before done"; - return RestStatus::DONE; } -RestStatus RestAgencyHandler::handleState() { +void RestAgencyHandler::handleState() { bool found; std::string const& val_str = _request->value("redirectToLeader", found); bool redirectToLeader = found && val_str == "true"; @@ -754,7 +734,7 @@ RestStatus RestAgencyHandler::handleState() { } if (leader != _agent->id()) { redirectRequest(leader); - return RestStatus::DONE; + return; } } } @@ -769,59 +749,57 @@ RestStatus RestAgencyHandler::handleState() { auto origin = transaction::OperationOriginInternal{"returning agency state"}; transaction::StandaloneContext ctx(_vocbase, origin); generateResult(rest::ResponseCode::OK, body.slice(), ctx.getVPackOptions()); - return RestStatus::DONE; } -RestStatus RestAgencyHandler::reportMethodNotAllowed() { +void RestAgencyHandler::reportMethodNotAllowed() { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; } -RestStatus RestAgencyHandler::execute() { +auto RestAgencyHandler::executeAsync() -> futures::Future { response()->setAllowCompression( rest::ResponseCompressionType::kAllowCompression); try { auto const& suffixes = _request->suffixes(); if (suffixes.empty()) { // Empty request - return reportErrorEmptyRequest(); + co_return reportErrorEmptyRequest(); } else if (suffixes.size() > 1) { // path size >= 2 - return reportTooManySuffices(); + co_return reportTooManySuffices(); } else { if (suffixes[0] == "write") { - return handleWrite(); + co_return handleWrite(); } else if (suffixes[0] == "read") { - return handleRead(); + co_return handleRead(); } else if (suffixes[0] == "poll") { - return handlePoll(); + co_return co_await handlePoll(); } else if (suffixes[0] == "inquire") { - return handleInquire(); + co_return handleInquire(); } else if (suffixes[0] == "transient") { - return handleTransient(); + co_return handleTransient(); } else if (suffixes[0] == "transact") { - return handleTransact(); + co_return handleTransact(); } else if (suffixes[0] == "config") { if (_request->requestType() != rest::RequestType::GET && _request->requestType() != rest::RequestType::PUT && _request->requestType() != rest::RequestType::POST) { - return reportMethodNotAllowed(); + co_return reportMethodNotAllowed(); } - return handleConfig(); + co_return handleConfig(); } else if (suffixes[0] == "state") { if (_request->requestType() != rest::RequestType::GET) { - return reportMethodNotAllowed(); + co_return reportMethodNotAllowed(); } - return handleState(); + co_return handleState(); } else if (suffixes[0] == "stores") { - return handleStores(); + co_return handleStores(); } else if (suffixes[0] == "store") { - return handleStore(); + co_return handleStore(); } else { - return reportUnknownMethod(); + co_return reportUnknownMethod(); } } } catch (...) { // Ignore this error } - return RestStatus::DONE; + co_return; } diff --git a/arangod/Agency/RestAgencyHandler.h b/arangod/Agency/RestAgencyHandler.h index 1399df74b33b..5cb84e9a0bce 100644 --- a/arangod/Agency/RestAgencyHandler.h +++ b/arangod/Agency/RestAgencyHandler.h @@ -46,31 +46,29 @@ class RestAgencyHandler : public RestVocbaseBaseHandler { return RequestLane::AGENCY_CLUSTER; } - RestStatus execute() override; - - using fvoid = futures::Future; - + auto executeAsync() -> futures::Future override; /** * @brief Async call to Agent poll with index to wait for within timeout */ - RestStatus pollIndex(consensus::index_t const& index, double const& timeout); + auto pollIndex(consensus::index_t const& index, double const& timeout) + -> async; private: - RestStatus reportErrorEmptyRequest(); - RestStatus reportTooManySuffices(); - RestStatus reportUnknownMethod(); - RestStatus reportMessage(arangodb::rest::ResponseCode, std::string const&); - RestStatus handleStores(); - RestStatus handleStore(); - RestStatus handleRead(); - RestStatus handleWrite(); - RestStatus handleTransact(); - RestStatus handleConfig(); - RestStatus reportMethodNotAllowed(); - RestStatus handleState(); - RestStatus handleTransient(); - RestStatus handleInquire(); - RestStatus handlePoll(); + void reportErrorEmptyRequest(); + void reportTooManySuffices(); + void reportUnknownMethod(); + void reportMessage(arangodb::rest::ResponseCode, std::string const&); + void handleStores(); + void handleStore(); + void handleRead(); + void handleWrite(); + void handleTransact(); + void handleConfig(); + void reportMethodNotAllowed(); + void handleState(); + void handleTransient(); + void handleInquire(); + auto handlePoll() -> async; void redirectRequest(std::string const& leaderId); consensus::Agent* _agent; diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index c90b00204676..50be414212c4 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -50,6 +50,9 @@ template class Future; } // namespace futures +template +struct async; + class GeneralRequest; class RequestStatistics; class Result; diff --git a/arangod/RestHandler/RestAdminLogHandler.cpp b/arangod/RestHandler/RestAdminLogHandler.cpp index 88ef87befebe..af4f843006bc 100644 --- a/arangod/RestHandler/RestAdminLogHandler.cpp +++ b/arangod/RestHandler/RestAdminLogHandler.cpp @@ -34,7 +34,6 @@ #include "Logger/LogLevel.h" #include "Logger/Logger.h" #include "Logger/LoggerFeature.h" -#include "Logger/LogMacros.h" #include "Logger/LogTopic.h" #include "Network/Methods.h" #include "Network/NetworkFeature.h" @@ -42,6 +41,7 @@ #include "RestServer/LogBufferFeature.h" #include "Utils/ExecContext.h" +#include #include #include #include @@ -79,12 +79,12 @@ arangodb::Result RestAdminLogHandler::verifyPermitted() { return arangodb::Result(); } -RestStatus RestAdminLogHandler::execute() { +auto RestAdminLogHandler::executeAsync() -> futures::Future { auto result = verifyPermitted(); if (!result.ok()) { generateError(rest::ResponseCode::FORBIDDEN, result.errorNumber(), result.errorMessage()); - return RestStatus::DONE; + co_return; } auto const& suffixes = _request->suffixes(); @@ -96,7 +96,7 @@ RestStatus RestAdminLogHandler::execute() { clearLogs(); } else if (suffixes.size() == 1 && suffixes[0] == "level") { // reset log levels to defaults - handleLogLevel(); + co_await handleLogLevel(); } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, @@ -105,11 +105,11 @@ RestStatus RestAdminLogHandler::execute() { } } else if (type == rest::RequestType::GET) { if (suffixes.empty()) { - return reportLogs(/*newFormat*/ false); + co_await reportLogs(/*newFormat*/ false); } else if (suffixes.size() == 1 && suffixes[0] == "entries") { - return reportLogs(/*newFormat*/ true); + co_await reportLogs(/*newFormat*/ true); } else if (suffixes.size() == 1 && suffixes[0] == "level") { - return handleLogLevel(); + co_await handleLogLevel(); } else if (suffixes.size() == 1 && suffixes[0] == "structured") { handleLogStructuredParams(); } else { @@ -121,7 +121,7 @@ RestStatus RestAdminLogHandler::execute() { } else if (type == rest::RequestType::PUT) { if (suffixes.size() == 1) { if (suffixes[0] == "level") { - return handleLogLevel(); + co_await handleLogLevel(); } else if (suffixes[0] == "structured") { handleLogStructuredParams(); } else { @@ -147,7 +147,7 @@ RestStatus RestAdminLogHandler::execute() { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } - return RestStatus::DONE; + co_return; } void RestAdminLogHandler::clearLogs() { @@ -155,7 +155,7 @@ void RestAdminLogHandler::clearLogs() { generateOk(rest::ResponseCode::OK, VPackSlice::emptyObjectSlice()); } -RestStatus RestAdminLogHandler::reportLogs(bool newFormat) { +auto RestAdminLogHandler::reportLogs(bool newFormat) -> async { bool foundServerIdParameter; std::string const& serverId = _request->value("serverId", foundServerIdParameter); @@ -178,7 +178,7 @@ RestStatus RestAdminLogHandler::reportLogs(bool newFormat) { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_BAD_PARAMETER, "unknown serverId supplied."); - return RestStatus::DONE; + co_return; } NetworkFeature const& nf = server().getFeature(); @@ -192,18 +192,17 @@ RestStatus RestAdminLogHandler::reportLogs(bool newFormat) { options.database = _request->databaseName(); options.parameters = _request->parameters(); - auto f = network::sendRequestRetry( + auto r = co_await network::sendRequestRetry( pool, "server:" + serverId, fuerte::RestVerb::Get, _request->requestPath(), VPackBuffer{}, options); - return waitForFuture(std::move(f).thenValue( - [self = std::dynamic_pointer_cast( - shared_from_this())](network::Response const& r) { - if (r.fail()) { - self->generateError(r.combinedResult()); - } else { - self->generateResult(rest::ResponseCode::OK, r.slice()); - } - })); + + if (r.fail()) { + generateError(r.combinedResult()); + } else { + generateResult(rest::ResponseCode::OK, r.slice()); + } + + co_return; } } @@ -245,7 +244,7 @@ RestStatus RestAdminLogHandler::reportLogs(bool newFormat) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, absl::StrCat("unknown '", (found2 ? "level" : "upto"), "' log level: '", logLevel, "'")); - return RestStatus::DONE; + co_return; } } } @@ -419,13 +418,13 @@ RestStatus RestAdminLogHandler::reportLogs(bool newFormat) { result.close(); result.close(); // Close the result object - } // format end + } // format end generateResult(rest::ResponseCode::OK, result.slice()); - return RestStatus::DONE; + co_return; } -RestStatus RestAdminLogHandler::handleLogLevel() { +auto RestAdminLogHandler::handleLogLevel() -> async { std::vector const& suffixes = _request->suffixes(); // was validated earlier TRI_ASSERT(!suffixes.empty()); @@ -433,7 +432,7 @@ RestStatus RestAdminLogHandler::handleLogLevel() { if (suffixes[0] != "level") { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "superfluous suffix, expecting /_admin/log/level"); - return RestStatus::DONE; + co_return; } bool foundServerIdParameter; @@ -458,7 +457,7 @@ RestStatus RestAdminLogHandler::handleLogLevel() { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_BAD_PARAMETER, "unknown serverId supplied."); - return RestStatus::DONE; + co_return; } NetworkFeature const& nf = server().getFeature(); @@ -492,21 +491,18 @@ RestStatus RestAdminLogHandler::handleLogLevel() { }); if (!body.has_value()) { - return RestStatus::DONE; // error message from vpack parser + co_return; // error message from vpack parser } - auto f = network::sendRequestRetry(pool, "server:" + serverId, - requestType, _request->requestPath(), - std::move(*body), options); - return waitForFuture(std::move(f).thenValue( - [self = std::dynamic_pointer_cast( - shared_from_this())](network::Response const& r) { - if (r.fail()) { - self->generateError(r.combinedResult()); - } else { - self->generateResult(rest::ResponseCode::OK, r.slice()); - } - })); + auto r = co_await network::sendRequestRetry( + pool, "server:" + serverId, requestType, _request->requestPath(), + std::move(*body), options); + if (r.fail()) { + generateError(r.combinedResult()); + } else { + generateResult(rest::ResponseCode::OK, r.slice()); + } + co_return; } } @@ -536,7 +532,7 @@ RestStatus RestAdminLogHandler::handleLogLevel() { bool parseSuccess = false; VPackSlice slice = this->parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; // error message generated in parseVPackBody + co_return; // error message generated in parseVPackBody } if (slice.isString()) { @@ -559,7 +555,7 @@ RestStatus RestAdminLogHandler::handleLogLevel() { if (withAppenders) { AppendersLogLevelConfig config; if (!parseConfig(config)) { - return RestStatus::DONE; + co_return; } auto res = Logger::setLogLevel(config); @@ -567,12 +563,12 @@ RestStatus RestAdminLogHandler::handleLogLevel() { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, absl::StrCat("Failed to update log levels: ", res.errorMessage())); - return RestStatus::DONE; + co_return; } } else { LogLevels config; if (!parseConfig(config)) { - return RestStatus::DONE; + co_return; } Logger::setLogLevel(config); } @@ -592,8 +588,6 @@ RestStatus RestAdminLogHandler::handleLogLevel() { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } - - return RestStatus::DONE; } void RestAdminLogHandler::handleLogStructuredParams() { diff --git a/arangod/RestHandler/RestAdminLogHandler.h b/arangod/RestHandler/RestAdminLogHandler.h index 94f649a34136..f8bbaf2c6b0a 100644 --- a/arangod/RestHandler/RestAdminLogHandler.h +++ b/arangod/RestHandler/RestAdminLogHandler.h @@ -39,13 +39,13 @@ class RestAdminLogHandler : public RestBaseHandler { public: char const* name() const override final { return "RestAdminLogHandler"; } RequestLane lane() const override final { return RequestLane::CLIENT_FAST; } - RestStatus execute() override; + auto executeAsync() -> futures::Future override; private: arangodb::Result verifyPermitted(); void clearLogs(); - RestStatus reportLogs(bool newFormat); - RestStatus handleLogLevel(); + auto reportLogs(bool newFormat) -> async; + auto handleLogLevel() -> async; void handleLogStructuredParams(); }; } // namespace arangodb diff --git a/arangod/RestHandler/RestDocumentHandler.cpp b/arangod/RestHandler/RestDocumentHandler.cpp index dbdba1bca830..e047992f5483 100644 --- a/arangod/RestHandler/RestDocumentHandler.cpp +++ b/arangod/RestHandler/RestDocumentHandler.cpp @@ -29,7 +29,6 @@ #include "Cluster/ClusterFeature.h" #include "Cluster/ClusterInfo.h" #include "Cluster/ServerState.h" -#include "Logger/LogMacros.h" #include "Random/RandomGenerator.h" #include "StorageEngine/EngineSelectorFeature.h" #include "StorageEngine/StorageEngine.h" @@ -45,6 +44,7 @@ #include "Utils/SingleCollectionTransaction.h" #include "VocBase/vocbase.h" +#include #include using namespace arangodb; @@ -91,7 +91,7 @@ RequestLane RestDocumentHandler::lane() const { return RequestLane::CLIENT_SLOW; } -RestStatus RestDocumentHandler::execute() { +auto RestDocumentHandler::executeAsync() -> futures::Future { // extract the sub-request type auto const type = _request->requestType(); #ifdef ARANGODB_ENABLE_FAILURE_TESTS @@ -106,24 +106,28 @@ RestStatus RestDocumentHandler::execute() { // execute one of the CRUD methods switch (type) { case rest::RequestType::DELETE_REQ: - return waitForFuture(removeDocument()); + co_return co_await removeDocument(); case rest::RequestType::GET: - return readDocument(); + co_return co_await readDocument(); + ; case rest::RequestType::HEAD: - return checkDocument(); + co_return co_await checkDocument(); + ; case rest::RequestType::POST: - return waitForFuture(insertDocument()); + co_return co_await insertDocument(); case rest::RequestType::PUT: - return replaceDocument(); + co_return co_await replaceDocument(); + ; case rest::RequestType::PATCH: - return updateDocument(); + co_return co_await updateDocument(); + ; default: { generateNotImplemented("ILLEGAL " + DOCUMENT_PATH); } } // this handler is done - return RestStatus::DONE; + co_return; } void RestDocumentHandler::shutdownExecute(bool isFinalized) noexcept { @@ -153,7 +157,7 @@ void RestDocumentHandler::shutdownExecute(bool isFinalized) noexcept { RestVocbaseBaseHandler::shutdownExecute(isFinalized); } -futures::Future RestDocumentHandler::insertDocument() { +async RestDocumentHandler::insertDocument() { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() > 1) { @@ -308,7 +312,7 @@ futures::Future RestDocumentHandler::insertDocument() { /// Either readSingleDocument or readAllDocuments. //////////////////////////////////////////////////////////////////////////////// -RestStatus RestDocumentHandler::readDocument() { +async RestDocumentHandler::readDocument() { size_t const len = _request->suffixes().size(); switch (len) { @@ -317,20 +321,19 @@ RestStatus RestDocumentHandler::readDocument() { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND, "expecting GET /_api/document//"); - return RestStatus::DONE; + co_return; case 2: - return waitForFuture(readSingleDocument(true)); + co_return co_await readSingleDocument(true); default: generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "expecting GET /_api/document//"); - return RestStatus::DONE; + co_return; } } -futures::Future RestDocumentHandler::readSingleDocument( - bool generateBody) { +async RestDocumentHandler::readSingleDocument(bool generateBody) { std::vector const& suffixes = _request->decodedSuffixes(); // split the document reference @@ -439,33 +442,32 @@ futures::Future RestDocumentHandler::readSingleDocument( trx->transactionContextPtr()->getVPackOptions()); } -RestStatus RestDocumentHandler::checkDocument() { +async RestDocumentHandler::checkDocument() { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expecting HEAD /_api/document//"); - return RestStatus::DONE; + co_return; } - return waitForFuture(readSingleDocument(false)); + co_return co_await readSingleDocument(false); } -RestStatus RestDocumentHandler::replaceDocument() { +async RestDocumentHandler::replaceDocument() { bool found; _request->value("onlyget", found); if (found) { - return waitForFuture(readManyDocuments()); + co_return co_await readManyDocuments(); } - return waitForFuture(modifyDocument(false)); + co_return co_await modifyDocument(false); } -RestStatus RestDocumentHandler::updateDocument() { - return waitForFuture(modifyDocument(true)); +async RestDocumentHandler::updateDocument() { + co_return co_await modifyDocument(true); } -futures::Future RestDocumentHandler::modifyDocument( - bool isPatch) { +async RestDocumentHandler::modifyDocument(bool isPatch) { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() > 2) { @@ -668,7 +670,7 @@ futures::Future RestDocumentHandler::modifyDocument( opOptions.silent, rest::ResponseCode::CREATED); } -futures::Future RestDocumentHandler::removeDocument() { +async RestDocumentHandler::removeDocument() { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() < 1 || suffixes.size() > 2) { @@ -809,7 +811,7 @@ futures::Future RestDocumentHandler::removeDocument() { opOptions.silent, rest::ResponseCode::OK); } -futures::Future RestDocumentHandler::readManyDocuments() { +async RestDocumentHandler::readManyDocuments() { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() != 1) { diff --git a/arangod/RestHandler/RestDocumentHandler.h b/arangod/RestHandler/RestDocumentHandler.h index 5ab7b735325a..3659aec0e20d 100644 --- a/arangod/RestHandler/RestDocumentHandler.h +++ b/arangod/RestHandler/RestDocumentHandler.h @@ -40,7 +40,7 @@ class RestDocumentHandler : public RestVocbaseBaseHandler { ~RestDocumentHandler(); public: - RestStatus execute() override final; + auto executeAsync() -> futures::Future override; char const* name() const override final { return "RestDocumentHandler"; } RequestLane lane() const override final; @@ -48,32 +48,31 @@ class RestDocumentHandler : public RestVocbaseBaseHandler { private: // inserts a document - [[nodiscard]] futures::Future insertDocument(); + async insertDocument(); // reads a single or all documents - RestStatus readDocument(); + async readDocument(); // reads a single document - [[nodiscard]] futures::Future readSingleDocument( - bool generateBody); + async readSingleDocument(bool generateBody); // reads multiple documents - [[nodiscard]] futures::Future readManyDocuments(); + async readManyDocuments(); // reads a single document head - RestStatus checkDocument(); + async checkDocument(); // replaces a document - RestStatus replaceDocument(); + async replaceDocument(); // updates a document - RestStatus updateDocument(); + async updateDocument(); // helper function for replace and update - [[nodiscard]] futures::Future modifyDocument(bool); + async modifyDocument(bool); // removes a document - [[nodiscard]] futures::Future removeDocument(); + async removeDocument(); void handleFillIndexCachesValue(OperationOptions& options); diff --git a/arangod/RestHandler/RestGraphHandler.cpp b/arangod/RestHandler/RestGraphHandler.cpp index 2cafddab421a..f09a972911e9 100644 --- a/arangod/RestHandler/RestGraphHandler.cpp +++ b/arangod/RestHandler/RestGraphHandler.cpp @@ -50,14 +50,11 @@ RestGraphHandler::RestGraphHandler(ArangodServer& server, : RestVocbaseBaseHandler(server, request, response), _graphManager(_vocbase, transaction::OperationOriginREST{::moduleName}) {} -RestStatus RestGraphHandler::execute() { - auto f = executeGharial().thenValue([&](Result res) { - if (res.fail()) { - TRI_ASSERT(!_response->isResponseEmpty()); - } - }); - - return waitForFuture(std::move(f)); +auto RestGraphHandler::executeAsync() -> futures::Future { + auto res = co_await executeGharial(); + if (res.fail()) { + TRI_ASSERT(!_response->isResponseEmpty()); + } } Result RestGraphHandler::returnError(ErrorCode errorNumber) { diff --git a/arangod/RestHandler/RestGraphHandler.h b/arangod/RestHandler/RestGraphHandler.h index e54160863aa9..e56bfb0a478a 100644 --- a/arangod/RestHandler/RestGraphHandler.h +++ b/arangod/RestHandler/RestGraphHandler.h @@ -51,7 +51,7 @@ class RestGraphHandler : public arangodb::RestVocbaseBaseHandler { char const* name() const override final { return "RestGraphHandler"; } - RestStatus execute() override; + auto executeAsync() -> futures::Future override; RequestLane lane() const override; diff --git a/arangod/RestHandler/RestImportHandler.cpp b/arangod/RestHandler/RestImportHandler.cpp index d676ac196b28..5e728fab9831 100644 --- a/arangod/RestHandler/RestImportHandler.cpp +++ b/arangod/RestHandler/RestImportHandler.cpp @@ -53,7 +53,7 @@ RestImportHandler::RestImportHandler(ArangodServer& server, _onDuplicateAction(DUPLICATE_ERROR), _ignoreMissing(false) {} -RestStatus RestImportHandler::execute() { +auto RestImportHandler::executeAsync() -> futures::Future { // set default value for onDuplicate _onDuplicateAction = DUPLICATE_ERROR; @@ -99,14 +99,17 @@ RestStatus RestImportHandler::execute() { std::string const& documentType = _request->value("type", found); if (_request->contentType() == arangodb::ContentType::VPACK) { - return waitForFuture(createFromVPack(documentType)); + co_await createFromVPack(documentType); + co_return; } else if (found && (documentType == "documents" || documentType == "array" || documentType == "list" || documentType == "auto")) { - return waitForFuture(createFromJson(documentType)); + co_await createFromJson(documentType); + co_return; } else { // CSV - return waitForFuture(createFromKeyValueList()); + co_await createFromKeyValueList(); + co_return; } } break; @@ -116,7 +119,7 @@ RestStatus RestImportHandler::execute() { } // this handler is done - return RestStatus::DONE; + co_return; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/RestHandler/RestImportHandler.h b/arangod/RestHandler/RestImportHandler.h index 4efaca5d10a7..14a82664d28e 100644 --- a/arangod/RestHandler/RestImportHandler.h +++ b/arangod/RestHandler/RestImportHandler.h @@ -65,7 +65,7 @@ class RestImportHandler : public RestVocbaseBaseHandler { explicit RestImportHandler(ArangodServer&, GeneralRequest*, GeneralResponse*); public: - RestStatus execute() override final; + auto executeAsync() -> futures::Future override; char const* name() const override final { return "RestImportHandler"; } RequestLane lane() const override final { return RequestLane::CLIENT_SLOW; } diff --git a/arangod/RestHandler/RestLogHandler.cpp b/arangod/RestHandler/RestLogHandler.cpp index fa0f1097f95e..be93582f40ef 100644 --- a/arangod/RestHandler/RestLogHandler.cpp +++ b/arangod/RestHandler/RestLogHandler.cpp @@ -23,16 +23,12 @@ #include "RestLogHandler.h" -#include - -#include -#include +#include +#include +#include #include #include -#include -#include - #include "Agency/AgencyPaths.h" #include "Inspection/VPack.h" #include "Replication2/AgencyMethods.h" @@ -49,45 +45,47 @@ using namespace arangodb; using namespace arangodb::replication2; -RestStatus RestLogHandler::execute() { +auto RestLogHandler::executeAsync() -> futures::Future { // for now required admin access to the database if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } auto methods = ReplicatedLogMethods::createInstance(_vocbase); - return executeByMethod(*methods); + co_await executeByMethod(*methods); + co_return; } -RestStatus RestLogHandler::executeByMethod( - ReplicatedLogMethods const& methods) { +auto RestLogHandler::executeByMethod(ReplicatedLogMethods const& methods) + -> async { switch (_request->requestType()) { case rest::RequestType::GET: - return handleGetRequest(methods); + co_return co_await handleGetRequest(methods); case rest::RequestType::POST: - return handlePostRequest(methods); + co_return co_await handlePostRequest(methods); case rest::RequestType::DELETE_REQ: - return handleDeleteRequest(methods); + co_return co_await handleDeleteRequest(methods); default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } - return RestStatus::DONE; + co_return; } -RestStatus RestLogHandler::handlePostRequest( - ReplicatedLogMethods const& methods) { +auto RestLogHandler::handlePostRequest(ReplicatedLogMethods const& methods) + -> async { std::vector const& suffixes = _request->decodedSuffixes(); bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - return RestStatus::DONE; + co_return; } if (suffixes.empty()) { - return handlePost(methods, body); + co_await handlePost(methods, body); + co_return; } else if (std::string_view logIdStr, toRemoveStr, toAddStr; rest::Match(suffixes).against(&logIdStr, "participant", &toRemoveStr, "replace-with", @@ -98,7 +96,7 @@ RestStatus RestLogHandler::handlePostRequest( if (!logId) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, basics::StringUtils::concatT("Not a log id: ", logIdStr)); - return RestStatus::DONE; + co_return; } // If this wasn't a temporary API, it would be nice to be able to pass a @@ -119,16 +117,14 @@ RestStatus RestLogHandler::handlePostRequest( auto stateTarget = velocypack::deserialize(res->slice()); - return waitForFuture( - methods.replaceParticipant(*logId, toRemove, toAdd, stateTarget.leader) - .thenValue([this](auto&& result) { - if (result.ok()) { - generateOk(rest::ResponseCode::OK, - VPackSlice::emptyObjectSlice()); - } else { - generateError(result); - } - })); + auto result = co_await methods.replaceParticipant(*logId, toRemove, toAdd, + stateTarget.leader); + if (result.ok()) { + generateOk(rest::ResponseCode::OK, VPackSlice::emptyObjectSlice()); + } else { + generateError(result); + } + co_return; } else if (std::string_view logIdStr, newLeaderStr; rest::Match(suffixes).against(&logIdStr, "leader", &newLeaderStr)) { @@ -137,110 +133,104 @@ RestStatus RestLogHandler::handlePostRequest( if (!logId) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, basics::StringUtils::concatT("Not a log id: ", logIdStr)); - return RestStatus::DONE; + co_return; } - return waitForFuture( - methods.setLeader(*logId, newLeader).thenValue([this](auto&& result) { - if (result.ok()) { - generateOk(rest::ResponseCode::OK, VPackSlice::emptyObjectSlice()); - } else { - generateError(result); - } - })); + auto result = co_await methods.setLeader(*logId, newLeader); + + if (result.ok()) { + generateOk(rest::ResponseCode::OK, VPackSlice::emptyObjectSlice()); + } else { + generateError(result); + } + co_return; } if (suffixes.size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect POST /_api/log//[verb]"); - return RestStatus::DONE; + co_return; } LogId logId{basics::StringUtils::uint64(suffixes[0])}; if (auto& verb = suffixes[1]; verb == "insert") { - return handlePostInsert(methods, logId, body); + co_return co_await handlePostInsert(methods, logId, body); } else if (verb == "release") { - return handlePostRelease(methods, logId); + co_return co_await handlePostRelease(methods, logId); } else if (verb == "ping") { - return handlePostPing(methods, logId, body); + co_return co_await handlePostPing(methods, logId, body); } else if (verb == "compact") { - return handlePostCompact(methods, logId); + co_return co_await handlePostCompact(methods, logId); } else if (verb == "multi-insert") { - return handlePostInsertMulti(methods, logId, body); + co_return co_await handlePostInsertMulti(methods, logId, body); } else { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "expecting one of the resources 'insert', 'release', " "'multi-insert', 'compact', 'ping'"); } - return RestStatus::DONE; + co_return; } -RestStatus RestLogHandler::handlePostInsert(ReplicatedLogMethods const& methods, - replication2::LogId logId, - velocypack::Slice payload) { +auto RestLogHandler::handlePostInsert(ReplicatedLogMethods const& methods, + replication2::LogId logId, + velocypack::Slice payload) + -> async { bool const waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false); bool const dontWaitForCommit = _request->parsedValue(StaticStrings::DontWaitForCommit, false); if (dontWaitForCommit) { - return waitForFuture( - methods - .insertWithoutCommit(logId, LogPayload::createFromSlice(payload), - waitForSync) - .thenValue([this](LogIndex idx) { - VPackBuilder response; - { - VPackObjectBuilder result(&response); - response.add("index", VPackValue(idx)); - } - generateOk(rest::ResponseCode::ACCEPTED, response.slice()); - })); + auto idx = co_await methods.insertWithoutCommit( + logId, LogPayload::createFromSlice(payload), waitForSync); + VPackBuilder response; + { + VPackObjectBuilder result(&response); + response.add("index", VPackValue(idx)); + } + generateOk(rest::ResponseCode::ACCEPTED, response.slice()); } else { - return waitForFuture( - methods.insert(logId, LogPayload::createFromSlice(payload), waitForSync) - .thenValue([this](auto&& waitForResult) { - VPackBuilder response; - { - VPackObjectBuilder result(&response); - response.add("index", VPackValue(waitForResult.first)); - response.add(VPackValue("result")); - waitForResult.second.toVelocyPack(response); - } - generateOk(rest::ResponseCode::CREATED, response.slice()); - })); + auto waitForResult = co_await methods.insert( + logId, LogPayload::createFromSlice(payload), waitForSync); + VPackBuilder response; + { + VPackObjectBuilder result(&response); + response.add("index", VPackValue(waitForResult.first)); + response.add(VPackValue("result")); + waitForResult.second.toVelocyPack(response); + } + generateOk(rest::ResponseCode::CREATED, response.slice()); } } -RestStatus RestLogHandler::handlePostPing(ReplicatedLogMethods const& methods, - replication2::LogId logId, - velocypack::Slice payload) { +auto RestLogHandler::handlePostPing(ReplicatedLogMethods const& methods, + replication2::LogId logId, + velocypack::Slice payload) -> async { std::optional message; if (payload.isObject()) { if (auto messageSlice = payload.get("message"); not messageSlice.isNone()) { message = messageSlice.copyString(); } } - return waitForFuture( - methods.ping(logId, std::move(message)) - .thenValue([this](auto&& waitForResult) { - VPackBuilder response; - { - VPackObjectBuilder result(&response); - response.add("index", VPackValue(waitForResult.first)); - response.add(VPackValue("result")); - waitForResult.second.toVelocyPack(response); - } - generateOk(rest::ResponseCode::CREATED, response.slice()); - })); + auto waitForResult = co_await methods.ping(logId, std::move(message)); + + VPackBuilder response; + { + VPackObjectBuilder result(&response); + response.add("index", VPackValue(waitForResult.first)); + response.add(VPackValue("result")); + waitForResult.second.toVelocyPack(response); + } + generateOk(rest::ResponseCode::CREATED, response.slice()); } -RestStatus RestLogHandler::handlePostInsertMulti( - ReplicatedLogMethods const& methods, replication2::LogId logId, - velocypack::Slice payload) { +auto RestLogHandler::handlePostInsertMulti(ReplicatedLogMethods const& methods, + replication2::LogId logId, + velocypack::Slice payload) + -> async { if (!payload.isArray()) { generateError(ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "array expected at top-level"); - return RestStatus::DONE; + co_return; } bool const waitForSync = _request->parsedValue(StaticStrings::WaitForSyncString, false); @@ -249,158 +239,145 @@ RestStatus RestLogHandler::handlePostInsertMulti( if (dontWaitForCommit) { generateError(ResponseCode::BAD, TRI_ERROR_HTTP_NOT_IMPLEMENTED, "dontWaitForCommit is not implemented for multiple inserts"); - return RestStatus::DONE; + co_return; } auto iter = replicated_log::VPackArrayToLogPayloadIterator{payload}; - auto f = methods.insert(logId, iter, waitForSync) - .thenValue([this](auto&& waitForResult) { - VPackBuilder response; - { - VPackObjectBuilder result(&response); - { - VPackArrayBuilder a(&response, "indexes"); - for (auto index : waitForResult.first) { - response.add(VPackValue(index)); - } - } - response.add(VPackValue("result")); - waitForResult.second.toVelocyPack(response); - } - generateOk(rest::ResponseCode::CREATED, response.slice()); - }); - return waitForFuture(std::move(f)); + auto waitForResult = co_await methods.insert(logId, iter, waitForSync); + + VPackBuilder response; + { + VPackObjectBuilder result(&response); + { + VPackArrayBuilder a(&response, "indexes"); + for (auto index : waitForResult.first) { + response.add(VPackValue(index)); + } + } + response.add(VPackValue("result")); + waitForResult.second.toVelocyPack(response); + } + generateOk(rest::ResponseCode::CREATED, response.slice()); } -RestStatus RestLogHandler::handlePostRelease( - ReplicatedLogMethods const& methods, replication2::LogId logId) { +auto RestLogHandler::handlePostRelease(ReplicatedLogMethods const& methods, + replication2::LogId logId) + -> async { auto idx = LogIndex{basics::StringUtils::uint64(_request->value("index"))}; - return waitForFuture( - methods.release(logId, idx).thenValue([this](Result&& res) { - if (res.fail()) { - generateError(res); - } else { - generateOk(rest::ResponseCode::ACCEPTED, VPackSlice::noneSlice()); - } - })); + + auto res = co_await methods.release(logId, idx); + if (res.fail()) { + generateError(res); + } else { + generateOk(rest::ResponseCode::ACCEPTED, VPackSlice::noneSlice()); + } } -RestStatus RestLogHandler::handlePostCompact( - ReplicatedLogMethods const& methods, replication2::LogId logId) { - return waitForFuture(methods.compact(logId).thenValue([this](auto&& res) { - VPackBuilder builder; - velocypack::serialize(builder, res); - generateOk(rest::ResponseCode::ACCEPTED, builder.slice()); - })); +auto RestLogHandler::handlePostCompact(ReplicatedLogMethods const& methods, + replication2::LogId logId) + -> async { + auto res = co_await methods.compact(logId); + + VPackBuilder builder; + velocypack::serialize(builder, res); + generateOk(rest::ResponseCode::ACCEPTED, builder.slice()); } -RestStatus RestLogHandler::handlePost(ReplicatedLogMethods const& methods, - velocypack::Slice specSlice) { +auto RestLogHandler::handlePost(ReplicatedLogMethods const& methods, + velocypack::Slice specSlice) -> async { if (_vocbase.replicationVersion() != replication::Version::TWO) { generateError( Result{TRI_ERROR_HTTP_FORBIDDEN, "Replicated logs available only in replication2 databases!"}); - return RestStatus::DONE; + co_return; } // create a new log auto spec = velocypack::deserialize(specSlice); - return waitForFuture( - methods.createReplicatedLog(std::move(spec)) - .thenValue( - [this](ResultT&& result) { - if (result.ok()) { - VPackBuilder builder; - velocypack::serialize(builder, result.get()); - generateOk(rest::ResponseCode::OK, builder.slice()); - } else { - generateError(result.result()); - } - })); + auto result = co_await methods.createReplicatedLog(std::move(spec)); + if (result.ok()) { + VPackBuilder builder; + velocypack::serialize(builder, result.get()); + generateOk(rest::ResponseCode::OK, builder.slice()); + } else { + generateError(result.result()); + } } -RestStatus RestLogHandler::handleGetRequest( - ReplicatedLogMethods const& methods) { +auto RestLogHandler::handleGetRequest(ReplicatedLogMethods const& methods) + -> async { std::vector const& suffixes = _request->suffixes(); if (suffixes.empty()) { - return handleGet(methods); + co_return co_await handleGet(methods); } if (suffixes.size() == 2) { if (suffixes[0] == "collection-status") { CollectionID cid{suffixes[1]}; - return handleGetCollectionStatus(methods, std::move(cid)); + co_return co_await handleGetCollectionStatus(methods, std::move(cid)); } } LogId logId{basics::StringUtils::uint64(suffixes[0])}; if (suffixes.size() == 1) { - return handleGetLog(methods, logId); + co_return co_await handleGetLog(methods, logId); } auto const& verb = suffixes[1]; if (verb == "poll") { - return handleGetPoll(methods, logId); + co_return co_await handleGetPoll(methods, logId); } else if (verb == "head") { - return handleGetHead(methods, logId); + co_return co_await handleGetHead(methods, logId); } else if (verb == "tail") { - return handleGetTail(methods, logId); + co_return co_await handleGetTail(methods, logId); } else if (verb == "entry") { - return handleGetEntry(methods, logId); + co_return co_await handleGetEntry(methods, logId); } else if (verb == "slice") { - return handleGetSlice(methods, logId); + co_return co_await handleGetSlice(methods, logId); } else if (verb == "local-status") { - return handleGetLocalStatus(methods, logId); + co_return co_await handleGetLocalStatus(methods, logId); } else if (verb == "global-status") { - return handleGetGlobalStatus(methods, logId); + co_return co_await handleGetGlobalStatus(methods, logId); } else { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "expecting one of the resources 'poll', 'head', 'tail', " "'entry', 'slice', 'local-status', 'global-status'"); } - return RestStatus::DONE; } -RestStatus RestLogHandler::handleDeleteRequest( - ReplicatedLogMethods const& methods) { +auto RestLogHandler::handleDeleteRequest(ReplicatedLogMethods const& methods) + -> async { auto const& suffixes = _request->suffixes(); if (std::string_view logIdStr; rest::Match(suffixes).against(&logIdStr)) { auto const logId = replication2::LogId::fromString(logIdStr); if (!logId) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, basics::StringUtils::concatT("Not a log id: ", logIdStr)); - return RestStatus::DONE; + co_return; + } + auto result = co_await methods.deleteReplicatedLog(*logId); + if (result.ok()) { + generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); + } else { + generateError(result); } - return waitForFuture( - methods.deleteReplicatedLog(*logId).thenValue([this](auto&& result) { - if (result.ok()) { - generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); - } else { - generateError(result); - } - })); - } else if (std::string_view logIdStr; rest::Match(suffixes).against(&logIdStr, "leader")) { auto const logId = replication2::LogId::fromString(logIdStr); if (!logId) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, basics::StringUtils::concatT("Not a log id: ", logIdStr)); - return RestStatus::DONE; + co_return; + } + auto result = co_await methods.setLeader(*logId, std::nullopt); + if (result.ok()) { + generateOk(rest::ResponseCode::OK, VPackSlice::emptyObjectSlice()); + } else { + generateError(result); } - return waitForFuture(methods.setLeader(*logId, std::nullopt) - .thenValue([this](auto&& result) { - if (result.ok()) { - generateOk(rest::ResponseCode::OK, - VPackSlice::emptyObjectSlice()); - } else { - generateError(result); - } - })); } else { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; } } @@ -408,53 +385,51 @@ RestLogHandler::RestLogHandler(ArangodServer& server, GeneralRequest* req, GeneralResponse* resp) : RestVocbaseBaseHandler(server, req, resp) {} -RestStatus RestLogHandler::handleGet(ReplicatedLogMethods const& methods) { - return waitForFuture(methods.getReplicatedLogs().thenValue([this]( - auto&& logs) { - VPackBuilder builder; - { - VPackObjectBuilder ob(&builder); - - for (auto const& [idx, logInfo] : logs) { - builder.add(VPackValue(std::to_string(idx.id()))); - std::visit(overload{[&](replicated_log::LogStatus const& status) { - status.toVelocyPack(builder); - }, - [&](ReplicatedLogMethods::ParticipantsList const& - participants) { - VPackArrayBuilder ab(&builder); - for (auto&& pid : participants) { - builder.add(VPackValue(pid)); - } - }}, - logInfo); - } +auto RestLogHandler::handleGet(ReplicatedLogMethods const& methods) + -> async { + auto logs = co_await methods.getReplicatedLogs(); + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + + for (auto const& [idx, logInfo] : logs) { + builder.add(VPackValue(std::to_string(idx.id()))); + std::visit( + overload{ + [&](replicated_log::LogStatus const& status) { + status.toVelocyPack(builder); + }, + [&](ReplicatedLogMethods::ParticipantsList const& participants) { + VPackArrayBuilder ab(&builder); + for (auto&& pid : participants) { + builder.add(VPackValue(pid)); + } + }}, + logInfo); } + } - generateOk(rest::ResponseCode::OK, builder.slice()); - })); + generateOk(rest::ResponseCode::OK, builder.slice()); } -RestStatus RestLogHandler::handleGetLog(const ReplicatedLogMethods& methods, - replication2::LogId logId) { - return waitForFuture( - methods.getStatus(logId).thenValue([this](auto&& status) { - std::visit( - [this](auto&& status) { - VPackBuilder buffer; - status.toVelocyPack(buffer); - generateOk(rest::ResponseCode::OK, buffer.slice()); - }, - status); - })); +auto RestLogHandler::handleGetLog(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { + auto status = co_await methods.getStatus(logId); + std::visit( + [this](auto&& status) { + VPackBuilder buffer; + status.toVelocyPack(buffer); + generateOk(rest::ResponseCode::OK, buffer.slice()); + }, + status); } -RestStatus RestLogHandler::handleGetPoll(ReplicatedLogMethods const& methods, - replication2::LogId logId) { +auto RestLogHandler::handleGetPoll(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//poll"); - return RestStatus::DONE; + co_return; } bool limitFound; LogIndex logIdx{basics::StringUtils::uint64(_request->value("first"))}; @@ -462,84 +437,73 @@ RestStatus RestLogHandler::handleGetPoll(ReplicatedLogMethods const& methods, basics::StringUtils::uint64(_request->value("limit", limitFound))}; limit = limitFound ? limit : ReplicatedLogMethods::kDefaultLimit; - auto fut = methods.poll(logId, logIdx, limit) - .thenValue([&](std::unique_ptr iter) { - VPackBuilder builder; - { - VPackArrayBuilder ab(&builder); - while (auto entry = iter->next()) { - entry->toVelocyPack(builder); - } - } - - generateOk(rest::ResponseCode::OK, builder.slice()); - }); + auto iter = co_await methods.poll(logId, logIdx, limit); + VPackBuilder builder; + { + VPackArrayBuilder ab(&builder); + while (auto entry = iter->next()) { + entry->toVelocyPack(builder); + } + } - return waitForFuture(std::move(fut)); + generateOk(rest::ResponseCode::OK, builder.slice()); } -RestStatus RestLogHandler::handleGetTail(ReplicatedLogMethods const& methods, - replication2::LogId logId) { +auto RestLogHandler::handleGetTail(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//tail"); - return RestStatus::DONE; + co_return; } bool limitFound; std::size_t limit{ basics::StringUtils::uint64(_request->value("limit", limitFound))}; limit = limitFound ? limit : ReplicatedLogMethods::kDefaultLimit; - auto fut = methods.tail(logId, limit) - .thenValue([&](std::unique_ptr iter) { - VPackBuilder builder; - { - VPackArrayBuilder ab(&builder); - while (auto entry = iter->next()) { - entry->toVelocyPack(builder); - } - } + auto iter = co_await methods.tail(logId, limit); - generateOk(rest::ResponseCode::OK, builder.slice()); - }); + VPackBuilder builder; + { + VPackArrayBuilder ab(&builder); + while (auto entry = iter->next()) { + entry->toVelocyPack(builder); + } + } - return waitForFuture(std::move(fut)); + generateOk(rest::ResponseCode::OK, builder.slice()); } -RestStatus RestLogHandler::handleGetHead(ReplicatedLogMethods const& methods, - replication2::LogId logId) { +auto RestLogHandler::handleGetHead(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//head"); - return RestStatus::DONE; + co_return; } bool limitFound; std::size_t limit{ basics::StringUtils::uint64(_request->value("limit", limitFound))}; limit = limitFound ? limit : ReplicatedLogMethods::kDefaultLimit; - auto fut = methods.head(logId, limit) - .thenValue([&](std::unique_ptr iter) { - VPackBuilder builder; - { - VPackArrayBuilder ab(&builder); - while (auto entry = iter->next()) { - entry->toVelocyPack(builder); - } - } - - generateOk(rest::ResponseCode::OK, builder.slice()); - }); + auto iter = co_await methods.head(logId, limit); + VPackBuilder builder; + { + VPackArrayBuilder ab(&builder); + while (auto entry = iter->next()) { + entry->toVelocyPack(builder); + } + } - return waitForFuture(std::move(fut)); + generateOk(rest::ResponseCode::OK, builder.slice()); } -RestStatus RestLogHandler::handleGetSlice(ReplicatedLogMethods const& methods, - replication2::LogId logId) { +auto RestLogHandler::handleGetSlice(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//slice"); - return RestStatus::DONE; + co_return; } bool stopFound; LogIndex start{basics::StringUtils::uint64(_request->value("start"))}; @@ -547,44 +511,42 @@ RestStatus RestLogHandler::handleGetSlice(ReplicatedLogMethods const& methods, basics::StringUtils::uint64(_request->value("stop", stopFound))}; stop = stopFound ? stop : start + ReplicatedLogMethods::kDefaultLimit + 1; - auto fut = methods.slice(logId, start, stop) - .thenValue([&](std::unique_ptr iter) { - VPackBuilder builder; - { - VPackArrayBuilder ab(&builder); - while (auto entry = iter->next()) { - entry->toVelocyPack(builder); - } - } + auto iter = co_await methods.slice(logId, start, stop); - generateOk(rest::ResponseCode::OK, builder.slice()); - }); + VPackBuilder builder; + { + VPackArrayBuilder ab(&builder); + while (auto entry = iter->next()) { + entry->toVelocyPack(builder); + } + } - return waitForFuture(std::move(fut)); + generateOk(rest::ResponseCode::OK, builder.slice()); } -RestStatus RestLogHandler::handleGetLocalStatus( - ReplicatedLogMethods const& methods, replication2::LogId logId) { +auto RestLogHandler::handleGetLocalStatus(ReplicatedLogMethods const& methods, + replication2::LogId logId) + -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//local-status"); - return RestStatus::DONE; + co_return; } - return waitForFuture( - methods.getLocalStatus(logId).thenValue([this](auto&& status) { - VPackBuilder buffer; - status.toVelocyPack(buffer); - generateOk(rest::ResponseCode::OK, buffer.slice()); - })); + auto status = co_await methods.getLocalStatus(logId); + + VPackBuilder buffer; + status.toVelocyPack(buffer); + generateOk(rest::ResponseCode::OK, buffer.slice()); } -RestStatus RestLogHandler::handleGetGlobalStatus( - ReplicatedLogMethods const& methods, replication2::LogId logId) { +auto RestLogHandler::handleGetGlobalStatus(ReplicatedLogMethods const& methods, + replication2::LogId logId) + -> async { if (_request->suffixes().size() != 2) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//global-status"); - return RestStatus::DONE; + co_return; } auto specSource = std::invoke([&] { @@ -594,47 +556,39 @@ RestStatus RestLogHandler::handleGetGlobalStatus( } return replicated_log::GlobalStatus::SpecificationSource::kRemoteAgency; }); + auto status = co_await methods.getGlobalStatus(logId, specSource); - return waitForFuture(methods.getGlobalStatus(logId, specSource) - .thenValue([this](auto&& status) { - VPackBuilder buffer; - status.toVelocyPack(buffer); - generateOk(rest::ResponseCode::OK, buffer.slice()); - })); + VPackBuilder buffer; + status.toVelocyPack(buffer); + generateOk(rest::ResponseCode::OK, buffer.slice()); } -RestStatus RestLogHandler::handleGetCollectionStatus( - replication2::ReplicatedLogMethods const& methods, CollectionID cid) { - return waitForFuture(methods.getCollectionStatus(std::move(cid)) - .thenValue([this](auto&& status) { - VPackBuilder buffer; - status.toVelocyPack(buffer); - generateOk(rest::ResponseCode::OK, buffer.slice()); - })); +auto RestLogHandler::handleGetCollectionStatus( + replication2::ReplicatedLogMethods const& methods, CollectionID cid) + -> async { + auto status = co_await methods.getCollectionStatus(std::move(cid)); + VPackBuilder buffer; + status.toVelocyPack(buffer); + generateOk(rest::ResponseCode::OK, buffer.slice()); } -RestStatus RestLogHandler::handleGetEntry(ReplicatedLogMethods const& methods, - replication2::LogId logId) { +auto RestLogHandler::handleGetEntry(ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async { auto const& suffixes = _request->suffixes(); if (suffixes.size() != 3) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_api/log//entry/"); - return RestStatus::DONE; + co_return; } LogIndex logIdx{basics::StringUtils::uint64(suffixes[2])}; - auto fut = - methods.slice(logId, logIdx, logIdx + 1) - .thenValue([this](std::unique_ptr&& iter) { - if (auto entry = iter->next(); entry) { - VPackBuilder result; - entry->toVelocyPack(result); - generateOk(rest::ResponseCode::OK, result.slice()); - } else { - generateError(rest::ResponseCode::NOT_FOUND, - TRI_ERROR_HTTP_NOT_FOUND, "log index not found"); - } - }); - - return waitForFuture(std::move(fut)); + auto iter = co_await methods.slice(logId, logIdx, logIdx + 1); + if (auto entry = iter->next(); entry) { + VPackBuilder result; + entry->toVelocyPack(result); + generateOk(rest::ResponseCode::OK, result.slice()); + } else { + generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, + "log index not found"); + } } diff --git a/arangod/RestHandler/RestLogHandler.h b/arangod/RestHandler/RestLogHandler.h index b32465322bfc..3ff7045e9f19 100644 --- a/arangod/RestHandler/RestLogHandler.h +++ b/arangod/RestHandler/RestLogHandler.h @@ -36,55 +36,56 @@ class RestLogHandler : public RestVocbaseBaseHandler { RestLogHandler(ArangodServer&, GeneralRequest*, GeneralResponse*); public: - RestStatus execute() final; + auto executeAsync() -> futures::Future override; char const* name() const final { return "RestLogHandler"; } RequestLane lane() const final { return RequestLane::CLIENT_SLOW; } private: - RestStatus executeByMethod(replication2::ReplicatedLogMethods const& methods); - RestStatus handleGetRequest( - replication2::ReplicatedLogMethods const& methods); - RestStatus handlePostRequest( - replication2::ReplicatedLogMethods const& methods); - RestStatus handleDeleteRequest( - replication2::ReplicatedLogMethods const& methods); + auto executeByMethod(replication2::ReplicatedLogMethods const& methods) + -> async; + auto handleGetRequest(replication2::ReplicatedLogMethods const& methods) + -> async; + auto handlePostRequest(replication2::ReplicatedLogMethods const& methods) + -> async; + auto handleDeleteRequest(replication2::ReplicatedLogMethods const& methods) + -> async; - RestStatus handlePost(replication2::ReplicatedLogMethods const& methods, - velocypack::Slice specSlice); - RestStatus handlePostInsert(replication2::ReplicatedLogMethods const& methods, - replication2::LogId logId, - velocypack::Slice payload); - RestStatus handlePostPing(replication2::ReplicatedLogMethods const& methods, - replication2::LogId logId, - velocypack::Slice payload); - RestStatus handlePostInsertMulti( - replication2::ReplicatedLogMethods const& methods, - replication2::LogId logId, velocypack::Slice payload); - RestStatus handlePostRelease( - replication2::ReplicatedLogMethods const& methods, - replication2::LogId logId); - RestStatus handlePostCompact( - replication2::ReplicatedLogMethods const& methods, - replication2::LogId logId); + auto handlePost(replication2::ReplicatedLogMethods const& methods, + velocypack::Slice specSlice) -> async; + auto handlePostInsert(replication2::ReplicatedLogMethods const& methods, + replication2::LogId logId, velocypack::Slice payload) + -> async; + auto handlePostPing(replication2::ReplicatedLogMethods const& methods, + replication2::LogId logId, velocypack::Slice payload) + -> async; + auto handlePostInsertMulti(replication2::ReplicatedLogMethods const& methods, + replication2::LogId logId, + velocypack::Slice payload) -> async; + auto handlePostRelease(replication2::ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async; + auto handlePostCompact(replication2::ReplicatedLogMethods const& methods, + replication2::LogId logId) -> async; - RestStatus handleGet(replication2::ReplicatedLogMethods const& methods); - RestStatus handleGetPoll(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); - RestStatus handleGetHead(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); - RestStatus handleGetTail(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); - RestStatus handleGetSlice(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); - RestStatus handleGetLocalStatus( - replication2::ReplicatedLogMethods const& methods, replication2::LogId); - RestStatus handleGetGlobalStatus( - replication2::ReplicatedLogMethods const& methods, replication2::LogId); - RestStatus handleGetCollectionStatus( - replication2::ReplicatedLogMethods const& methods, CollectionID cid); - RestStatus handleGetLog(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); - RestStatus handleGetEntry(replication2::ReplicatedLogMethods const& methods, - replication2::LogId); + auto handleGet(replication2::ReplicatedLogMethods const& methods) + -> async; + auto handleGetPoll(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetHead(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetTail(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetSlice(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetLocalStatus(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetGlobalStatus(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetCollectionStatus( + replication2::ReplicatedLogMethods const& methods, CollectionID cid) + -> async; + auto handleGetLog(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; + auto handleGetEntry(replication2::ReplicatedLogMethods const& methods, + replication2::LogId) -> async; }; } // namespace arangodb diff --git a/arangod/RestHandler/RestLogInternalHandler.cpp b/arangod/RestHandler/RestLogInternalHandler.cpp index c73da2ea41d3..29f537bedf04 100644 --- a/arangod/RestHandler/RestLogInternalHandler.cpp +++ b/arangod/RestHandler/RestLogInternalHandler.cpp @@ -28,6 +28,8 @@ #include "Replication2/ReplicatedLog/LogLeader.h" #include "absl/strings/str_cat.h" +#include + using namespace arangodb; using namespace arangodb::replication2; @@ -37,25 +39,27 @@ RestLogInternalHandler::RestLogInternalHandler(ArangodServer& server, : RestVocbaseBaseHandler(server, req, resp) {} RestLogInternalHandler::~RestLogInternalHandler() = default; -RestStatus RestLogInternalHandler::execute() { +auto RestLogInternalHandler::executeAsync() -> futures::Future { // for now required admin access to the database if (!ExecContext::current().isSuperuser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (_request->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() == 2) { if (suffixes[1] == "update-snapshot-status") { - return handleUpdateSnapshotStatus(); + handleUpdateSnapshotStatus(); + co_return; } else if (suffixes[1] == "append-entries") { - return handleAppendEntries(); + co_await handleAppendEntries(); + co_return; } } @@ -63,49 +67,47 @@ RestStatus RestLogInternalHandler::execute() { rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect POST " "/_api/log-internal//[append-entries|update-snapshot-status]"); - return RestStatus::DONE; + co_return; } -RestStatus RestLogInternalHandler::handleAppendEntries() { +auto RestLogInternalHandler::handleAppendEntries() -> async { std::vector const& suffixes = _request->suffixes(); bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - return RestStatus::DONE; + co_return; } LogId logId{basics::StringUtils::uint64(suffixes[0])}; auto request = replicated_log::AppendEntriesRequest::fromVelocyPack(body); - auto f = _vocbase.getReplicatedLogFollowerById(logId) - ->appendEntries(request) - .thenValue([this](replicated_log::AppendEntriesResult&& res) { - VPackBuilder builder; - res.toVelocyPack(builder); - // TODO fix the result type here. Currently we always return - // the error under the - // `result` field. Maybe we want to change the HTTP status - // code as well? Don't forget to update the deserializer - // that reads the response! - generateOk(rest::ResponseCode::ACCEPTED, builder.slice()); - }); + auto res = + co_await _vocbase.getReplicatedLogFollowerById(logId)->appendEntries( + request); + + VPackBuilder builder; + res.toVelocyPack(builder); + // TODO fix the result type here. Currently we always return the error under + // the `result` field. Maybe we want to change the HTTP status code as + // well? Don't forget to update the deserializer that reads the response! + generateOk(rest::ResponseCode::ACCEPTED, builder.slice()); - return waitForFuture(std::move(f)); + co_return; } -RestStatus RestLogInternalHandler::handleUpdateSnapshotStatus() { +void RestLogInternalHandler::handleUpdateSnapshotStatus() { auto const& suffixes = _request->suffixes(); auto maybeLogId = LogId::fromString(suffixes[0]); if (!maybeLogId.has_value()) { generateError(Result(TRI_ERROR_HTTP_BAD_PARAMETER, absl::StrCat("Not a log id: ", suffixes[0]))); - return RestStatus::DONE; + return; } auto logId = *maybeLogId; auto participant = _request->value("follower"); bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - return RestStatus::DONE; + return; } auto report = velocypack::deserialize(body); @@ -117,5 +119,4 @@ RestStatus RestLogInternalHandler::handleUpdateSnapshotStatus() { } else { generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); } - return RestStatus::DONE; } diff --git a/arangod/RestHandler/RestLogInternalHandler.h b/arangod/RestHandler/RestLogInternalHandler.h index 12c9e500f5f7..3645520ba544 100644 --- a/arangod/RestHandler/RestLogInternalHandler.h +++ b/arangod/RestHandler/RestLogInternalHandler.h @@ -36,12 +36,12 @@ class RestLogInternalHandler : public RestVocbaseBaseHandler { ~RestLogInternalHandler() override; public: - RestStatus execute() final; + auto executeAsync() -> futures::Future final; char const* name() const final { return "RestLogInternalHandler"; } RequestLane lane() const final { return RequestLane::CLIENT_SLOW; } private: - RestStatus handleAppendEntries(); - RestStatus handleUpdateSnapshotStatus(); + auto handleAppendEntries() -> async; + void handleUpdateSnapshotStatus(); }; } // namespace arangodb diff --git a/arangod/RestHandler/RestMetricsHandler.cpp b/arangod/RestHandler/RestMetricsHandler.cpp index afaa4da775e8..1c426d33dcc8 100644 --- a/arangod/RestHandler/RestMetricsHandler.cpp +++ b/arangod/RestHandler/RestMetricsHandler.cpp @@ -37,7 +37,8 @@ #include "Network/NetworkFeature.h" #include "Network/Utils.h" #include "Rest/Version.h" -#include "RestServer/ServerFeature.h" + +#include #include #include @@ -87,20 +88,20 @@ RestMetricsHandler::RestMetricsHandler(ArangodServer& server, GeneralResponse* response) : RestBaseHandler(server, request, response) {} -RestStatus RestMetricsHandler::execute() { +auto RestMetricsHandler::executeAsync() -> futures::Future { auto& security = server().getFeature(); if (!security.canAccessHardenedApi()) { // don't leak information about server internals here generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (_request->requestType() != RequestType::GET) { // TODO(MBkkt) Now our API return 405 errorCode for 400 HTTP response code // I think we should fix it, but its breaking change generateError(ResponseCode::BAD, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool foundServerId; @@ -180,11 +181,12 @@ RestStatus RestMetricsHandler::execute() { generateError( notFound ? rest::ResponseCode::NOT_FOUND : rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, error); - return RestStatus::DONE; + co_return; } if (foundServerId) { - return makeRedirection(serverId, false); + co_await makeRedirection(serverId, false); + co_return; } _response->setAllowCompression( @@ -200,14 +202,14 @@ RestStatus RestMetricsHandler::execute() { } else { _response->addPayload(VPackSlice::nullSlice()); } - return RestStatus::DONE; + co_return; } auto& metrics = server().getFeature(); if (!metrics.exportAPI()) { // don't export metrics, if so desired generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; + co_return; } // only export standard metrics @@ -219,7 +221,7 @@ RestStatus RestMetricsHandler::execute() { _response->setResponseCode(rest::ResponseCode::OK); _response->setContentType(rest::ContentType::VPACK); _response->addPayload(builder.slice()); - return RestStatus::DONE; + co_return; } auto const leader = [&]() -> std::optional { @@ -233,7 +235,7 @@ RestStatus RestMetricsHandler::execute() { if (leader && (leader->empty() || type == metrics::kLast)) { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "We didn't find leader server"); - return RestStatus::DONE; + co_return; } if (!leader) { @@ -242,16 +244,17 @@ RestStatus RestMetricsHandler::execute() { _response->setResponseCode(rest::ResponseCode::OK); _response->setContentType(rest::ContentType::TEXT); _response->addRawPayload(result); - return RestStatus::DONE; + co_return; } TRI_ASSERT(mode == metrics::CollectMode::ReadGlobal || mode == metrics::CollectMode::WriteGlobal) << modeStr; - return makeRedirection(*leader, true); + co_await makeRedirection(*leader, true); + co_return; } -RestStatus RestMetricsHandler::makeRedirection(std::string const& serverId, - bool last) { +auto RestMetricsHandler::makeRedirection(std::string const& serverId, bool last) + -> async { auto* pool = server().getFeature().pool(); if (pool == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_SHUTTING_DOWN); @@ -265,40 +268,36 @@ RestStatus RestMetricsHandler::makeRedirection(std::string const& serverId, options.parameters.try_emplace("type", metrics::kLast); } - auto f = network::sendRequest(pool, "server:" + serverId, - fuerte::RestVerb::Get, _request->requestPath(), - VPackBuffer{}, options); - - return waitForFuture(std::move(f).thenValue([self = shared_from_this(), - last](network::Response&& r) { - auto& me = basics::downCast(*self); - if (r.fail() || !r.hasResponse()) { - TRI_ASSERT(r.fail()); - me.generateError(r.combinedResult()); - return; - } - if (last) { - auto& cm = me.server().getFeature(); - if (cm.isEnabled()) { - cm.update(metrics::CollectMode::TriggerGlobal); - } - } - // TODO(MBkkt) move response - // the response will not contain any velocypack. - // we need to forward the request with content-type text/plain. - if (r.response().header.meta().contains(StaticStrings::ContentEncoding)) { - // forward original Content-Encoding header - me._response->setHeaderNC( - StaticStrings::ContentEncoding, - r.response().header.metaByKey(StaticStrings::ContentEncoding)); + auto r = co_await network::sendRequest( + pool, "server:" + serverId, fuerte::RestVerb::Get, + _request->requestPath(), VPackBuffer{}, options); + + if (r.fail() || !r.hasResponse()) { + TRI_ASSERT(r.fail()); + generateError(r.combinedResult()); + co_return; + } + if (last) { + auto& cm = server().getFeature(); + if (cm.isEnabled()) { + cm.update(metrics::CollectMode::TriggerGlobal); } + } + // TODO(MBkkt) move response + // the response will not contain any velocypack. + // we need to forward the request with content-type text/plain. + if (r.response().header.meta().contains(StaticStrings::ContentEncoding)) { + // forward original Content-Encoding header + _response->setHeaderNC( + StaticStrings::ContentEncoding, + r.response().header.metaByKey(StaticStrings::ContentEncoding)); + } - me._response->setResponseCode(rest::ResponseCode::OK); - me._response->setContentType(rest::ContentType::TEXT); - auto payload = r.response().stealPayload(); - me._response->addRawPayload( - {reinterpret_cast(payload->data()), payload->size()}); - })); + _response->setResponseCode(rest::ResponseCode::OK); + _response->setContentType(rest::ContentType::TEXT); + auto payload = r.response().stealPayload(); + _response->addRawPayload( + {reinterpret_cast(payload->data()), payload->size()}); } } // namespace arangodb diff --git a/arangod/RestHandler/RestMetricsHandler.h b/arangod/RestHandler/RestMetricsHandler.h index e91e1427a6eb..24877f25053c 100644 --- a/arangod/RestHandler/RestMetricsHandler.h +++ b/arangod/RestHandler/RestMetricsHandler.h @@ -37,10 +37,10 @@ class RestMetricsHandler : public arangodb::RestBaseHandler { /// @brief must be on fast lane so that metrics can always be retrieved, /// even from otherwise totally busy servers RequestLane lane() const final { return RequestLane::CLIENT_FAST; } - RestStatus execute() final; + auto executeAsync() -> futures::Future override; private: - RestStatus makeRedirection(std::string const& serverId, bool last); + auto makeRedirection(std::string const& serverId, bool last) -> async; }; } // namespace arangodb diff --git a/arangod/RestHandler/RestReplicationHandler.cpp b/arangod/RestHandler/RestReplicationHandler.cpp index 95d6777bec81..a81db60dee9b 100644 --- a/arangod/RestHandler/RestReplicationHandler.cpp +++ b/arangod/RestHandler/RestReplicationHandler.cpp @@ -440,11 +440,11 @@ std::string const RestReplicationHandler::HoldReadLockCollection = "holdReadLockCollection"; // main function that dispatches the different routes and commands -RestStatus RestReplicationHandler::execute() { +auto RestReplicationHandler::executeAsync() -> futures::Future { auto res = testPermissions(); if (!res.ok()) { generateError(res); - return RestStatus::DONE; + co_return; } // extract the request type auto const type = _request->requestType(); @@ -465,7 +465,7 @@ RestStatus RestReplicationHandler::execute() { goto BAD_CALL; } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandLoggerTickRanges(); } else if (command == LoggerFirstTick) { @@ -473,7 +473,7 @@ RestStatus RestReplicationHandler::execute() { goto BAD_CALL; } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandLoggerFirstTick(); } else if (command == LoggerFollow) { @@ -481,7 +481,7 @@ RestStatus RestReplicationHandler::execute() { goto BAD_CALL; } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } // track the number of parallel invocations of the tailing API auto& rf = _vocbase.server().getFeature(); @@ -504,7 +504,8 @@ RestStatus RestReplicationHandler::execute() { if (ServerState::instance()->isCoordinator()) { handleUnforwardedTrampolineCoordinator(); } else { - return waitForFuture(handleCommandBatch()); + co_await handleCommandBatch(); + co_return; } } else if (command == Inventory) { // get overview of collections and indexes followed by some extra data @@ -541,7 +542,7 @@ RestStatus RestReplicationHandler::execute() { } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } if (type == rest::RequestType::POST) { @@ -554,7 +555,8 @@ RestStatus RestReplicationHandler::execute() { // { "id": , // "count": // } - return waitForFuture(handleCommandCreateKeys()); + co_await handleCommandCreateKeys(); + co_return; } else if (type == rest::RequestType::GET) { // curl --dump - // 'http://localhost:5555/_db/_system/_api/replication/keys/123?collection=_users' @@ -567,7 +569,7 @@ RestStatus RestReplicationHandler::execute() { } } else if (command == Revisions) { if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } if (len > 1) { @@ -575,7 +577,8 @@ RestStatus RestReplicationHandler::execute() { if (type == rest::RequestType::GET && subCommand == Tree) { handleCommandRevisionTree(); } else if (type == rest::RequestType::POST && subCommand == Tree) { - return waitForFuture(handleCommandRebuildRevisionTree()); + co_await handleCommandRebuildRevisionTree(); + co_return; #ifdef ARANGODB_ENABLE_FAILURE_TESTS } else if (type == rest::RequestType::PUT && subCommand == Tree) { handleCommandCorruptRevisionTree(); @@ -612,7 +615,8 @@ RestStatus RestReplicationHandler::execute() { goto BAD_CALL; } - return waitForFuture(handleCommandRestoreCollection()); + co_await handleCommandRestoreCollection(); + co_return; } else if (command == RestoreIndexes) { if (type != rest::RequestType::PUT) { goto BAD_CALL; @@ -623,7 +627,8 @@ RestStatus RestReplicationHandler::execute() { if (type != rest::RequestType::PUT) { goto BAD_CALL; } - return waitForFuture(handleCommandRestoreData()); + co_await handleCommandRestoreData(); + co_return; } else if (command == RestoreView) { if (type != rest::RequestType::PUT) { goto BAD_CALL; @@ -636,7 +641,7 @@ RestStatus RestReplicationHandler::execute() { } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandSync(); @@ -647,7 +652,7 @@ RestStatus RestReplicationHandler::execute() { } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandMakeFollower(); @@ -671,7 +676,7 @@ RestStatus RestReplicationHandler::execute() { } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandApplierStart(); @@ -681,7 +686,7 @@ RestStatus RestReplicationHandler::execute() { } if (isCoordinatorError()) { - return RestStatus::DONE; + co_return; } handleCommandApplierStop(); @@ -717,7 +722,8 @@ RestStatus RestReplicationHandler::execute() { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_CLUSTER_ONLY_ON_DBSERVER); } else { - return waitForFuture(handleCommandAddFollower()); + co_await handleCommandAddFollower(); + co_return; } } else if (command == RemoveFollower) { if (type != rest::RequestType::PUT) { @@ -745,7 +751,8 @@ RestStatus RestReplicationHandler::execute() { TRI_ERROR_CLUSTER_ONLY_ON_DBSERVER); } else { if (type == rest::RequestType::POST) { - return waitForFuture(handleCommandHoldReadLockCollection()); + co_await handleCommandHoldReadLockCollection(); + co_return; } else if (type == rest::RequestType::DELETE_REQ) { handleCommandCancelHoldReadLockCollection(); } else if (type == rest::RequestType::GET) { @@ -759,7 +766,7 @@ RestStatus RestReplicationHandler::execute() { std::string("invalid command '") + command + "'"); } - return RestStatus::DONE; + co_return; } BAD_CALL: @@ -771,7 +778,7 @@ RestStatus RestReplicationHandler::execute() { TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } - return RestStatus::DONE; + co_return; } Result RestReplicationHandler::testPermissions() { diff --git a/arangod/RestHandler/RestReplicationHandler.h b/arangod/RestHandler/RestReplicationHandler.h index 268cd25c2dfa..881afa52741b 100644 --- a/arangod/RestHandler/RestReplicationHandler.h +++ b/arangod/RestHandler/RestReplicationHandler.h @@ -55,7 +55,7 @@ class RestReplicationHandler : public RestVocbaseBaseHandler { public: RequestLane lane() const override final; - RestStatus execute() override; + auto executeAsync() -> futures::Future override; // Never instantiate this. // Only specific implementations allowed diff --git a/arangod/RestHandler/RestTransactionHandler.cpp b/arangod/RestHandler/RestTransactionHandler.cpp index f515b5c6a85d..418e02b38e21 100644 --- a/arangod/RestHandler/RestTransactionHandler.cpp +++ b/arangod/RestHandler/RestTransactionHandler.cpp @@ -107,12 +107,13 @@ RequestLane RestTransactionHandler::lane() const { return RequestLane::CLIENT_V8; } -RestStatus RestTransactionHandler::execute() { +auto RestTransactionHandler::executeAsync() -> futures::Future { switch (_request->requestType()) { case rest::RequestType::POST: if (_request->suffixes().size() == 1 && _request->suffixes()[0] == "begin") { - return waitForFuture(executeBegin()); + co_await executeBegin(); + co_return; } else if (_request->suffixes().empty()) { executeJSTransaction(); } else { @@ -121,10 +122,12 @@ RestStatus RestTransactionHandler::execute() { break; case rest::RequestType::PUT: - return waitForFuture(executeCommit()); + co_await executeCommit(); + co_return; case rest::RequestType::DELETE_REQ: - return waitForFuture(executeAbort()); + co_await executeAbort(); + co_return; case rest::RequestType::GET: executeGetState(); @@ -135,7 +138,7 @@ RestStatus RestTransactionHandler::execute() { TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); break; } - return RestStatus::DONE; + co_return; } void RestTransactionHandler::executeGetState() { diff --git a/arangod/RestHandler/RestTransactionHandler.h b/arangod/RestHandler/RestTransactionHandler.h index 83ec5e7566a0..21934721cf78 100644 --- a/arangod/RestHandler/RestTransactionHandler.h +++ b/arangod/RestHandler/RestTransactionHandler.h @@ -42,7 +42,7 @@ class RestTransactionHandler : public arangodb::RestVocbaseBaseHandler { char const* name() const override final { return "RestTransactionHandler"; } RequestLane lane() const override final; - RestStatus execute() override; + auto executeAsync() -> futures::Future override; void cancel() override final; protected: diff --git a/arangod/RestHandler/RestUsageMetricsHandler.cpp b/arangod/RestHandler/RestUsageMetricsHandler.cpp index 334ff12cbb1f..ced1828b29ae 100644 --- a/arangod/RestHandler/RestUsageMetricsHandler.cpp +++ b/arangod/RestHandler/RestUsageMetricsHandler.cpp @@ -31,12 +31,11 @@ #include "GeneralServer/ServerSecurityFeature.h" #include "Metrics/MetricsFeature.h" #include "Metrics/MetricsParts.h" -#include "Metrics/Types.h" #include "Network/Methods.h" #include "Network/NetworkFeature.h" #include "Network/Utils.h" -#include "Rest/Version.h" -#include "RestServer/ServerFeature.h" + +#include namespace arangodb { @@ -45,18 +44,18 @@ RestUsageMetricsHandler::RestUsageMetricsHandler(ArangodServer& server, GeneralResponse* response) : RestBaseHandler(server, request, response) {} -RestStatus RestUsageMetricsHandler::execute() { +auto RestUsageMetricsHandler::executeAsync() -> futures::Future { auto& security = server().getFeature(); if (!security.canAccessHardenedApi()) { // don't leak information about server internals here generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (_request->requestType() != RequestType::GET) { generateError(ResponseCode::BAD, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool foundServerId = false; @@ -70,12 +69,13 @@ RestStatus RestUsageMetricsHandler::execute() { if (!ci.serverExists(serverId)) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "Unknown value of serverId parameter."); - return RestStatus::DONE; + co_return; } } if (foundServerId) { - return makeRedirection(serverId); + co_await makeRedirection(serverId); + co_return; } _response->setAllowCompression( @@ -85,7 +85,7 @@ RestStatus RestUsageMetricsHandler::execute() { if (!metrics.exportAPI()) { // don't export metrics, if so desired generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; + co_return; } // only export dynamic metrics for shard usage @@ -96,11 +96,11 @@ RestStatus RestUsageMetricsHandler::execute() { _response->setResponseCode(rest::ResponseCode::OK); _response->setContentType(rest::ContentType::TEXT); _response->addRawPayload(result); - return RestStatus::DONE; + co_return; } -RestStatus RestUsageMetricsHandler::makeRedirection( - std::string const& serverId) { +auto RestUsageMetricsHandler::makeRedirection(std::string const& serverId) + -> async { auto* pool = server().getFeature().pool(); if (pool == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_SHUTTING_DOWN); @@ -111,33 +111,29 @@ RestStatus RestUsageMetricsHandler::makeRedirection( options.database = _request->databaseName(); options.parameters = _request->parameters(); - auto f = network::sendRequest(pool, "server:" + serverId, - fuerte::RestVerb::Get, _request->requestPath(), - VPackBuffer{}, options); - - return waitForFuture(std::move(f).thenValue([self = shared_from_this()]( - network::Response&& r) { - auto& me = basics::downCast(*self); - if (r.fail() || !r.hasResponse()) { - TRI_ASSERT(r.fail()); - me.generateError(r.combinedResult()); - return; - } - // the response will not contain any velocypack. - // we need to forward the request with content-type text/plain. - if (r.response().header.meta().contains(StaticStrings::ContentEncoding)) { - // forward original Content-Encoding header - me._response->setHeaderNC( - StaticStrings::ContentEncoding, - r.response().header.metaByKey(StaticStrings::ContentEncoding)); - } + auto r = co_await network::sendRequest( + pool, "server:" + serverId, fuerte::RestVerb::Get, + _request->requestPath(), VPackBuffer{}, options); + + if (r.fail() || !r.hasResponse()) { + TRI_ASSERT(r.fail()); + generateError(r.combinedResult()); + co_return; + } + // the response will not contain any velocypack. + // we need to forward the request with content-type text/plain. + if (r.response().header.meta().contains(StaticStrings::ContentEncoding)) { + // forward original Content-Encoding header + _response->setHeaderNC( + StaticStrings::ContentEncoding, + r.response().header.metaByKey(StaticStrings::ContentEncoding)); + } - me._response->setResponseCode(rest::ResponseCode::OK); - me._response->setContentType(rest::ContentType::TEXT); - auto payload = r.response().stealPayload(); - me._response->addRawPayload( - {reinterpret_cast(payload->data()), payload->size()}); - })); + _response->setResponseCode(rest::ResponseCode::OK); + _response->setContentType(rest::ContentType::TEXT); + auto payload = r.response().stealPayload(); + _response->addRawPayload( + {reinterpret_cast(payload->data()), payload->size()}); } } // namespace arangodb diff --git a/arangod/RestHandler/RestUsageMetricsHandler.h b/arangod/RestHandler/RestUsageMetricsHandler.h index b0cba040321e..0f4c76877a88 100644 --- a/arangod/RestHandler/RestUsageMetricsHandler.h +++ b/arangod/RestHandler/RestUsageMetricsHandler.h @@ -37,10 +37,10 @@ class RestUsageMetricsHandler : public arangodb::RestBaseHandler { /// @brief must be on fast lane so that metrics can always be retrieved, /// even from otherwise totally busy servers RequestLane lane() const final { return RequestLane::CLIENT_FAST; } - RestStatus execute() final; + auto executeAsync() -> futures::Future final; private: - RestStatus makeRedirection(std::string const& serverId); + auto makeRedirection(std::string const& serverId) -> async; }; } // namespace arangodb From f888ccb8dba0916cf70818a0095e59068f35fa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 13:57:25 +0200 Subject: [PATCH 07/35] linter fixes --- arangod/RestHandler/RestAdminLogHandler.cpp | 2 +- arangod/RestHandler/RestTransactionHandler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arangod/RestHandler/RestAdminLogHandler.cpp b/arangod/RestHandler/RestAdminLogHandler.cpp index af4f843006bc..e4f15115d105 100644 --- a/arangod/RestHandler/RestAdminLogHandler.cpp +++ b/arangod/RestHandler/RestAdminLogHandler.cpp @@ -418,7 +418,7 @@ auto RestAdminLogHandler::reportLogs(bool newFormat) -> async { result.close(); result.close(); // Close the result object - } // format end + } // format end generateResult(rest::ResponseCode::OK, result.slice()); co_return; diff --git a/arangod/RestHandler/RestTransactionHandler.h b/arangod/RestHandler/RestTransactionHandler.h index 21934721cf78..348403d9f040 100644 --- a/arangod/RestHandler/RestTransactionHandler.h +++ b/arangod/RestHandler/RestTransactionHandler.h @@ -42,7 +42,7 @@ class RestTransactionHandler : public arangodb::RestVocbaseBaseHandler { char const* name() const override final { return "RestTransactionHandler"; } RequestLane lane() const override final; - auto executeAsync() -> futures::Future override; + auto executeAsync() -> futures::Future override; void cancel() override final; protected: From f153318931e47d38d64e07bf7b0850392eabf86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 14:00:00 +0200 Subject: [PATCH 08/35] Refactored RestAdminClusterHandler to use executeAsync instead of execute+waitForFuture --- .../RestHandler/RestAdminClusterHandler.cpp | 1106 ++++++++--------- arangod/RestHandler/RestAdminClusterHandler.h | 122 +- 2 files changed, 589 insertions(+), 639 deletions(-) diff --git a/arangod/RestHandler/RestAdminClusterHandler.cpp b/arangod/RestHandler/RestAdminClusterHandler.cpp index 84a0a8cf28d3..237589daf6eb 100644 --- a/arangod/RestHandler/RestAdminClusterHandler.cpp +++ b/arangod/RestHandler/RestAdminClusterHandler.cpp @@ -355,7 +355,7 @@ std::string const RestAdminClusterHandler::VPackSortMigrationMigrate = "migrate"; std::string const RestAdminClusterHandler::VPackSortMigrationStatus = "status"; -RestStatus RestAdminClusterHandler::execute() { +auto RestAdminClusterHandler::executeAsync() -> futures::Future { // here we first do a glboal check, which is based on the setting in startup // option // `--cluster.api-jwt-policy`: @@ -378,7 +378,7 @@ RestStatus RestAdminClusterHandler::execute() { if (apiJwtPolicy == "jwt-all" || (apiJwtPolicy == "jwt-write" && isWriteOperation)) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } } @@ -394,69 +394,89 @@ RestStatus RestAdminClusterHandler::execute() { std::string const& command = suffixes.at(0); if (command == Health) { - return handleHealth(); + co_await handleHealth(); + co_return; } else if (command == NumberOfServers) { - return handleNumberOfServers(); + co_await handleNumberOfServers(); + co_return; } else if (command == Maintenance) { - return handleMaintenance(); + co_await handleMaintenance(); + co_return; } else if (command == NodeVersion) { - return handleNodeVersion(); + co_await handleNodeVersion(); + co_return; } else if (command == NodeEngine) { - return handleNodeEngine(); + co_await handleNodeEngine(); + co_return; } else if (command == NodeStatistics) { - return handleNodeStatistics(); + co_await handleNodeStatistics(); + co_return; } else if (command == Statistics) { - return handleStatistics(); + co_await handleStatistics(); + co_return; } else if (command == ShardDistribution) { - return handleShardDistribution(); + handleShardDistribution(); + co_return; } else if (command == CollectionShardDistribution) { - return handleCollectionShardDistribution(); + handleCollectionShardDistribution(); + co_return; } else if (command == CleanoutServer) { - return handleCleanoutServer(); + co_await handleCleanoutServer(); + co_return; } else if (command == ResignLeadership) { - return handleResignLeadership(); + co_await handleResignLeadership(); + co_return; } else if (command == MoveShard) { - return handleMoveShard(); + co_await handleMoveShard(); + co_return; } else if (command == CancelJob) { - return handleCancelJob(); + co_await handleCancelJob(); + co_return; } else if (command == QueryJobStatus) { - return handleQueryJobStatus(); + co_await handleQueryJobStatus(); + co_return; } else if (command == RemoveServer) { - return handleRemoveServer(); + co_await handleRemoveServer(); + co_return; } else if (command == RebalanceShards) { - return handleRebalanceShards(); + co_await handleRebalanceShards(); + co_return; } else if (command == Rebalance) { - return handleRebalance(); + co_await handleRebalance(); + co_return; } else if (command == ShardStatistics) { - return handleShardStatistics(); + handleShardStatistics(); + co_return; } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, std::string("invalid command '") + command + "'"); - return RestStatus::DONE; + co_return; } } else if (len == 2) { std::string const& command = suffixes.at(0); if (command == Rebalance) { - return handleRebalance(); + co_await handleRebalance(); + co_return; } else if (command == Maintenance) { - return handleDBServerMaintenance(suffixes.at(1)); + co_await handleDBServerMaintenance(suffixes.at(1)); + co_return; } else if (command == VPackSortMigration) { - return waitForFuture(handleVPackSortMigration(suffixes.at(1))); + co_await handleVPackSortMigration(suffixes.at(1)); + co_return; } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, std::string("invalid command '") + command + "', expecting 'rebalance' or 'maintenance'"); - return RestStatus::DONE; + co_return; } } generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "expecting URL /_admin/cluster/"); - return RestStatus::DONE; + co_return; } -RestAdminClusterHandler::FutureVoid -RestAdminClusterHandler::retryTryDeleteServer( +futures::Future RestAdminClusterHandler::retryTryDeleteServer( std::unique_ptr&& ctx) { if (++ctx->tries < 60) { return SchedulerFeature::SCHEDULER->delay("remove-server", 1s) @@ -471,7 +491,7 @@ RestAdminClusterHandler::retryTryDeleteServer( } } -RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::tryDeleteServer( +futures::Future RestAdminClusterHandler::tryDeleteServer( std::unique_ptr&& ctx) { auto rootPath = arangodb::cluster::paths::root()->arango(); VPackBuffer trx; @@ -609,37 +629,36 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::tryDeleteServer( }); } -RestStatus RestAdminClusterHandler::handlePostRemoveServer( +async RestAdminClusterHandler::handlePostRemoveServer( std::string const& server) { auto ctx = std::make_unique(server); - return waitForFuture( - tryDeleteServer(std::move(ctx)) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + co_await tryDeleteServer(std::move(ctx)); + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleRemoveServer() { +async RestAdminClusterHandler::handleRemoveServer() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } VPackSlice server = VPackSlice::noneSlice(); @@ -651,37 +670,37 @@ RestStatus RestAdminClusterHandler::handleRemoveServer() { if (server.isString()) { std::string serverId = resolveServerNameID(server.copyString()); - return handlePostRemoveServer(serverId); + co_return co_await handlePostRemoveServer(serverId); } generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "expecting string or object with key `server`"); - return RestStatus::DONE; + co_return; } -RestStatus RestAdminClusterHandler::handleShardStatistics() { +void RestAdminClusterHandler::handleShardStatistics() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + return; } if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::NOT_IMPLEMENTED, TRI_ERROR_CLUSTER_ONLY_ON_COORDINATOR); - return RestStatus::DONE; + return; } if (!_vocbase.isSystem()) { generateError( GeneralResponse::responseCode(TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE), TRI_ERROR_ARANGO_USE_SYSTEM_DATABASE); - return RestStatus::DONE; + return; } std::string const& restrictServer = _request->value("DBserver"); @@ -703,16 +722,14 @@ RestStatus RestAdminClusterHandler::handleShardStatistics() { } else { generateError(res); } - - return RestStatus::DONE; } -RestStatus RestAdminClusterHandler::handleCleanoutServer() { - return handleSingleServerJob("cleanOutServer"); +async RestAdminClusterHandler::handleCleanoutServer() { + co_return co_await handleSingleServerJob("cleanOutServer"); } -RestStatus RestAdminClusterHandler::handleResignLeadership() { - return handleSingleServerJob("resignLeadership"); +async RestAdminClusterHandler::handleResignLeadership() { + co_return co_await handleSingleServerJob("resignLeadership"); } std::unique_ptr @@ -743,23 +760,23 @@ RestAdminClusterHandler::MoveShardContext::fromVelocyPack(VPackSlice slice) { return nullptr; } -RestStatus RestAdminClusterHandler::handleMoveShard() { +async RestAdminClusterHandler::handleMoveShard() { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } std::unique_ptr ctx = @@ -776,21 +793,21 @@ RestStatus RestAdminClusterHandler::handleMoveShard() { if (!canAccess) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "insufficient permissions on database to move shard"); - return RestStatus::DONE; + co_return; } ctx->fromServer = resolveServerNameID(ctx->fromServer); ctx->toServer = resolveServerNameID(ctx->toServer); - return handlePostMoveShard(std::move(ctx)); + co_return co_await handlePostMoveShard(std::move(ctx)); } generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object with keys `database`, `collection`, `shard`, " "`fromServer` and `toServer` (all strings) expected"); - return RestStatus::DONE; + co_return; } -RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( +async RestAdminClusterHandler::createMoveShard( std::unique_ptr&& ctx, VPackSlice plan) { auto planPath = arangodb::cluster::paths::root()->arango()->plan(); @@ -801,7 +818,7 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( if (!serversFound) { generateError(ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "one or both dbservers not found"); - return futures::makeFuture(); + co_return; } VPackSlice collection = plan.get(planPath->collections() @@ -811,14 +828,14 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( if (!collection.isObject()) { generateError(ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "database/collection not found"); - return futures::makeFuture(); + co_return; } if (collection.hasKey(StaticStrings::DistributeShardsLike)) { generateError(ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "MoveShard only allowed for collections which have " "distributeShardsLike unset."); - return futures::makeFuture(); + co_return; } VPackSlice shard = @@ -826,7 +843,7 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( if (!shard.isArray()) { generateError(ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "shard not found"); - return futures::makeFuture(); + co_return; } bool fromFound = false; @@ -841,7 +858,7 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( if (!fromFound) { generateError(ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "shard is not located on the server"); - return futures::makeFuture(); + co_return; } std::string jobId = std::to_string( @@ -878,26 +895,23 @@ RestAdminClusterHandler::FutureVoid RestAdminClusterHandler::createMoveShard( .done(); } - return AsyncAgencyComm() - .sendWriteTransaction(20s, std::move(trx)) - .thenValue([this, ctx = std::move(ctx), - jobId = std::move(jobId)](AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - VPackBuilder builder; - ; - { - VPackObjectBuilder ob(&builder); - builder.add("id", VPackValue(jobId)); - } + auto result = + co_await AsyncAgencyComm().sendWriteTransaction(20s, std::move(trx)); - generateOk(rest::ResponseCode::ACCEPTED, builder); - } else { - generateError(result.asResult()); - } - }); + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + builder.add("id", VPackValue(jobId)); + } + + generateOk(rest::ResponseCode::ACCEPTED, builder); + } else { + generateError(result.asResult()); + } } -RestStatus RestAdminClusterHandler::handlePostMoveShard( +async RestAdminClusterHandler::handlePostMoveShard( std::unique_ptr&& ctx) { std::shared_ptr collection = server().getFeature().clusterInfo().getCollectionNT( @@ -906,7 +920,7 @@ RestStatus RestAdminClusterHandler::handlePostMoveShard( if (collection == nullptr) { generateError(ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, "database/collection not found"); - return RestStatus::DONE; + co_return; } // base64-encode collection id with RevisionId @@ -928,59 +942,55 @@ RestStatus RestAdminClusterHandler::handlePostMoveShard( } // gather information about that shard - return waitForFuture( - AsyncAgencyComm() - .sendReadTransaction(20s, std::move(trx)) - .thenValue([this, ctx = std::move(ctx)]( - AsyncAgencyCommResult&& result) mutable { - if (result.ok()) { - switch (result.statusCode()) { - case fuerte::StatusOK: - return createMoveShard(std::move(ctx), result.slice().at(0)); - case fuerte::StatusNotFound: - generateError(rest::ResponseCode::NOT_FOUND, - TRI_ERROR_HTTP_NOT_FOUND, "unknown collection"); - return futures::makeFuture(); - default: - break; - } - } + try { + auto result = + co_await AsyncAgencyComm().sendReadTransaction(20s, std::move(trx)); + if (result.ok()) { + switch (result.statusCode()) { + case fuerte::StatusOK: + co_return co_await createMoveShard(std::move(ctx), + result.slice().at(0)); + case fuerte::StatusNotFound: + generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, + "unknown collection"); + co_return; + default: + break; + } + } - generateError(result.asResult()); - return futures::makeFuture(); - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + generateError(result.asResult()); + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleQueryJobStatus() { +async RestAdminClusterHandler::handleQueryJobStatus() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } std::string jobId = request()->value("id"); if (jobId.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "missing id parameter"); - return RestStatus::DONE; + co_return; } auto targetPath = arangodb::cluster::paths::root()->arango()->target(); @@ -991,78 +1001,73 @@ RestStatus RestAdminClusterHandler::handleQueryJobStatus() { targetPath->toDo()->job(jobId)->str(), }; - return waitForFuture( - AsyncAgencyComm() - .sendTransaction(20s, AgencyReadTransaction{std::move(paths)}) - .thenValue([this, targetPath, jobId = std::move(jobId)]( - AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - auto paths = std::vector{ - targetPath->pending()->job(jobId)->vec(), - targetPath->failed()->job(jobId)->vec(), - targetPath->finished()->job(jobId)->vec(), - targetPath->toDo()->job(jobId)->vec(), - }; - - for (auto const& path : paths) { - VPackSlice job = result.slice().at(0).get(path); - - if (job.isObject()) { - VPackBuffer payload; - { - VPackBuilder builder(payload); - VPackObjectBuilder ob(&builder); - - // append all the job keys - builder.add(VPackObjectIterator(job)); - builder.add("error", VPackValue(false)); - builder.add("job", VPackValue(jobId)); - builder.add("status", VPackValue(path[2])); - } + try { + auto result = co_await AsyncAgencyComm().sendTransaction( + 20s, AgencyReadTransaction{std::move(paths)}); + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + auto paths = std::vector{ + targetPath->pending()->job(jobId)->vec(), + targetPath->failed()->job(jobId)->vec(), + targetPath->finished()->job(jobId)->vec(), + targetPath->toDo()->job(jobId)->vec(), + }; + + for (auto const& path : paths) { + VPackSlice job = result.slice().at(0).get(path); + + if (job.isObject()) { + VPackBuffer payload; + { + VPackBuilder builder(payload); + VPackObjectBuilder ob(&builder); - resetResponse(rest::ResponseCode::OK); - response()->setPayload(std::move(payload)); - return; - } - } + // append all the job keys + builder.add(VPackObjectIterator(job)); + builder.add("error", VPackValue(false)); + builder.add("job", VPackValue(jobId)); + builder.add("status", VPackValue(path[2])); + } - generateError(rest::ResponseCode::NOT_FOUND, - TRI_ERROR_HTTP_NOT_FOUND); - } else { - generateError(result.asResult()); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + resetResponse(rest::ResponseCode::OK); + response()->setPayload(std::move(payload)); + co_return; + } + } + + generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleCancelJob() { +async RestAdminClusterHandler::handleCancelJob() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } std::string jobId; @@ -1071,7 +1076,7 @@ RestStatus RestAdminClusterHandler::handleCancelJob() { } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object with key `id`"); - return RestStatus::DONE; + co_return; } auto targetPath = arangodb::cluster::paths::root()->arango()->target(); @@ -1106,11 +1111,11 @@ RestStatus RestAdminClusterHandler::handleCancelJob() { generateError( Result{TRI_ERROR_BAD_PARAMETER, "Only MoveShard and CleanOutServer jobs can be aborted"}); - return RestStatus::DONE; + co_return; } else if (path[2] != "Pending" && path[2] != "ToDo") { generateError(Result{TRI_ERROR_BAD_PARAMETER, path[2] + " jobs can no longer be cancelled."}); - return RestStatus::DONE; + co_return; } // This tranaction aims at killing a job that is todo or pending. @@ -1159,44 +1164,42 @@ RestStatus RestAdminClusterHandler::handleCancelJob() { std::move(trxBody)); }; - return waitForFuture( - sendTransaction() - .thenValue([=, this](AsyncAgencyCommResult&& wr) { - if (!wr.ok()) { - // Only if no longer pending or todo. - if (wr.statusCode() == 412) { - auto results = wr.slice().get("results"); - if (results[0].getNumber() == 0 && - results[1].getNumber() == 0) { - generateError( - Result{TRI_ERROR_HTTP_PRECONDITION_FAILED, - "Job is no longer pending or to do"}); - return; - } - } else { - generateError(wr.asResult()); - return; - } - } + try { + auto wr = co_await sendTransaction(); + + if (!wr.ok()) { + // Only if no longer pending or todo. + if (wr.statusCode() == 412) { + auto results = wr.slice().get("results"); + if (results[0].getNumber() == 0 && + results[1].getNumber() == 0) { + generateError(Result{TRI_ERROR_HTTP_PRECONDITION_FAILED, + "Job is no longer pending or to do"}); + co_return; + } + } else { + generateError(wr.asResult()); + co_return; + } + } - VPackBuffer payload; - { - VPackBuilder builder(payload); - VPackObjectBuilder ob(&builder); - builder.add("job", VPackValue(jobId)); - builder.add("status", VPackValue("aborted")); - builder.add("error", VPackValue(false)); - } - resetResponse(rest::ResponseCode::OK); - response()->setPayload(std::move(payload)); - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + VPackBuffer payload; + { + VPackBuilder builder(payload); + VPackObjectBuilder ob(&builder); + builder.add("job", VPackValue(jobId)); + builder.add("status", VPackValue("aborted")); + builder.add("error", VPackValue(false)); + } + resetResponse(rest::ResponseCode::OK); + response()->setPayload(std::move(payload)); + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR, e.what()); + } + co_return; } } @@ -1209,48 +1212,48 @@ RestStatus RestAdminClusterHandler::handleCancelJob() { e.what()); } - return RestStatus::DONE; + co_return; } -RestStatus RestAdminClusterHandler::handleSingleServerJob( +async RestAdminClusterHandler::handleSingleServerJob( std::string const& job) { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } if (body.isObject()) { VPackSlice server = body.get("server"); if (server.isString()) { std::string serverId = resolveServerNameID(server.copyString()); - return handleCreateSingleServerJob(job, serverId, body); + co_return co_await handleCreateSingleServerJob(job, serverId, body); } } generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object with key `server`"); - return RestStatus::DONE; + co_return; } -RestStatus RestAdminClusterHandler::handleCreateSingleServerJob( +async RestAdminClusterHandler::handleCreateSingleServerJob( std::string const& job, std::string const& serverId, VPackSlice body) { std::string jobId = std::to_string( server().getFeature().clusterInfo().uniqid()); @@ -1274,44 +1277,41 @@ RestStatus RestAdminClusterHandler::handleCreateSingleServerJob( VPackValue(timepointToString(std::chrono::system_clock::now()))); } - return waitForFuture( - AsyncAgencyComm() - .setValue(20s, jobToDoPath, builder.slice()) - .thenValue( - [this, jobId = std::move(jobId)](AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == 200) { - VPackBuilder builder; - { - VPackObjectBuilder ob(&builder); - builder.add("id", VPackValue(jobId)); - } + try { + auto result = + co_await AsyncAgencyComm().setValue(20s, jobToDoPath, builder.slice()); - generateOk(arangodb::rest::ResponseCode::ACCEPTED, builder); - } else { - generateError(result.asResult()); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + if (result.ok() && result.statusCode() == 200) { + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + builder.add("id", VPackValue(jobId)); + } + + generateOk(arangodb::rest::ResponseCode::ACCEPTED, builder); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleProxyGetRequest( +async RestAdminClusterHandler::handleProxyGetRequest( std::string const& url, std::string const& serverFromParameter) { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } std::string const& serverId = request()->value(serverFromParameter); @@ -1319,7 +1319,7 @@ RestStatus RestAdminClusterHandler::handleProxyGetRequest( generateError( rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, std::string("missing parameter `") + serverFromParameter + "`"); - return RestStatus::DONE; + co_return; } auto* pool = server().getFeature().pool(); @@ -1329,75 +1329,72 @@ RestStatus RestAdminClusterHandler::handleProxyGetRequest( auto frequest = network::sendRequestRetry(pool, "server:" + serverId, fuerte::RestVerb::Get, url, VPackBuffer(), opt); - - return waitForFuture( - std::move(frequest) - .thenValue([this](network::Response&& result) { - if (result.ok()) { - resetResponse(ResponseCode( - result.statusCode())); // I quit if the values are not the - // HTTP Status Codes - auto payload = result.response().stealPayload(); - response()->setPayload(std::move(*payload)); - } else { - switch (result.error) { - case fuerte::Error::ConnectionCanceled: - generateError(rest::ResponseCode::BAD, - TRI_ERROR_HTTP_BAD_PARAMETER, "unknown server"); - break; - case fuerte::Error::CouldNotConnect: - case fuerte::Error::RequestTimeout: - generateError(rest::ResponseCode::REQUEST_TIMEOUT, - TRI_ERROR_HTTP_GATEWAY_TIMEOUT, - "server did not answer"); - break; - default: - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR); - } - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = co_await std::move(frequest); + + if (result.ok()) { + resetResponse(ResponseCode( + result.statusCode())); // I quit if the values are not the + // HTTP Status Codes + auto payload = result.response().stealPayload(); + response()->setPayload(std::move(*payload)); + } else { + switch (result.error) { + case fuerte::Error::ConnectionCanceled: + generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, + "unknown server"); + break; + case fuerte::Error::CouldNotConnect: + case fuerte::Error::RequestTimeout: + generateError(rest::ResponseCode::REQUEST_TIMEOUT, + TRI_ERROR_HTTP_GATEWAY_TIMEOUT, + "server did not answer"); + break; + default: + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR); + } + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleNodeVersion() { - return handleProxyGetRequest("/_api/version", "ServerID"); +async RestAdminClusterHandler::handleNodeVersion() { + co_return co_await handleProxyGetRequest("/_api/version", "ServerID"); } -RestStatus RestAdminClusterHandler::handleNodeStatistics() { - return handleProxyGetRequest("/_admin/statistics", "ServerID"); +async RestAdminClusterHandler::handleNodeStatistics() { + co_return co_await handleProxyGetRequest("/_admin/statistics", "ServerID"); } -RestStatus RestAdminClusterHandler::handleNodeEngine() { - return handleProxyGetRequest("/_api/engine", "ServerID"); +async RestAdminClusterHandler::handleNodeEngine() { + co_return co_await handleProxyGetRequest("/_api/engine", "ServerID"); } -RestStatus RestAdminClusterHandler::handleStatistics() { - return handleProxyGetRequest("/_admin/statistics", "DBserver"); +async RestAdminClusterHandler::handleStatistics() { + co_return co_await handleProxyGetRequest("/_admin/statistics", "DBserver"); } -RestStatus RestAdminClusterHandler::handleShardDistribution() { +void RestAdminClusterHandler::handleShardDistribution() { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + return; } if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + return; } if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + return; } auto reporter = cluster::ShardDistributionReporter::instance(server()); @@ -1409,15 +1406,14 @@ RestStatus RestAdminClusterHandler::handleShardDistribution() { reporter->getDistributionForDatabase(_vocbase.name(), builder); } generateOk(rest::ResponseCode::OK, builder); - return RestStatus::DONE; } -RestStatus RestAdminClusterHandler::handleGetCollectionShardDistribution( +void RestAdminClusterHandler::handleGetCollectionShardDistribution( std::string const& collection) { if (collection.empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "expected nonempty `collection` parameter"); - return RestStatus::DONE; + return; } auto reporter = cluster::ShardDistributionReporter::instance(server()); @@ -1430,19 +1426,18 @@ RestStatus RestAdminClusterHandler::handleGetCollectionShardDistribution( builder); } generateOk(rest::ResponseCode::OK, builder); - return RestStatus::DONE; } -RestStatus RestAdminClusterHandler::handleCollectionShardDistribution() { +void RestAdminClusterHandler::handleCollectionShardDistribution() { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + return; } if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + return; } switch (request()->requestType()) { @@ -1454,13 +1449,13 @@ RestStatus RestAdminClusterHandler::handleCollectionShardDistribution() { default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + return; } if (body.isObject()) { @@ -1472,14 +1467,13 @@ RestStatus RestAdminClusterHandler::handleCollectionShardDistribution() { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object with key `collection`"); - return RestStatus::DONE; } -RestStatus RestAdminClusterHandler::handleGetMaintenance() { +async RestAdminClusterHandler::handleGetMaintenance() { if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } auto maintenancePath = arangodb::cluster::paths::root() @@ -1487,32 +1481,27 @@ RestStatus RestAdminClusterHandler::handleGetMaintenance() { ->supervision() ->state() ->mode(); - - return waitForFuture( - AsyncAgencyComm() - .getValues(maintenancePath) - .thenValue([this](AgencyReadResult&& result) { - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - generateOk(rest::ResponseCode::OK, result.value()); - } else { - generateError(result.asResult()); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = co_await AsyncAgencyComm().getValues(maintenancePath); + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + generateOk(rest::ResponseCode::OK, result.value()); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handleGetDBServerMaintenance( +async RestAdminClusterHandler::handleGetDBServerMaintenance( std::string const& serverId) { if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } auto maintenancePath = arangodb::cluster::paths::root() @@ -1521,27 +1510,22 @@ RestStatus RestAdminClusterHandler::handleGetDBServerMaintenance( ->maintenanceDBServers() ->dbserver(serverId); - return waitForFuture( - AsyncAgencyComm() - .getValues(maintenancePath) - .thenValue([this](AgencyReadResult&& result) { - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - generateOk(rest::ResponseCode::OK, result.value()); - } else { - generateError(result.asResult()); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = co_await AsyncAgencyComm().getValues(maintenancePath); + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + generateOk(rest::ResponseCode::OK, result.value()); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestAdminClusterHandler::FutureVoid -RestAdminClusterHandler::waitForSupervisionState( +futures::Future RestAdminClusterHandler::waitForSupervisionState( bool state, std::string const& reactivationTime, clock::time_point startTime) { return SchedulerFeature::SCHEDULER->delay("wait-for-supervision", 1s) @@ -1592,8 +1576,8 @@ RestAdminClusterHandler::waitForSupervisionState( }); } -RestStatus RestAdminClusterHandler::setMaintenance(bool wantToActivate, - uint64_t timeout) { +async RestAdminClusterHandler::setMaintenance(bool wantToActivate, + uint64_t timeout) { // we don't need a timeout when disabling the maintenance TRI_ASSERT(wantToActivate || timeout == 0); @@ -1618,28 +1602,25 @@ RestStatus RestAdminClusterHandler::setMaintenance(bool wantToActivate, auto self(shared_from_this()); - return waitForFuture( - sendTransaction() - .thenValue([this, wantToActivate, - reactivationTime](AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == 200) { - return waitForSupervisionState(wantToActivate, reactivationTime, - std::chrono::steady_clock::now()); - } else { - generateError(result.asResult()); - } - return futures::makeFuture(); - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = co_await sendTransaction(); + if (result.ok() && result.statusCode() == 200) { + co_await waitForSupervisionState(wantToActivate, reactivationTime, + std::chrono::steady_clock::now()); + co_return; + } else { + generateError(result.asResult()); + } + co_return; + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestAdminClusterHandler::FutureVoid +futures::Future RestAdminClusterHandler::waitForDBServerMaintenance(std::string const& serverId, bool waitForMaintenance) { struct Context { @@ -1689,7 +1670,7 @@ RestAdminClusterHandler::waitForDBServerMaintenance(std::string const& serverId, }); } -RestStatus RestAdminClusterHandler::setDBServerMaintenance( +async RestAdminClusterHandler::setDBServerMaintenance( std::string const& serverId, std::string const& mode, uint64_t timeout) { auto maintenancePath = arangodb::cluster::paths::root() ->arango() @@ -1741,41 +1722,37 @@ RestStatus RestAdminClusterHandler::setDBServerMaintenance( auto self(shared_from_this()); bool const isMaintenanceMode = mode == "maintenance"; - - return waitForFuture( - sendTransaction() - .thenValue([this, reactivationTime, isMaintenanceMode, - serverId](AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == 200) { - VPackBuilder builder; - { VPackObjectBuilder obj(&builder); } - generateOk(rest::ResponseCode::OK, builder); - return waitForDBServerMaintenance(serverId, isMaintenanceMode); - } else { - generateError(result.asResult()); - } - return futures::makeFuture(); - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = co_await sendTransaction(); + if (result.ok() && result.statusCode() == 200) { + VPackBuilder builder; + { + VPackObjectBuilder obj(&builder); + } + generateOk(rest::ResponseCode::OK, builder); + co_await waitForDBServerMaintenance(serverId, isMaintenanceMode); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handlePutMaintenance() { +async RestAdminClusterHandler::handlePutMaintenance() { if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } if (body.isString()) { @@ -1783,10 +1760,10 @@ RestStatus RestAdminClusterHandler::handlePutMaintenance() { // supervision maintenance will turn itself off automatically // after one hour by default constexpr uint64_t timeout = 3600; // 1 hour - return setMaintenance(true, timeout); + co_return co_await setMaintenance(true, timeout); } else if (body.isEqualString("off")) { // timeout doesn't matter for turning off the supervision - return setMaintenance(false, /*timeout*/ 0); + co_return co_await setMaintenance(false, /*timeout*/ 0); } else { // user sent a stringified timeout value, so lets honor this VPackValueLength l; @@ -1794,29 +1771,29 @@ RestStatus RestAdminClusterHandler::handlePutMaintenance() { bool valid = false; uint64_t timeout = NumberUtils::atoi_positive(p, p + l, valid); if (valid) { - return setMaintenance(true, timeout); + co_return co_await setMaintenance(true, timeout); } // otherwise fall-through to error } } else if (body.isNumber()) { // user sent a numeric timeout value, so lets honor this uint64_t timeout = body.getNumber(); - return setMaintenance(true, timeout); + co_return co_await setMaintenance(true, timeout); } generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "string expected with value `on` or `off`"); - return RestStatus::DONE; + co_return; } -RestStatus RestAdminClusterHandler::handlePutDBServerMaintenance( +async RestAdminClusterHandler::handlePutDBServerMaintenance( std::string const& serverId) { TRI_ASSERT(AsyncAgencyCommManager::INSTANCE != nullptr); bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } if (body.isObject() && body.hasKey("mode")) { @@ -1833,7 +1810,7 @@ RestStatus RestAdminClusterHandler::handlePutDBServerMaintenance( } } } - return setDBServerMaintenance(serverId, mode, timeout); + co_return co_await setDBServerMaintenance(serverId, mode, timeout); } // otherwise fall-through to error } @@ -1841,72 +1818,72 @@ RestStatus RestAdminClusterHandler::handlePutDBServerMaintenance( generateError( rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object expected with attribute 'mode' and optional attribute 'timeout'"); - return RestStatus::DONE; + co_return; } -RestStatus RestAdminClusterHandler::handleMaintenance() { +async RestAdminClusterHandler::handleMaintenance() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator() && !ServerState::instance()->isSingleServer()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on single server and coordinators"); - return RestStatus::DONE; + co_return; } if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } switch (request()->requestType()) { case rest::RequestType::GET: - return handleGetMaintenance(); + co_return co_await handleGetMaintenance(); case rest::RequestType::PUT: - return handlePutMaintenance(); + co_return co_await handlePutMaintenance(); default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } } -RestStatus RestAdminClusterHandler::handleDBServerMaintenance( +async RestAdminClusterHandler::handleDBServerMaintenance( std::string const& serverId) { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } TRI_ASSERT(AsyncAgencyCommManager::INSTANCE != nullptr); switch (request()->requestType()) { case rest::RequestType::GET: - return handleGetDBServerMaintenance(serverId); + co_return co_await handleGetDBServerMaintenance(serverId); case rest::RequestType::PUT: - return handlePutDBServerMaintenance(serverId); + co_return co_await handlePutDBServerMaintenance(serverId); default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } } -RestStatus RestAdminClusterHandler::handleGetNumberOfServers() { +async RestAdminClusterHandler::handleGetNumberOfServers() { if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } auto targetPath = arangodb::cluster::paths::root()->arango()->target(); @@ -1923,67 +1900,59 @@ RestStatus RestAdminClusterHandler::handleGetNumberOfServers() { .done(); } - auto self(shared_from_this()); - return waitForFuture( - AsyncAgencyComm() - .sendReadTransaction(10.0s, std::move(trx)) - .thenValue([this](AsyncAgencyCommResult&& result) { - auto targetPath = - arangodb::cluster::paths::root()->arango()->target(); + try { + auto result = + co_await AsyncAgencyComm().sendReadTransaction(10.0s, std::move(trx)); - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - VPackBuilder builder; - { - VPackObjectBuilder ob(&builder); - builder.add("numberOfDBServers", - result.slice().at(0).get( - targetPath->numberOfDBServers()->vec())); - builder.add("numberOfCoordinators", - result.slice().at(0).get( - targetPath->numberOfCoordinators()->vec())); - builder.add("cleanedServers", - result.slice().at(0).get( - targetPath->cleanedServers()->vec())); - } + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + builder.add( + "numberOfDBServers", + result.slice().at(0).get(targetPath->numberOfDBServers()->vec())); + builder.add("numberOfCoordinators", + result.slice().at(0).get( + targetPath->numberOfCoordinators()->vec())); + builder.add("cleanedServers", result.slice().at(0).get( + targetPath->cleanedServers()->vec())); + } - generateOk(rest::ResponseCode::OK, builder); - } else { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, - "agency communication failed"); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + generateOk(rest::ResponseCode::OK, builder); + } else { + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR, "agency communication failed"); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } -RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { +async RestAdminClusterHandler::handlePutNumberOfServers() { if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } bool parseSuccess; VPackSlice body = parseVPackBody(parseSuccess); if (!parseSuccess) { - return RestStatus::DONE; + co_return; } if (!body.isObject()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "object expected"); - return RestStatus::DONE; + co_return; } std::vector ops; @@ -2003,7 +1972,7 @@ RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { } else if (!numberOfCoordinators.isNone()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "numberOfCoordinators: number expected"); - return RestStatus::DONE; + co_return; } VPackSlice numberOfDBServers = body.get("numberOfDBServers"); @@ -2014,7 +1983,7 @@ RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { } else if (!numberOfDBServers.isNone()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "numberOfDBServers: number expected"); - return RestStatus::DONE; + co_return; } VPackSlice cleanedServers = body.get("cleanedServers"); @@ -2034,12 +2003,12 @@ RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "cleanedServers: array of strings expected"); - return RestStatus::DONE; + co_return; } } else if (!cleanedServers.isNone()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "cleanedServers: array expected"); - return RestStatus::DONE; + co_return; } std::move(write).end().done(); @@ -2052,34 +2021,29 @@ RestStatus RestAdminClusterHandler::handlePutNumberOfServers() { // "missing fields"); // but that would break API compatibility. Introduce this behavior // in 4.0!! - return RestStatus::DONE; + co_return; + } + try { + auto result = + co_await AsyncAgencyComm().sendWriteTransaction(20s, std::move(trx)); + if (result.ok() && result.statusCode() == fuerte::StatusOK) { + generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); + } else { + generateError(result.asResult()); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); } - - auto self(shared_from_this()); - return waitForFuture( - AsyncAgencyComm() - .sendWriteTransaction(20s, std::move(trx)) - .thenValue([this](AsyncAgencyCommResult&& result) { - if (result.ok() && result.statusCode() == fuerte::StatusOK) { - generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); - } else { - generateError(result.asResult()); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); } -RestStatus RestAdminClusterHandler::handleNumberOfServers() { +async RestAdminClusterHandler::handleNumberOfServers() { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } // GET requests are allowed for everyone, unless --server.harden is used. @@ -2093,55 +2057,53 @@ RestStatus RestAdminClusterHandler::handleNumberOfServers() { if (needsAdminPrivileges && !ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } switch (request()->requestType()) { case rest::RequestType::GET: - return handleGetNumberOfServers(); + co_return co_await handleGetNumberOfServers(); case rest::RequestType::PUT: - return handlePutNumberOfServers(); + co_return co_await handlePutNumberOfServers(); default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } } -RestStatus RestAdminClusterHandler::handleHealth() { +async RestAdminClusterHandler::handleHealth() { // We allow this API whenever one is authenticated in some way. There used // to be a check for isAdminUser here. However, we want that the UI with // the cluster health dashboard works for every authenticated user. if (request()->requestType() != rest::RequestType::GET) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator() && !ServerState::instance()->isSingleServer()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on single server and coordinators"); - return RestStatus::DONE; + co_return; } if (AsyncAgencyCommManager::INSTANCE == nullptr) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "not allowed on single servers"); - return RestStatus::DONE; + co_return; } // TODO handle timeout parameter - auto self(shared_from_this()); - // query the agency config auto fConfig = AsyncAgencyComm() .sendWithFailover(fuerte::RestVerb::Get, "/_api/agency/config", 60.0s, AsyncAgencyComm::RequestType::READ, VPackBuffer()) - .thenValue([self](AsyncAgencyCommResult&& result) { + .thenValue([this](AsyncAgencyCommResult&& result) { // this lambda has to capture self since collect returns early on // an exception and the RestHandler might be freed too early // otherwise @@ -2154,7 +2116,7 @@ RestStatus RestAdminClusterHandler::handleHealth() { // version std::vector> fs; - auto* pool = self->server().getFeature().pool(); + auto* pool = server().getFeature().pool(); for (auto member : VPackObjectIterator(result.slice().get( std::vector{"configuration", "pool"}))) { std::string endpoint = member.value.copyString(); @@ -2196,33 +2158,27 @@ RestStatus RestAdminClusterHandler::handleHealth() { } auto fStore = AsyncAgencyComm().sendReadTransaction(60.0s, std::move(trx)); - return waitForFuture( - futures::collect(std::move(fConfig), std::move(fStore)) - .thenValue([this](auto&& result) { - auto rootPath = arangodb::cluster::paths::root()->arango(); - auto& [configResult, storeResult] = result; - if (storeResult.ok() && - storeResult.statusCode() == fuerte::StatusOK) { - VPackBuilder builder; - { - VPackObjectBuilder ob(&builder); - ::buildHealthResult(builder, configResult, - storeResult.slice().at(0)); - } - generateOk(rest::ResponseCode::OK, builder); - } else { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, - "agency communication failed"); - } - }) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + try { + auto result = + co_await futures::collect(std::move(fConfig), std::move(fStore)); + auto& [configResult, storeResult] = result; + if (storeResult.ok() && storeResult.statusCode() == fuerte::StatusOK) { + VPackBuilder builder; + { + VPackObjectBuilder ob(&builder); + ::buildHealthResult(builder, configResult, storeResult.slice().at(0)); + } + generateOk(rest::ResponseCode::OK, builder); + } else { + generateError(rest::ResponseCode::SERVER_ERROR, + TRI_ERROR_HTTP_SERVER_ERROR, "agency communication failed"); + } + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } std::string RestAdminClusterHandler::resolveServerNameID( @@ -2332,9 +2288,9 @@ void theSimpleStupidOne( } } // namespace -RestAdminClusterHandler::FutureVoid +futures::Future RestAdminClusterHandler::handlePostRebalanceShards( - const ReshardAlgorithm& algorithm) { + ReshardAlgorithm const& algorithm) { // dbserver -> shards std::vector moves; std::map> shardMap; @@ -2397,24 +2353,24 @@ RestAdminClusterHandler::handlePostRebalanceShards( }); } -RestStatus RestAdminClusterHandler::handleRebalanceShards() { +async RestAdminClusterHandler::handleRebalanceShards() { if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } ExecContext const& exec = ExecContext::current(); if (!exec.canUseDatabase(auth::Level::RW)) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "insufficient permissions"); - return RestStatus::DONE; + co_return; } // ADD YOUR ALGORITHM HERE!!! @@ -2426,18 +2382,17 @@ RestStatus RestAdminClusterHandler::handleRebalanceShards() { } else { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "unknown algorithm"); - return RestStatus::DONE; - } - - return waitForFuture( - handlePostRebalanceShards(algorithm) - .thenError([this](VPackException const& e) { - generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); - }) - .thenError([this](std::exception const& e) { - generateError(rest::ResponseCode::SERVER_ERROR, - TRI_ERROR_HTTP_SERVER_ERROR, e.what()); - })); + co_return; + } + + try { + co_await handlePostRebalanceShards(algorithm); + } catch (VPackException const& e) { + generateError(Result{TRI_ERROR_HTTP_SERVER_ERROR, e.what()}); + } catch (std::exception const& e) { + generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + e.what()); + } } namespace { @@ -2495,11 +2450,11 @@ RestAdminClusterHandler::countAllMoveShardJobs() { }; } -RestStatus RestAdminClusterHandler::handleRebalanceGet() { +void RestAdminClusterHandler::handleRebalanceGet() { if (request()->suffixes().size() != 1) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect GET /_admin/cluster/rebalance"); - return RestStatus::DONE; + return; } auto [todo, pending] = countAllMoveShardJobs(); @@ -2519,7 +2474,7 @@ RestStatus RestAdminClusterHandler::handleRebalanceGet() { } generateOk(rest::ResponseCode::OK, builder.slice()); - return RestStatus::DONE; + return; } namespace { @@ -2584,11 +2539,11 @@ futures::Future executeMoveShardOperations(std::vector const& batch, } } // namespace -RestStatus RestAdminClusterHandler::handleRebalanceExecute() { +async RestAdminClusterHandler::handleRebalanceExecute() { if (request()->requestType() != rest::RequestType::POST) { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } auto opts = velocypack::deserialize(_request->payload()); @@ -2596,7 +2551,7 @@ RestStatus RestAdminClusterHandler::handleRebalanceExecute() { if (opts.version != 1) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "unknown version provided"); - return RestStatus::DONE; + co_return; } auto& ci = server().getFeature().clusterInfo(); @@ -2607,21 +2562,18 @@ RestStatus RestAdminClusterHandler::handleRebalanceExecute() { if (opts.moves.empty()) { generateOk(rest::ResponseCode::OK, VPackSlice::noneSlice()); - return RestStatus::DONE; - } - - return waitForFuture(executeMoveShardOperations(opts.moves, ci, idMapper) - .thenValue([this](auto&& result) { - if (result.ok()) { - generateOk(rest::ResponseCode::ACCEPTED, - VPackSlice::noneSlice()); - } else { - generateError(result); - } - })); + co_return; + } + + auto result = co_await executeMoveShardOperations(opts.moves, ci, idMapper); + if (result.ok()) { + generateOk(rest::ResponseCode::ACCEPTED, VPackSlice::noneSlice()); + } else { + generateError(result); + } } -RestStatus RestAdminClusterHandler::handleRebalancePlan() { +async RestAdminClusterHandler::handleRebalancePlan() { using namespace cluster::rebalance; auto const readRebalanceOptions = [&]() -> std::optional { @@ -2645,7 +2597,7 @@ RestStatus RestAdminClusterHandler::handleRebalancePlan() { auto options = readRebalanceOptions(); if (!options) { - return RestStatus::DONE; + co_return; } auto p = collectRebalanceInformation(options->databasesExcluded, @@ -2720,57 +2672,56 @@ RestStatus RestAdminClusterHandler::handleRebalancePlan() { switch (request()->requestType()) { case rest::RequestType::POST: { buildResponse(rest::ResponseCode::OK); - return RestStatus::DONE; + co_return; } case rest::RequestType::PUT: { buildResponse(rest::ResponseCode::ACCEPTED); - return waitForFuture( - executeMoveShardOperations(moves, ci, moveShardConverter) - .thenValue([&](auto&& result) { - if (!result.ok()) { - generateError(result); - } - })); + auto result = + co_await executeMoveShardOperations(moves, ci, moveShardConverter); + if (!result.ok()) { + generateError(result); + } + co_return; } default: generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); - return RestStatus::DONE; + co_return; } } -RestStatus RestAdminClusterHandler::handleRebalance() { +async RestAdminClusterHandler::handleRebalance() { if (!ServerState::instance()->isCoordinator()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN, "only allowed on coordinators"); - return RestStatus::DONE; + co_return; } if (!ExecContext::current().isAdminUser()) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_HTTP_FORBIDDEN); - return RestStatus::DONE; + co_return; } if (request()->requestType() == rest::RequestType::GET) { - return handleRebalanceGet(); + co_return handleRebalanceGet(); } bool parseSuccess = false; std::ignore = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody - return RestStatus::DONE; + co_return; } if (auto const& suff = request()->suffixes(); suff.size() == 2) { if (suff[1] != "execute") { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "expect /_admin/cluster/rebalance[/execute]"); - return RestStatus::DONE; + co_return; } - return handleRebalanceExecute(); + co_return co_await handleRebalanceExecute(); } - return handleRebalancePlan(); + co_return co_await handleRebalancePlan(); } cluster::rebalance::AutoRebalanceProblem @@ -2912,8 +2863,7 @@ RestAdminClusterHandler::collectRebalanceInformation( return p; } -RestAdminClusterHandler::FutureVoid -RestAdminClusterHandler::handleVPackSortMigration( +async RestAdminClusterHandler::handleVPackSortMigration( std::string const& subCommand) { // First we do the authentication: We only allow superuser access, since // this is a critical migration operation: diff --git a/arangod/RestHandler/RestAdminClusterHandler.h b/arangod/RestHandler/RestAdminClusterHandler.h index fee0d30c7bfa..ea69a778f36f 100644 --- a/arangod/RestHandler/RestAdminClusterHandler.h +++ b/arangod/RestHandler/RestAdminClusterHandler.h @@ -43,7 +43,7 @@ class RestAdminClusterHandler : public RestVocbaseBaseHandler { ~RestAdminClusterHandler() override = default; public: - RestStatus execute() override; + auto executeAsync() -> futures::Future override; char const* name() const override final { return "RestAdminClusterHandler"; } RequestLane lane() const override final { return RequestLane::CLIENT_SLOW; } @@ -71,49 +71,47 @@ class RestAdminClusterHandler : public RestVocbaseBaseHandler { static std::string const VPackSortMigrationMigrate; static std::string const VPackSortMigrationStatus; - RestStatus handleHealth(); - RestStatus handleNumberOfServers(); - RestStatus handleMaintenance(); - RestStatus handleDBServerMaintenance(std::string const& serverId); + async handleHealth(); + async handleNumberOfServers(); + async handleMaintenance(); + async handleDBServerMaintenance(std::string const& serverId); // timeout can be used to set an arbitrary timeout for the maintenance // duration. it will be ignored if "state" is not true. - RestStatus setMaintenance(bool state, uint64_t timeout); - RestStatus setDBServerMaintenance(std::string const& serverId, - std::string const& mode, uint64_t timeout); - RestStatus handlePutMaintenance(); - RestStatus handleGetMaintenance(); - RestStatus handlePutDBServerMaintenance(std::string const& serverId); - RestStatus handleGetDBServerMaintenance(std::string const& serverId); - - RestStatus handleGetNumberOfServers(); - RestStatus handlePutNumberOfServers(); - - RestStatus handleNodeVersion(); - RestStatus handleNodeStatistics(); - RestStatus handleNodeEngine(); - RestStatus handleStatistics(); - - RestStatus handleShardDistribution(); - RestStatus handleCollectionShardDistribution(); - RestStatus handleShardStatistics(); - - RestStatus handleCleanoutServer(); - RestStatus handleResignLeadership(); - RestStatus handleMoveShard(); - RestStatus handleCancelJob(); - RestStatus handleQueryJobStatus(); - - RestStatus handleRemoveServer(); - RestStatus handleRebalanceShards(); - RestStatus handleRebalance(); - RestStatus handleRebalanceGet(); - RestStatus handleRebalanceExecute(); - RestStatus handleRebalancePlan(); - - typedef futures::Future FutureVoid; - - FutureVoid handleVPackSortMigration(std::string const& subCommand); + async setMaintenance(bool state, uint64_t timeout); + async setDBServerMaintenance(std::string const& serverId, + std::string const& mode, uint64_t timeout); + async handlePutMaintenance(); + async handleGetMaintenance(); + async handlePutDBServerMaintenance(std::string const& serverId); + async handleGetDBServerMaintenance(std::string const& serverId); + + async handleGetNumberOfServers(); + async handlePutNumberOfServers(); + + async handleNodeVersion(); + async handleNodeStatistics(); + async handleNodeEngine(); + async handleStatistics(); + + void handleShardDistribution(); + void handleCollectionShardDistribution(); + void handleShardStatistics(); + + async handleCleanoutServer(); + async handleResignLeadership(); + async handleMoveShard(); + async handleCancelJob(); + async handleQueryJobStatus(); + + async handleRemoveServer(); + async handleRebalanceShards(); + async handleRebalance(); + void handleRebalanceGet(); + async handleRebalanceExecute(); + async handleRebalancePlan(); + + async handleVPackSortMigration(std::string const& subCommand); struct MoveShardContext { std::string database; @@ -142,21 +140,21 @@ class RestAdminClusterHandler : public RestVocbaseBaseHandler { arangodb::velocypack::Slice slice); }; - RestStatus handlePostMoveShard(std::unique_ptr&& ctx); + async handlePostMoveShard(std::unique_ptr&& ctx); - RestStatus handleSingleServerJob(std::string const& job); - RestStatus handleCreateSingleServerJob(std::string const& job, - std::string const& server, - VPackSlice body); + async handleSingleServerJob(std::string const& job); + async handleCreateSingleServerJob(std::string const& job, + std::string const& server, + VPackSlice body); typedef std::chrono::steady_clock clock; - FutureVoid waitForSupervisionState(bool state, - std::string const& reactivationTime, - clock::time_point startTime); + futures::Future waitForSupervisionState( + bool state, std::string const& reactivationTime, + clock::time_point startTime); - FutureVoid waitForDBServerMaintenance(std::string const& serverId, - bool waitForMaintenance); + futures::Future waitForDBServerMaintenance( + std::string const& serverId, bool waitForMaintenance); struct RemoveServerContext { size_t tries; @@ -166,17 +164,18 @@ class RestAdminClusterHandler : public RestVocbaseBaseHandler { : tries(0), server(std::move(s)) {} }; - FutureVoid tryDeleteServer(std::unique_ptr&& ctx); - FutureVoid retryTryDeleteServer(std::unique_ptr&& ctx); - FutureVoid createMoveShard(std::unique_ptr&& ctx, - velocypack::Slice plan); + futures::Future tryDeleteServer( + std::unique_ptr&& ctx); + futures::Future retryTryDeleteServer( + std::unique_ptr&& ctx); + async createMoveShard(std::unique_ptr&& ctx, + velocypack::Slice plan); - RestStatus handleProxyGetRequest(std::string const& url, - std::string const& serverFromParameter); - RestStatus handleGetCollectionShardDistribution( - std::string const& collection); + async handleProxyGetRequest(std::string const& url, + std::string const& serverFromParameter); + void handleGetCollectionShardDistribution(std::string const& collection); - RestStatus handlePostRemoveServer(std::string const& server); + async handlePostRemoveServer(std::string const& server); std::string resolveServerNameID(std::string const&); @@ -210,7 +209,8 @@ class RestAdminClusterHandler : public RestVocbaseBaseHandler { std::uint32_t, std::string)>; private: - FutureVoid handlePostRebalanceShards(const ReshardAlgorithm&); + futures::Future handlePostRebalanceShards( + ReshardAlgorithm const&); cluster::rebalance::AutoRebalanceProblem collectRebalanceInformation( std::vector const& excludedDatabases, From 73d0e3a9b4a7c50e10359d9c4b2ed006ed169b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 14:09:16 +0200 Subject: [PATCH 09/35] linter fix --- arangod/RestHandler/RestAdminClusterHandler.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arangod/RestHandler/RestAdminClusterHandler.cpp b/arangod/RestHandler/RestAdminClusterHandler.cpp index 237589daf6eb..cf0561d8c55e 100644 --- a/arangod/RestHandler/RestAdminClusterHandler.cpp +++ b/arangod/RestHandler/RestAdminClusterHandler.cpp @@ -1726,9 +1726,7 @@ async RestAdminClusterHandler::setDBServerMaintenance( auto result = co_await sendTransaction(); if (result.ok() && result.statusCode() == 200) { VPackBuilder builder; - { - VPackObjectBuilder obj(&builder); - } + { VPackObjectBuilder obj(&builder); } generateOk(rest::ResponseCode::OK, builder); co_await waitForDBServerMaintenance(serverId, isMaintenanceMode); } else { From 663b90368598a3d12da073cc8cd04844efe3ff48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 15:03:53 +0200 Subject: [PATCH 10/35] Removed empty lines --- arangod/RestHandler/RestDocumentHandler.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arangod/RestHandler/RestDocumentHandler.cpp b/arangod/RestHandler/RestDocumentHandler.cpp index e047992f5483..5c9c0c55fa2f 100644 --- a/arangod/RestHandler/RestDocumentHandler.cpp +++ b/arangod/RestHandler/RestDocumentHandler.cpp @@ -109,18 +109,14 @@ auto RestDocumentHandler::executeAsync() -> futures::Future { co_return co_await removeDocument(); case rest::RequestType::GET: co_return co_await readDocument(); - ; case rest::RequestType::HEAD: co_return co_await checkDocument(); - ; case rest::RequestType::POST: co_return co_await insertDocument(); case rest::RequestType::PUT: co_return co_await replaceDocument(); - ; case rest::RequestType::PATCH: co_return co_await updateDocument(); - ; default: { generateNotImplemented("ILLEGAL " + DOCUMENT_PATH); } From 4e128b2c58830e8fa227bb55184b79bfdc7ab44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 20:46:25 +0200 Subject: [PATCH 11/35] Make RestAqlHandler async, and finally delete waitForFuture --- arangod/Aql/QueryContext.h | 6 + arangod/Aql/RestAqlHandler.cpp | 182 +++++++++++--------------- arangod/Aql/RestAqlHandler.h | 11 +- arangod/GeneralServer/RestHandler.cpp | 48 +------ arangod/GeneralServer/RestHandler.h | 28 +++- 5 files changed, 112 insertions(+), 163 deletions(-) diff --git a/arangod/Aql/QueryContext.h b/arangod/Aql/QueryContext.h index d5abe8375e5d..a95e4cd53f93 100644 --- a/arangod/Aql/QueryContext.h +++ b/arangod/Aql/QueryContext.h @@ -124,6 +124,12 @@ class QueryContext { std::lock_guard acquireLockGuard() { return std::lock_guard{_mutex}; } + std::unique_lock acquireUniqueLock() { + return std::unique_lock{_mutex}; + } + std::unique_lock acquireUniqueLock(std::defer_lock_t) noexcept { + return std::unique_lock{_mutex, std::defer_lock}; + } void incHttpRequests(unsigned i) noexcept { _numRequests.fetch_add(i, std::memory_order_relaxed); diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index fe7c4c6eac63..2bb8db87c0a3 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -398,12 +398,12 @@ futures::Future RestAqlHandler::setupClusterQuery() { // PUT method for /_api/aql//, (internal) // see comment in header for details -RestStatus RestAqlHandler::useQuery(std::string const& operation, - std::string const& idString) { +auto RestAqlHandler::useQuery(std::string const& operation, + std::string const& idString) -> async { bool success = false; VPackSlice querySlice = this->parseVPackBody(success); if (!success) { - return RestStatus::DONE; + co_return; } if (_logContextQueryIdValue == nullptr) { @@ -416,17 +416,13 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, TRI_ASSERT(this->state() == RestHandler::HandlerState::EXECUTE || this->state() == RestHandler::HandlerState::CONTINUED); - auto res = findEngine(idString); + auto res = co_await findEngine(idString); + TRI_ASSERT(!res.is(TRI_ERROR_LOCKED)); if (res.fail()) { - if (res.is(TRI_ERROR_LOCKED)) { - // engine is still in use, but we have enqueued a callback to be woken - // up once it is free again - return RestStatus::WAITING; - } TRI_ASSERT(res.is(TRI_ERROR_QUERY_NOT_FOUND)); generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_QUERY_NOT_FOUND, absl::StrCat("query ID ", idString, " not found")); - return RestStatus::DONE; + co_return; } std::shared_ptr ss = _engine->sharedState(); ss->setWakeupHandler(withLogContext( @@ -435,7 +431,6 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, TRI_ASSERT(_engine != nullptr); TRI_ASSERT(std::to_string(_engine->engineId()) == idString); - auto guard = _engine->getQuery().acquireLockGuard(); if (_engine->getQuery().queryOptions().profile >= ProfileLevel::TraceOne) { LOG_TOPIC("1bf67", INFO, Logger::QUERIES) @@ -444,8 +439,20 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, << " registryId=" << idString; } + auto guard = _engine->getQuery().acquireUniqueLock(std::defer_lock); try { - return handleUseQuery(operation, querySlice); + co_await waitingFunToCoro([&] { + // only keep the lock while actually running the query. Also, note that + // we must never keep a mutex locked while co_awaiting, as we might switch + // threads. + guard.lock(); + auto status = handleUseQuery(operation, querySlice); + if (status == RestStatus::WAITING) { + guard.unlock(); + } + return status; + }); + TRI_ASSERT(guard.owns_lock()); } catch (arangodb::basics::Exception const& ex) { generateError(rest::ResponseCode::SERVER_ERROR, ex.code(), ex.what()); } catch (std::exception const& ex) { @@ -461,8 +468,6 @@ RestStatus RestAqlHandler::useQuery(std::string const& operation, generateError(rest::ResponseCode::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, "an unknown exception occurred"); } - - return RestStatus::DONE; } auto RestAqlHandler::prepareExecute(bool isContinue) -> std::vector> { @@ -474,12 +479,12 @@ auto RestAqlHandler::prepareExecute(bool isContinue) } // executes the handler -RestStatus RestAqlHandler::execute() { +auto RestAqlHandler::executeAsync() -> futures::Future { if (ServerState::instance()->isSingleServer()) { generateError(rest::ResponseCode::NOT_IMPLEMENTED, TRI_ERROR_HTTP_NOT_IMPLEMENTED, "this endpoint is only available in clusters"); - return RestStatus::DONE; + co_return; } std::vector const& suffixes = _request->suffixes(); @@ -493,7 +498,8 @@ RestStatus RestAqlHandler::execute() { if (suffixes.size() != 1) { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); } else if (suffixes[0] == "setup") { - return waitForFuture(setupClusterQuery()); + co_await setupClusterQuery(); + co_return; } else { auto msg = absl::StrCat("Unknown POST API: ", basics::StringUtils::join(suffixes, '/')); @@ -511,10 +517,7 @@ RestStatus RestAqlHandler::execute() { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, std::move(msg)); } else { - auto status = useQuery(suffixes[0], suffixes[1]); - if (status == RestStatus::WAITING) { - return status; - } + co_await useQuery(suffixes[0], suffixes[1]); } break; } @@ -525,10 +528,11 @@ RestStatus RestAqlHandler::execute() { LOG_TOPIC("f1993", ERR, arangodb::Logger::AQL) << msg; generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND, std::move(msg)); - return RestStatus::DONE; + co_return; } if (suffixes[0] == "finish") { - return handleFinishQuery(suffixes[1]); + co_await handleFinishQuery(suffixes[1]); + co_return; } generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_QUERY_NOT_FOUND, @@ -543,39 +547,7 @@ RestStatus RestAqlHandler::execute() { } } - return RestStatus::DONE; -} - -RestStatus RestAqlHandler::continueExecute() { - std::vector const& suffixes = _request->suffixes(); - - // extract the sub-request type - rest::RequestType type = _request->requestType(); - - if (type == rest::RequestType::POST) { - // we can get here when the future produced in setupClusterQuery() - // completes. in this case we can simply declare success - TRI_ASSERT(suffixes.size() == 1 && suffixes[0] == "setup"); - return RestStatus::DONE; - } - if (type == rest::RequestType::PUT) { - TRI_ASSERT(suffixes.size() == 2); - return useQuery(suffixes[0], suffixes[1]); - } - if (type == rest::RequestType::DELETE_REQ) { - // we can get here when the future produced in handleFinishQuery() - // completes. in this case we can simply declare success - TRI_ASSERT(suffixes.size() == 2 && suffixes[0] == "finish"); - return RestStatus::DONE; - } - - generateError( - rest::ResponseCode::SERVER_ERROR, TRI_ERROR_INTERNAL, - absl::StrCat("continued non-continuable method for ", - GeneralRequest::translateMethod(type), " /_api/aql/", - basics::StringUtils::join(suffixes, "/"))); - - return RestStatus::DONE; + co_return; } void RestAqlHandler::shutdownExecute(bool isFinalized) noexcept { @@ -600,7 +572,7 @@ void RestAqlHandler::shutdownExecute(bool isFinalized) noexcept { } // dig out the query from ID, handle errors -Result RestAqlHandler::findEngine(std::string const& idString) { +auto RestAqlHandler::findEngine(std::string const& idString) -> async { TRI_ASSERT(_engine == nullptr); uint64_t qId = arangodb::basics::StringUtils::uint64(idString); @@ -656,16 +628,26 @@ Result RestAqlHandler::findEngine(std::string const& idString) { // Here Engine could be gone } } - auto res = _queryRegistry->openExecutionEngine( - qId, [self = shared_from_this()]() { self->wakeupHandler(); }); + + auto res = co_await waitingFunToCoro([&] { + auto res = _queryRegistry->openExecutionEngine( + qId, [self = shared_from_this()]() { self->wakeupHandler(); }); + if (res.is(TRI_ERROR_LOCKED)) { + // engine is still in use, but we have enqueued a callback to be woken + // up once it is free again + return std::optional{std::nullopt}; + } + return std::optional{std::move(res)}; + }); + if (res.fail()) { - return std::move(res).result(); + co_return std::move(res).result(); } _engine = res.get(); TRI_ASSERT(_engine != nullptr || _engine->engineId() == qId); - return Result{}; + co_return Result{}; } class AqlExecuteCall { @@ -827,7 +809,8 @@ RestStatus RestAqlHandler::handleUseQuery(std::string const& operation, } // handle query finalization for all engines -RestStatus RestAqlHandler::handleFinishQuery(std::string const& idString) { +auto RestAqlHandler::handleFinishQuery(std::string const& idString) + -> async { TRI_IF_FAILURE("Query::finishTimeout") { // intentionally delay the request std::this_thread::sleep_for( @@ -838,7 +821,7 @@ RestStatus RestAqlHandler::handleFinishQuery(std::string const& idString) { bool success = false; VPackSlice querySlice = this->parseVPackBody(success); if (!success) { - return RestStatus::DONE; + co_return; } auto errorCode = @@ -846,49 +829,36 @@ RestStatus RestAqlHandler::handleFinishQuery(std::string const& idString) { ErrorCode::ValueType>( querySlice, StaticStrings::Code, TRI_ERROR_INTERNAL); - auto f = - _queryRegistry->finishQuery(qid, errorCode) - .thenValue([self = shared_from_this(), this, - errorCode](std::shared_ptr query) mutable - -> futures::Future { - if (query == nullptr) { - // this may be a race between query garbage collection and - // the client shutting down the query. it is debatable - // whether this is an actual error if we only want to abort - // the query... - generateError(rest::ResponseCode::NOT_FOUND, - TRI_ERROR_HTTP_NOT_FOUND); - return futures::Unit{}; - } - // we must be the only user of this query - TRI_ASSERT(query.use_count() == 1) - << "Finalizing query with use_count " << query.use_count(); - return query->finalizeClusterQuery(errorCode).thenValue( - [self = std::move(self), this, - q = std::move(query)](Result res) { - VPackBufferUInt8 buffer; - VPackBuilder answerBuilder(buffer); - answerBuilder.openObject(/*unindexed*/ true); - answerBuilder.add(VPackValue("stats")); - - q->executionStatsGuard().doUnderLock( - [&](auto& executionStats) { - executionStats.toVelocyPack( - answerBuilder, q->queryOptions().fullCount); - }); - - q->warnings().toVelocyPack(answerBuilder); - answerBuilder.add(StaticStrings::Error, - VPackValue(res.fail())); - answerBuilder.add(StaticStrings::Code, - VPackValue(res.errorNumber())); - answerBuilder.close(); - - generateResult(rest::ResponseCode::OK, std::move(buffer)); - }); - }); - - return waitForFuture(std::move(f)); + auto query = co_await _queryRegistry->finishQuery(qid, errorCode); + + if (query == nullptr) { + // this may be a race between query garbage collection and + // the client shutting down the query. it is debatable + // whether this is an actual error if we only want to abort + // the query... + generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); + co_return; + } + // we must be the only user of this query + TRI_ASSERT(query.use_count() == 1) + << "Finalizing query with use_count " << query.use_count(); + auto res = co_await query->finalizeClusterQuery(errorCode); + + VPackBufferUInt8 buffer; + VPackBuilder answerBuilder(buffer); + answerBuilder.openObject(/*unindexed*/ true); + answerBuilder.add(VPackValue("stats")); + + query->executionStatsGuard().doUnderLock([&](auto& executionStats) { + executionStats.toVelocyPack(answerBuilder, query->queryOptions().fullCount); + }); + + query->warnings().toVelocyPack(answerBuilder); + answerBuilder.add(StaticStrings::Error, VPackValue(res.fail())); + answerBuilder.add(StaticStrings::Code, VPackValue(res.errorNumber())); + answerBuilder.close(); + + generateResult(rest::ResponseCode::OK, std::move(buffer)); } RequestLane RestAqlHandler::lane() const { diff --git a/arangod/Aql/RestAqlHandler.h b/arangod/Aql/RestAqlHandler.h index 1037c70d53d3..be895d13fcf4 100644 --- a/arangod/Aql/RestAqlHandler.h +++ b/arangod/Aql/RestAqlHandler.h @@ -50,8 +50,7 @@ class RestAqlHandler : public RestVocbaseBaseHandler { char const* name() const override final { return "RestAqlHandler"; } RequestLane lane() const override final; - RestStatus execute() override; - RestStatus continueExecute() override; + auto executeAsync() -> futures::Future override; [[nodiscard]] auto prepareExecute(bool isContinue) -> std::vector> override; void shutdownExecute(bool isFinalized) noexcept override; @@ -103,8 +102,8 @@ class RestAqlHandler : public RestVocbaseBaseHandler { // set, then the root block of the stored query must be a ScatterBlock // and the shard ID is given as an additional argument to the ScatterBlock's // special API. - RestStatus useQuery(std::string const& operation, - std::string const& idString); + auto useQuery(std::string const& operation, std::string const& idString) + -> async; // POST method for /_api/aql/setup (internal) // Only available on DBServers in the Cluster. @@ -136,10 +135,10 @@ class RestAqlHandler : public RestVocbaseBaseHandler { arangodb::velocypack::Slice querySlice); // handle query finalization for all engines - RestStatus handleFinishQuery(std::string const& idString); + auto handleFinishQuery(std::string const& idString) -> async; // dig out vocbase from context and query from ID, handle errors - Result findEngine(std::string const& idString); + auto findEngine(std::string const& idString) -> async; // our query registry QueryRegistry* _queryRegistry; diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index cc04edbbfaa1..c78005d41bd7 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -46,6 +46,7 @@ #include "VocBase/Identifiers/TransactionId.h" #include "VocBase/ticks.h" +#include #include #include #include @@ -697,50 +698,6 @@ void RestHandler::generateError(arangodb::Result const& r) { generateError(code, r.errorNumber(), r.errorMessage()); } -RestStatus RestHandler::waitForFuture(futures::Future&& f) { - if (f.isReady()) { // fast-path out - f.result().throwIfFailed(); // just throw the error upwards - return RestStatus::DONE; - } - TRI_ASSERT(_executionCounter == 0); - _executionCounter = 2; - std::move(f).thenFinal(withLogContext( - [self = shared_from_this()](futures::Try&& t) -> void { - if (t.hasException()) { - self->handleExceptionPtr(std::move(t).exception()); - } - if (--self->_executionCounter == 0) { - self->wakeupHandler(); - } - })); - return --_executionCounter == 0 ? RestStatus::DONE : RestStatus::WAITING; -} - -RestStatus RestHandler::waitForFuture(futures::Future&& f) { - if (f.isReady()) { // fast-path out - f.result().throwIfFailed(); // just throw the error upwards - return f.waitAndGet(); - } - TRI_ASSERT(_executionCounter == 0); - _executionCounter = 2; - std::move(f).thenFinal(withLogContext( - [self = shared_from_this()](futures::Try&& t) -> void { - if (t.hasException()) { - self->handleExceptionPtr(std::move(t).exception()); - self->_followupRestStatus = RestStatus::DONE; - } else { - self->_followupRestStatus = t.get(); - if (t.get() == RestStatus::WAITING) { - return; // rest handler will be woken up externally - } - } - if (--self->_executionCounter == 0) { - self->wakeupHandler(); - } - })); - return --_executionCounter == 0 ? _followupRestStatus : RestStatus::WAITING; -} - // ----------------------------------------------------------------------------- // --SECTION-- protected methods // ----------------------------------------------------------------------------- @@ -756,7 +713,8 @@ futures::Future RestHandler::executeAsync() { auto state = execute(); if (state == RestStatus::WAITING) { - co_await waitingFunToCoro(std::bind(&std::decay_t::continueExecute, this)); + co_await waitingFunToCoro( + std::bind(&std::decay_t::continueExecute, this)); } } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 9e82b6cf8f6d..849c57413943 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -165,9 +165,6 @@ class RestHandler : public std::enable_shared_from_this { // generates an error void generateError(arangodb::Result const&); - [[nodiscard]] RestStatus waitForFuture(futures::Future&& f); - [[nodiscard]] RestStatus waitForFuture(futures::Future&& f); - enum class HandlerState : uint8_t { PREPARE = 0, EXECUTE, @@ -205,8 +202,6 @@ class RestHandler : public std::enable_shared_from_this { private: mutable std::mutex _executionMutex; - mutable std::atomic_uint8_t _executionCounter{0}; - mutable RestStatus _followupRestStatus; protected: // TODO Move this in a separate header, side-by-side with SuspensionSemaphore? @@ -214,7 +209,9 @@ class RestHandler : public std::enable_shared_from_this { // RestHandler::wakeupHandler() does that, and can be called e.g. by the // SharedQueryState's wakeup handler (for AQL-related code). template - requires requires(F f) { { f() } -> std::same_as; } + requires requires(F f) { + { f() } -> std::same_as; + } [[nodiscard]] auto waitingFunToCoro(F&& funArg) -> async { auto fun = std::forward(funArg); auto state = fun(); @@ -230,6 +227,25 @@ class RestHandler : public std::enable_shared_from_this { co_return; } + template::value_type> + requires requires(F f) { + { f() } -> std::same_as>; + } + [[nodiscard]] auto waitingFunToCoro(F&& funArg) -> async { + auto fun = std::forward(funArg); + auto res = fun(); + + while (!res.has_value()) { + // Get the number of wakeups. We call fun() up to that many + // times before suspending again. + auto n = co_await _suspensionSemaphore.await(); + for (auto i = 0; i < n && !res.has_value(); ++i) { + res = fun(); + } + } + co_return std::move(res).value(); + } + private: SuspensionSemaphore _suspensionSemaphore; From 7c58e6a9ddfb1e8804175b3fbeebc80b78da0c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 7 May 2025 21:08:21 +0200 Subject: [PATCH 12/35] clang-format --- arangod/RestHandler/RestCursorHandler.cpp | 4 ++-- arangod/RestHandler/RestCursorHandler.h | 2 +- arangod/RestHandler/RestSimpleHandler.cpp | 6 ++---- lib/Logger/LogContext.h | 13 +++++++------ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index 059982c2207c..65e09fead2e7 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -253,7 +253,7 @@ async RestCursorHandler::processQuery() { // always clean up auto guard = scopeGuard([this]() noexcept { unregisterQuery(); }); - co_await waitingFunToCoro([&]{ + co_await waitingFunToCoro([&] { auto state = query->execute(_queryResult); if (state == aql::ExecutionState::WAITING) { @@ -575,7 +575,7 @@ async RestCursorHandler::generateCursorResult(rest::ResponseCode code) { auto r = Result(); - co_await waitingFunToCoro([&](){ + co_await waitingFunToCoro([&]() { auto const [state, result] = _cursor->dump(builder); if (state == aql::ExecutionState::WAITING) { diff --git a/arangod/RestHandler/RestCursorHandler.h b/arangod/RestHandler/RestCursorHandler.h index 25aa3933cddb..a0890186717f 100644 --- a/arangod/RestHandler/RestCursorHandler.h +++ b/arangod/RestHandler/RestCursorHandler.h @@ -109,7 +109,7 @@ class RestCursorHandler : public RestVocbaseBaseHandler { /// @brief create a cursor and return the first results async createQueryCursor(); - /// @brief return the next results from an existing cursor + /// @brief return the next results from an existing cursor async modifyQueryCursor(); /// @brief dispose an existing cursor diff --git a/arangod/RestHandler/RestSimpleHandler.cpp b/arangod/RestHandler/RestSimpleHandler.cpp index 26a9b7db3849..d591e7ed39e0 100644 --- a/arangod/RestHandler/RestSimpleHandler.cpp +++ b/arangod/RestHandler/RestSimpleHandler.cpp @@ -83,8 +83,7 @@ auto RestSimpleHandler::executeAsync() -> futures::Future { co_return; } -async RestSimpleHandler::removeByKeys( - VPackSlice const& slice) { +async RestSimpleHandler::removeByKeys(VPackSlice const& slice) { TRI_ASSERT(slice.isObject()); std::string collectionName; { @@ -250,8 +249,7 @@ void RestSimpleHandler::handleQueryResultLookupByKeys() { _queryResult.context); } -async RestSimpleHandler::lookupByKeys( - VPackSlice const& slice) { +async RestSimpleHandler::lookupByKeys(VPackSlice const& slice) { if (response() == nullptr) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid response"); } diff --git a/lib/Logger/LogContext.h b/lib/Logger/LogContext.h index dbf50f5e4f8d..db6c1caffe11 100644 --- a/lib/Logger/LogContext.h +++ b/lib/Logger/LogContext.h @@ -572,12 +572,13 @@ struct LogContext::Accessor::ScopedValue { } // TODO This constructor is broken, as the destructor will still pop only a // single entry! -// explicit ScopedValue(std::vector>&& vs) { -// for (auto&& v : vs) { -// appendEntry>(std::move(v)); -// } -// vs.clear(); -// } + // explicit ScopedValue(std::vector>&& + // vs) { + // for (auto&& v : vs) { + // appendEntry>(std::move(v)); + // } + // vs.clear(); + // } template explicit ScopedValue(ValueBuilder&& v) { From af703879cca35645c71610d8bc78be3b290fd691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 13:12:18 +0200 Subject: [PATCH 13/35] Fixed RestCursorHandler::cancel(): Must not reset wakeup handler --- arangod/GeneralServer/RestHandler.h | 2 +- arangod/RestHandler/RestCursorHandler.cpp | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 849c57413943..21be339ec405 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -134,7 +134,7 @@ class RestHandler : public std::enable_shared_from_this { virtual RestStatus continueExecute() { return RestStatus::DONE; } virtual void shutdownExecute(bool isFinalized) noexcept; - // you might need to implment this in your handler + // you might need to implement this in your handler // if it will be executed in an async job virtual void cancel() { _canceled.store(true); } diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index 65e09fead2e7..6bffd8ac2e21 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -450,13 +450,7 @@ void RestCursorHandler::cancelQuery() { std::lock_guard mutexLocker{_queryLock}; if (_query != nullptr) { - // cursor is canceled. now remove the continue handler we may have - // registered in the query - if (_query->sharedState()) { - _query->sharedState()->resetWakeupHandler(); - } - - _query->setKillFlag(); + _query->kill(); } _queryKilled = true; } From b716cf0a0a74e398298a8ddb7b2f2a894d5cfe76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 13:24:20 +0200 Subject: [PATCH 14/35] Fixed false positives in shell-aql-kill.js --- tests/js/client/shell/multi/shell-aql-kill.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/js/client/shell/multi/shell-aql-kill.js b/tests/js/client/shell/multi/shell-aql-kill.js index d57e197c8b07..296971ee4b14 100644 --- a/tests/js/client/shell/multi/shell-aql-kill.js +++ b/tests/js/client/shell/multi/shell-aql-kill.js @@ -56,7 +56,8 @@ function aqlKillSuite () { } } console.warn(`Giving up after ${stopAfter}s in ` + JSON.stringify(new Error().stack)); - return { code: 500, error: true, errorMessage: `Giving up after ${stopAfter}s in ${JSON.stringify(new Error().stack)}`}; + + return undefined; } function queryGone (queryId) { @@ -116,9 +117,6 @@ function aqlKillSuite () { assertEqual(killResult.code, 200, { httpres: JSON.stringify(killResult), sleepForMs }); const putResult = tryForUntil({until: jobGone(jobId)}); - if (isCov && putResult.code === 404 && putResult.errorNum === 1591) { - return; - } assertEqual(410, putResult.code, JSON.stringify(putResult)); } From ea8ad936970a9d042c3b8465cc1c88cfe4b12bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 14:40:48 +0200 Subject: [PATCH 15/35] Fixed QueryCursorTest: call RestCursorHandler::executeAsync() instead of ::execute() --- tests/Aql/QueryCursorTest.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Aql/QueryCursorTest.cpp b/tests/Aql/QueryCursorTest.cpp index 91af64cd7ca5..32d90263483a 100644 --- a/tests/Aql/QueryCursorTest.cpp +++ b/tests/Aql/QueryCursorTest.cpp @@ -76,7 +76,7 @@ TEST_F(QueryCursorTest, resultCursorResultArrayIndexSingleBatch) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->execute(); + testee->executeAsync().waitAndGet(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -110,7 +110,7 @@ TEST_F(QueryCursorTest, resultCursorResultArrayIndexTwoBatches) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->execute(); + testee->executeAsync().waitAndGet(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -145,7 +145,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexSingleBatch) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->execute(); + testee->executeAsync().waitAndGet(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -180,7 +180,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexTwoBatches) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->execute(); + testee->executeAsync().waitAndGet(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -201,7 +201,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexTwoBatches) { auto restHandler = std::make_shared( server->server(), fakeRequest.release(), fakeResponse.release(), registry); - restHandler->execute(); + restHandler->executeAsync().waitAndGet(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); } From c78e5b51fced8de58e083d8f9aa44710a6f765a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 15:28:44 +0200 Subject: [PATCH 16/35] Fix more C++ tests --- tests/Aql/QueryCursorTest.cpp | 10 +++--- tests/Mocks/MockGraph.cpp | 2 +- .../RestTransactionHandlerTest.cpp | 33 +++++++------------ 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/tests/Aql/QueryCursorTest.cpp b/tests/Aql/QueryCursorTest.cpp index 32d90263483a..0f6133beb12f 100644 --- a/tests/Aql/QueryCursorTest.cpp +++ b/tests/Aql/QueryCursorTest.cpp @@ -76,7 +76,7 @@ TEST_F(QueryCursorTest, resultCursorResultArrayIndexSingleBatch) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->executeAsync().waitAndGet(); + testee->executeAsync().wait(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -110,7 +110,7 @@ TEST_F(QueryCursorTest, resultCursorResultArrayIndexTwoBatches) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->executeAsync().waitAndGet(); + testee->executeAsync().wait(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -145,7 +145,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexSingleBatch) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->executeAsync().waitAndGet(); + testee->executeAsync().wait(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -180,7 +180,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexTwoBatches) { server->server(), fakeRequest.release(), fakeResponse.release(), registry); - testee->executeAsync().waitAndGet(); + testee->executeAsync().wait(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); @@ -201,7 +201,7 @@ TEST_F(QueryCursorTest, streamingCursorResultArrayIndexTwoBatches) { auto restHandler = std::make_shared( server->server(), fakeRequest.release(), fakeResponse.release(), registry); - restHandler->executeAsync().waitAndGet(); + restHandler->executeAsync().wait(); fakeResponse.reset( dynamic_cast(testee->stealResponse().release())); } diff --git a/tests/Mocks/MockGraph.cpp b/tests/Mocks/MockGraph.cpp index 77c0cf90e49f..5429986b5fe2 100644 --- a/tests/Mocks/MockGraph.cpp +++ b/tests/Mocks/MockGraph.cpp @@ -293,7 +293,7 @@ MockGraph::simulateApi( server.server(), fakeRequest.release(), fakeResponse.release(), &queryRegistry}; - aqlHandler.execute(); + aqlHandler.executeAsync().wait(); auto response = aqlHandler.stealResponse(); // Read: (EngineId eid) auto resBody = static_cast(response.get())->_payload.slice(); diff --git a/tests/Transaction/RestTransactionHandlerTest.cpp b/tests/Transaction/RestTransactionHandlerTest.cpp index a0243ea5f23c..19707c422834 100644 --- a/tests/Transaction/RestTransactionHandlerTest.cpp +++ b/tests/Transaction/RestTransactionHandlerTest.cpp @@ -85,8 +85,7 @@ TEST_F(RestTransactionHandlerTest, parsing_errors) { request.addSuffix("begin"); parser.parse("{ \"write\": [33] }"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::BAD, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -111,8 +110,7 @@ TEST_F(RestTransactionHandlerTest, collection_not_found_ro) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"read\": [\"33\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::NOT_FOUND, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -137,8 +135,7 @@ TEST_F(RestTransactionHandlerTest, collection_not_found_write) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"write\": [\"33\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::NOT_FOUND, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -163,8 +160,7 @@ TEST_F(RestTransactionHandlerTest, collection_not_found_exclusive) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"exclusive\": [\"33\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::NOT_FOUND, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -197,8 +193,7 @@ TEST_F(RestTransactionHandlerTest, simple_transaction_abort) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"read\": [\"42\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::CREATED, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -222,8 +217,7 @@ TEST_F(RestTransactionHandlerTest, simple_transaction_abort) { request.clearSuffixes(); request.addSuffix(tid); - status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::OK, responce.responseCode()); slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -245,8 +239,7 @@ TEST_F(RestTransactionHandlerTest, simple_transaction_abort) { request.setRequestType(arangodb::rest::RequestType::DELETE_REQ); - status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::OK, responce.responseCode()); slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -278,8 +271,7 @@ TEST_F(RestTransactionHandlerTest, simple_transaction_and_commit) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"read\": [\"42\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::CREATED, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -303,8 +295,7 @@ TEST_F(RestTransactionHandlerTest, simple_transaction_and_commit) { request.clearSuffixes(); request.addSuffix(tid); - status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::OK, responce.responseCode()); slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -346,8 +337,7 @@ TEST_F(RestTransactionHandlerTest, permission_denied_read_only) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"write\": [\"42\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::FORBIDDEN, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); @@ -390,8 +380,7 @@ TEST_F(RestTransactionHandlerTest, permission_denied_forbidden) { request.addSuffix("begin"); parser.parse("{ \"collections\":{\"write\": [\"42\"]}}"); - arangodb::RestStatus status = handler.execute(); - EXPECT_EQ(arangodb::RestStatus::DONE, status); + handler.executeAsync().wait(); EXPECT_EQ(arangodb::rest::ResponseCode::FORBIDDEN, responce.responseCode()); VPackSlice slice = responce._payload.slice(); EXPECT_TRUE(slice.isObject()); From 2e7c144726f8497b724e6afb266c09968be81b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 17:10:47 +0200 Subject: [PATCH 17/35] Restored LogContext functionality by adding it to async::Context --- arangod/GeneralServer/RestHandler.cpp | 9 +++--- lib/Async/include/Async/context.h | 16 +++++++++- lib/Logger/LogContext.cpp | 7 +++++ lib/Logger/LogContext.h | 44 +++++++++++++++------------ 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index c78005d41bd7..2b4b77145bf6 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -426,13 +426,14 @@ auto RestHandler::runHandlerStateMachine() -> futures::Future { TRI_ASSERT(_state == HandlerState::PREPARE); auto logContextValues = prepareEngine(); // TODO This is currently broken. - // For one, because the ScopedValue constructor from a vector is broken. - // Second, because somewhere the along the line the LogContext gets + // Because somewhere the along the line the LogContext gets // changed, and we have one with a different tail before the destructor // gets called. The latter is planned to being addressed in a different // PR. - // auto const logScopeGuard = - // LogContext::Accessor::ScopedValue(std::move(logContextValues)); + auto const logScopeGuard = + LogContext::Accessor::ScopedValue(std::move(logContextValues)); + + if (_state == HandlerState::FAILED) { co_return fail(); } diff --git a/lib/Async/include/Async/context.h b/lib/Async/include/Async/context.h index ff2d35b74408..af96587316e6 100644 --- a/lib/Async/include/Async/context.h +++ b/lib/Async/include/Async/context.h @@ -23,6 +23,7 @@ #pragma once #include "Async/Registry/promise.h" +#include "Logger/LogContext.h" #include "Utils/ExecContext.h" namespace arangodb { @@ -30,14 +31,27 @@ namespace arangodb { struct Context { std::shared_ptr _execContext; async_registry::Requester _requester; + std::optional _logContext; Context() : _execContext{ExecContext::currentAsShared()}, - _requester{*async_registry::get_current_coroutine()} {} + _requester{*async_registry::get_current_coroutine()}, + _logContext{LogContext::current()} {} + + auto operator=(Context&& other) noexcept -> Context& { + _execContext = std::move(other._execContext); + _requester = other._requester; + _logContext.reset(); + _logContext.swap(other._logContext); + other._logContext.reset(); + + return *this; + } auto set() -> void { ExecContext::set(_execContext); *async_registry::get_current_coroutine() = _requester; + LogContext::setCurrent(*_logContext); } }; diff --git a/lib/Logger/LogContext.cpp b/lib/Logger/LogContext.cpp index 39c6fdf02b01..dc94f0ca5d3f 100644 --- a/lib/Logger/LogContext.cpp +++ b/lib/Logger/LogContext.cpp @@ -71,5 +71,12 @@ void LogContext::doVisit(Visitor const& visitor, Entry const* entry) const { } void LogContext::setCurrent(LogContext ctx) noexcept { + _threadControlBlock._logContext.clear(_threadControlBlock._entryCache); _threadControlBlock._logContext = std::move(ctx); } + +void LogContext::ValueBag::visit(Visitor const& visitor) const { + for (auto& v : _values) { + v->visit(visitor); + } +} diff --git a/lib/Logger/LogContext.h b/lib/Logger/LogContext.h index db6c1caffe11..adbf90d9d4d9 100644 --- a/lib/Logger/LogContext.h +++ b/lib/Logger/LogContext.h @@ -96,6 +96,7 @@ class LogContext { struct EntryPtr; struct Values; + struct ValueBag; template struct KeyValue { @@ -311,7 +312,7 @@ struct LogContext::ValueBuilder, Base, Depth> { *this, std::forward(v)); } std::shared_ptr share() && { - return std::move(*this).passValues([](Args && ... args) { + return std::move(*this).passValues([](Args&&... args) { return std::make_shared>( std::forward(args)...); }); @@ -324,10 +325,9 @@ struct LogContext::ValueBuilder, Base, Depth> { template auto passValues(F&& func) && { - return [ this, &func ](std::index_sequence) { + return [this, &func](std::index_sequence) { return std::forward(func)(this->template value()...); - } - (std::make_index_sequence{}); + }(std::make_index_sequence{}); } template @@ -361,6 +361,16 @@ struct LogContext::Values { virtual void visit(Visitor const&) const = 0; }; +struct LogContext::ValueBag : Values { + template + explicit ValueBag(V&&... v) : _values(std::forward(v)...) {} + virtual ~ValueBag() = default; + void visit(Visitor const&) const override; + + private: + std::vector> _values; +}; + template struct LogContext::ValuesImpl> final : Values { @@ -570,19 +580,14 @@ struct LogContext::Accessor::ScopedValue { explicit ScopedValue(std::shared_ptr v) { appendEntry>(std::move(v)); } - // TODO This constructor is broken, as the destructor will still pop only a - // single entry! - // explicit ScopedValue(std::vector>&& - // vs) { - // for (auto&& v : vs) { - // appendEntry>(std::move(v)); - // } - // vs.clear(); - // } + // TODO Maybe we should remove this constructor and have users create a + // ValueBag themselves. + explicit ScopedValue(std::vector>&& vs) + : ScopedValue(std::make_shared(std::move(vs))) {} template explicit ScopedValue(ValueBuilder&& v) { - std::move(v).passValues([this](Args && ... args) { + std::move(v).passValues([this](Args&&... args) { this->appendEntry< ValuesImpl::ValueTypesT, typename ValueBuilder::KeysT>>( @@ -735,7 +740,7 @@ inline LogContext::EntryPtr LogContext::Current::pushValues( template inline LogContext::EntryPtr LogContext::Current::pushValues( ValueBuilder&& v) { - return std::move(v).passValues([](Args && ... args) { + return std::move(v).passValues([](Args&&... args) { return Current::appendEntry< ValuesImpl::ValueTypesT, typename ValueBuilder::KeysT>>( @@ -797,11 +802,10 @@ inline void LogContext::popTail(EntryCache& cache) noexcept { /// to retain the current LogContext (e.g., when using futures). template auto withLogContext(Func&& func) { - return [ - func = std::forward(func), ctx = LogContext::current() - ]>>( - Args && ... args) mutable { + return [func = std::forward(func), ctx = LogContext::current()]< + typename... Args, + typename = std::enable_if_t>>( + Args&&... args) mutable { LogContext::ScopedContext ctxGuard(ctx); return std::forward(func)(std::forward(args)...); }; From 6d523babbe02169ffac224170e2c18dde8fb2e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Fri, 9 May 2025 20:05:16 +0200 Subject: [PATCH 18/35] clang-format --- arangod/GeneralServer/RestHandler.cpp | 5 ++--- lib/Logger/LogContext.h | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 2b4b77145bf6..ab1dc7099062 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -430,9 +430,8 @@ auto RestHandler::runHandlerStateMachine() -> futures::Future { // changed, and we have one with a different tail before the destructor // gets called. The latter is planned to being addressed in a different // PR. - auto const logScopeGuard = - LogContext::Accessor::ScopedValue(std::move(logContextValues)); - + auto const logScopeGuard = + LogContext::Accessor::ScopedValue(std::move(logContextValues)); if (_state == HandlerState::FAILED) { co_return fail(); diff --git a/lib/Logger/LogContext.h b/lib/Logger/LogContext.h index adbf90d9d4d9..55d6502ad03e 100644 --- a/lib/Logger/LogContext.h +++ b/lib/Logger/LogContext.h @@ -312,7 +312,7 @@ struct LogContext::ValueBuilder, Base, Depth> { *this, std::forward(v)); } std::shared_ptr share() && { - return std::move(*this).passValues([](Args&&... args) { + return std::move(*this).passValues([](Args && ... args) { return std::make_shared>( std::forward(args)...); }); @@ -325,9 +325,10 @@ struct LogContext::ValueBuilder, Base, Depth> { template auto passValues(F&& func) && { - return [this, &func](std::index_sequence) { + return [ this, &func ](std::index_sequence) { return std::forward(func)(this->template value()...); - }(std::make_index_sequence{}); + } + (std::make_index_sequence{}); } template @@ -587,7 +588,7 @@ struct LogContext::Accessor::ScopedValue { template explicit ScopedValue(ValueBuilder&& v) { - std::move(v).passValues([this](Args&&... args) { + std::move(v).passValues([this](Args && ... args) { this->appendEntry< ValuesImpl::ValueTypesT, typename ValueBuilder::KeysT>>( @@ -740,7 +741,7 @@ inline LogContext::EntryPtr LogContext::Current::pushValues( template inline LogContext::EntryPtr LogContext::Current::pushValues( ValueBuilder&& v) { - return std::move(v).passValues([](Args&&... args) { + return std::move(v).passValues([](Args && ... args) { return Current::appendEntry< ValuesImpl::ValueTypesT, typename ValueBuilder::KeysT>>( @@ -802,10 +803,11 @@ inline void LogContext::popTail(EntryCache& cache) noexcept { /// to retain the current LogContext (e.g., when using futures). template auto withLogContext(Func&& func) { - return [func = std::forward(func), ctx = LogContext::current()]< - typename... Args, - typename = std::enable_if_t>>( - Args&&... args) mutable { + return [ + func = std::forward(func), ctx = LogContext::current() + ]>>( + Args && ... args) mutable { LogContext::ScopedContext ctxGuard(ctx); return std::forward(func)(std::forward(args)...); }; From 321bebd011f3182a7c584782bc0d214703c952ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Sat, 10 May 2025 10:20:25 +0200 Subject: [PATCH 19/35] Just set kill flag during cancel to avoid a data race --- arangod/RestHandler/RestCursorHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arangod/RestHandler/RestCursorHandler.cpp b/arangod/RestHandler/RestCursorHandler.cpp index 6bffd8ac2e21..a5459243c818 100644 --- a/arangod/RestHandler/RestCursorHandler.cpp +++ b/arangod/RestHandler/RestCursorHandler.cpp @@ -450,7 +450,7 @@ void RestCursorHandler::cancelQuery() { std::lock_guard mutexLocker{_queryLock}; if (_query != nullptr) { - _query->kill(); + _query->setKillFlag(); } _queryKilled = true; } From c0d6b29566eba231c18e5742bb48cf497a8804fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Thu, 15 May 2025 14:44:49 +0200 Subject: [PATCH 20/35] Move co_await into try/catch --- arangod/Agency/RestAgencyHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arangod/Agency/RestAgencyHandler.cpp b/arangod/Agency/RestAgencyHandler.cpp index 8877777fa1fa..6e55aacc65e1 100644 --- a/arangod/Agency/RestAgencyHandler.cpp +++ b/arangod/Agency/RestAgencyHandler.cpp @@ -160,9 +160,9 @@ auto RestAgencyHandler::pollIndex(index_t const& start, double const& timeout) auto pollResult = _agent->poll(start, timeout); if (std::get<1>(pollResult)) { - auto rb = co_await std::move(std::get<0>(pollResult)); - try { + auto rb = co_await std::move(std::get<0>(pollResult)); + VPackSlice res = rb->slice(); if (res.isObject() && res.hasKey("result")) { From 626d53427cdc471332b864d14895b6f4aad86b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 15:06:02 +0200 Subject: [PATCH 21/35] Set queryId LogContext correctly --- arangod/Aql/RestAqlHandler.cpp | 21 ++++++--------------- arangod/Aql/RestAqlHandler.h | 5 ----- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index 2bb8db87c0a3..da7f08402a78 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -150,10 +150,10 @@ futures::Future RestAqlHandler::setupClusterQuery() { TRI_ASSERT(clusterQueryId > 0); } - TRI_ASSERT(_logContextQueryIdValue == nullptr); - _logContextQueryIdValue = LogContext::makeValue() + auto queryIdValue = LogContext::makeValue() .with(clusterQueryId) .share(); + auto const logContextScopeGuard = ScopedValue{std::move(queryIdValue)}; VPackSlice lockInfoSlice = querySlice.get("lockInfo"); @@ -406,11 +406,10 @@ auto RestAqlHandler::useQuery(std::string const& operation, co_return; } - if (_logContextQueryIdValue == nullptr) { - _logContextQueryIdValue = LogContext::makeValue() - .with(idString) - .share(); - } + auto queryIdValue = LogContext::makeValue() + .with(idString) + .share(); + auto const logContextScopeGuard = ScopedValue{std::move(queryIdValue)}; if (!_engine) { // the PUT verb TRI_ASSERT(this->state() == RestHandler::HandlerState::EXECUTE || @@ -469,14 +468,6 @@ auto RestAqlHandler::useQuery(std::string const& operation, "an unknown exception occurred"); } } -auto RestAqlHandler::prepareExecute(bool isContinue) - -> std::vector> { - auto vector = RestVocbaseBaseHandler::prepareExecute(isContinue); - if (_logContextQueryIdValue != nullptr) { - vector.emplace_back(_logContextQueryIdValue); - } - return vector; -} // executes the handler auto RestAqlHandler::executeAsync() -> futures::Future { diff --git a/arangod/Aql/RestAqlHandler.h b/arangod/Aql/RestAqlHandler.h index be895d13fcf4..f8637ad4ee1b 100644 --- a/arangod/Aql/RestAqlHandler.h +++ b/arangod/Aql/RestAqlHandler.h @@ -51,8 +51,6 @@ class RestAqlHandler : public RestVocbaseBaseHandler { char const* name() const override final { return "RestAqlHandler"; } RequestLane lane() const override final; auto executeAsync() -> futures::Future override; - [[nodiscard]] auto prepareExecute(bool isContinue) - -> std::vector> override; void shutdownExecute(bool isFinalized) noexcept override; class Route { @@ -144,9 +142,6 @@ class RestAqlHandler : public RestVocbaseBaseHandler { QueryRegistry* _queryRegistry; ExecutionEngine* _engine; - - std::shared_ptr _logContextQueryIdValue; - LogContext::EntryPtr _logContextQueryIdEntry; }; } // namespace arangodb::aql From 09f9553fa35166217765d3935563bd2ee89ebc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 15:12:30 +0200 Subject: [PATCH 22/35] Minor change to Context::_logContext --- lib/Async/include/Async/context.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Async/include/Async/context.h b/lib/Async/include/Async/context.h index af96587316e6..7dade75937ac 100644 --- a/lib/Async/include/Async/context.h +++ b/lib/Async/include/Async/context.h @@ -31,6 +31,9 @@ namespace arangodb { struct Context { std::shared_ptr _execContext; async_registry::Requester _requester; + // Note that this is optional just because LogContext::clear is private, + // and operator= needs the LHS to be cleared. A simple change to LogContext + // could make this optional unnecessary. std::optional _logContext; Context() @@ -42,13 +45,13 @@ struct Context { _execContext = std::move(other._execContext); _requester = other._requester; _logContext.reset(); - _logContext.swap(other._logContext); + _logContext = std::move(other._logContext); other._logContext.reset(); return *this; } - auto set() -> void { + auto set() noexcept -> void { ExecContext::set(_execContext); *async_registry::get_current_coroutine() = _requester; LogContext::setCurrent(*_logContext); From eec77eeeef193027859ef4946495cc9e52f4bc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 15:43:03 +0200 Subject: [PATCH 23/35] Updated comments --- arangod/GeneralServer/RestHandler.cpp | 5 ----- arangod/GeneralServer/RestHandler.h | 1 + arangod/RestHandler/RestVocbaseBaseHandler.h | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index ab1dc7099062..8fd58bf7d919 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -425,11 +425,6 @@ auto RestHandler::runHandlerStateMachine() -> futures::Future { TRI_ASSERT(_state == HandlerState::PREPARE); auto logContextValues = prepareEngine(); - // TODO This is currently broken. - // Because somewhere the along the line the LogContext gets - // changed, and we have one with a different tail before the destructor - // gets called. The latter is planned to being addressed in a different - // PR. auto const logScopeGuard = LogContext::Accessor::ScopedValue(std::move(logContextValues)); diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 21be339ec405..ac24de153756 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -265,6 +265,7 @@ class RestHandler : public std::enable_shared_from_this { RequestLane _lane; + // TODO we don't need the member any longer std::shared_ptr _logContextScopeValues; protected: diff --git a/arangod/RestHandler/RestVocbaseBaseHandler.h b/arangod/RestHandler/RestVocbaseBaseHandler.h index b26d6195c5ff..75efcc2c1164 100644 --- a/arangod/RestHandler/RestVocbaseBaseHandler.h +++ b/arangod/RestHandler/RestVocbaseBaseHandler.h @@ -206,6 +206,7 @@ class RestVocbaseBaseHandler : public RestBaseHandler { TRI_vocbase_t& _vocbase; private: + // TODO we don't need the member any longer std::shared_ptr _scopeVocbaseValues; }; From fd0981c81267db2585d724078dc34470e3a6ca40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 15:45:14 +0200 Subject: [PATCH 24/35] clang-format --- arangod/Aql/RestAqlHandler.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index da7f08402a78..944e0fb1f3a8 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -151,8 +151,8 @@ futures::Future RestAqlHandler::setupClusterQuery() { } auto queryIdValue = LogContext::makeValue() - .with(clusterQueryId) - .share(); + .with(clusterQueryId) + .share(); auto const logContextScopeGuard = ScopedValue{std::move(queryIdValue)}; VPackSlice lockInfoSlice = querySlice.get("lockInfo"); @@ -406,9 +406,8 @@ auto RestAqlHandler::useQuery(std::string const& operation, co_return; } - auto queryIdValue = LogContext::makeValue() - .with(idString) - .share(); + auto queryIdValue = + LogContext::makeValue().with(idString).share(); auto const logContextScopeGuard = ScopedValue{std::move(queryIdValue)}; if (!_engine) { // the PUT verb From ab4445c920759adc3faf12c8cdeb810d88c8a264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 16:16:02 +0200 Subject: [PATCH 25/35] Renamed SuspensionSemaphore to -Counter --- arangod/GeneralServer/RestHandler.cpp | 2 +- arangod/GeneralServer/RestHandler.h | 12 +++++------ lib/Async/CMakeLists.txt | 6 ++++-- ...pensionSemaphore.h => SuspensionCounter.h} | 21 ++++++++++++------- ...ionSemaphore.cpp => SuspensionCounter.cpp} | 2 ++ 5 files changed, 26 insertions(+), 17 deletions(-) rename lib/Async/include/Async/{SuspensionSemaphore.h => SuspensionCounter.h} (73%) rename lib/Async/src/{SuspensionSemaphore.cpp => SuspensionCounter.cpp} (95%) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 8fd58bf7d919..b9a93589e222 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -496,7 +496,7 @@ void RestHandler::shutdownExecute(bool isFinalized) noexcept {} /// For older RestHandlers, that implement execute() and continueExecute() with /// WAITING instead of executeAsync(). Calling wakeupHandler() will continue the /// execution by calling continueExecute(). -bool RestHandler::wakeupHandler() { return _suspensionSemaphore.notify(); } +bool RestHandler::wakeupHandler() { return _suspensionCounter.notify(); } auto RestHandler::executeEngine() -> async { DTRACE_PROBE1(arangod, RestHandlerExecuteEngine, this); diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index ac24de153756..df6e5463d116 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -23,7 +23,7 @@ #pragma once -#include "Async/SuspensionSemaphore.h" +#include "Async/SuspensionCounter.h" #include "Async/async.h" #include "Basics/ResultT.h" #include "Futures/Unit.h" @@ -204,8 +204,8 @@ class RestHandler : public std::enable_shared_from_this { mutable std::mutex _executionMutex; protected: - // TODO Move this in a separate header, side-by-side with SuspensionSemaphore? - // Note: _suspensionSemaphore.notify() must be called for this to resume. + // TODO Move this in a separate header, side-by-side with SuspensionCounter? + // Note: _suspensionCounter.notify() must be called for this to resume. // RestHandler::wakeupHandler() does that, and can be called e.g. by the // SharedQueryState's wakeup handler (for AQL-related code). template @@ -219,7 +219,7 @@ class RestHandler : public std::enable_shared_from_this { while (state == RestStatus::WAITING) { // Get the number of wakeups. We call fun() up to that many // times before suspending again. - auto n = co_await _suspensionSemaphore.await(); + auto n = co_await _suspensionCounter.await(); for (auto i = 0; i < n && state == RestStatus::WAITING; ++i) { state = fun(); } @@ -238,7 +238,7 @@ class RestHandler : public std::enable_shared_from_this { while (!res.has_value()) { // Get the number of wakeups. We call fun() up to that many // times before suspending again. - auto n = co_await _suspensionSemaphore.await(); + auto n = co_await _suspensionCounter.await(); for (auto i = 0; i < n && !res.has_value(); ++i) { res = fun(); } @@ -247,7 +247,7 @@ class RestHandler : public std::enable_shared_from_this { } private: - SuspensionSemaphore _suspensionSemaphore; + SuspensionCounter _suspensionCounter; std::function _sendResponseCallback; diff --git a/lib/Async/CMakeLists.txt b/lib/Async/CMakeLists.txt index f7e7471d050b..2348d430b8e1 100644 --- a/lib/Async/CMakeLists.txt +++ b/lib/Async/CMakeLists.txt @@ -9,12 +9,14 @@ add_library(arango_async STATIC include/Async/async.h include/Async/coro-utils.h include/Async/expected.h - include/Async/SuspensionSemaphore.h - src/SuspensionSemaphore.cpp + include/Async/SuspensionCounter.h + src/SuspensionCounter.cpp ) target_include_directories(arango_async INTERFACE include) +target_include_directories(arango_async PRIVATE + include) target_link_libraries(arango_async INTERFACE arango_async_registry diff --git a/lib/Async/include/Async/SuspensionSemaphore.h b/lib/Async/include/Async/SuspensionCounter.h similarity index 73% rename from lib/Async/include/Async/SuspensionSemaphore.h rename to lib/Async/include/Async/SuspensionCounter.h index 4ba28ddc6189..6d1ceeb5a490 100644 --- a/lib/Async/include/Async/SuspensionSemaphore.h +++ b/lib/Async/include/Async/SuspensionCounter.h @@ -31,9 +31,14 @@ namespace arangodb { class Scheduler; -// SuspensionSemaphore::await() returns an awaitable that suspends until -// notify() is called, and which returns the number of notifies. -struct SuspensionSemaphore { +// SuspensionCounter::await() returns an awaitable that suspends until +// notify() is called, and which in turn returns the number of notifies that +// happened while it was suspended. +// This is useful to connect a callee using WAITING for asynchronous execution +// with a caller that is a coroutine. +// The callee can be instructed to call notify() when it is done, and the caller +// can await the result of notify(). +struct SuspensionCounter { // returns true if still suspended bool notify() { auto counter = _counter.load(); @@ -59,18 +64,18 @@ struct SuspensionSemaphore { auto await() { struct Awaitable { bool await_ready() const noexcept { - return _semaphore->_counter.load(std::memory_order_relaxed) > 0; + return _suspensionCounter->_counter.load(std::memory_order_relaxed) > 0; } [[nodiscard]] std::int64_t await_resume() const noexcept { - return _semaphore->_counter.exchange(0); + return _suspensionCounter->_counter.exchange(0); } bool await_suspend(std::coroutine_handle<> c) { - _semaphore->_c = c; + _suspensionCounter->_c = c; auto counter = std::int64_t{}; - return _semaphore->_counter.compare_exchange_strong(counter, -1); + return _suspensionCounter->_counter.compare_exchange_strong(counter, -1); } - SuspensionSemaphore* _semaphore; + SuspensionCounter* _suspensionCounter; }; return Awaitable{this}; diff --git a/lib/Async/src/SuspensionSemaphore.cpp b/lib/Async/src/SuspensionCounter.cpp similarity index 95% rename from lib/Async/src/SuspensionSemaphore.cpp rename to lib/Async/src/SuspensionCounter.cpp index c00ab4b3e048..0b79cfdad593 100644 --- a/lib/Async/src/SuspensionSemaphore.cpp +++ b/lib/Async/src/SuspensionCounter.cpp @@ -19,3 +19,5 @@ /// /// @author Tobias Gödderz //////////////////////////////////////////////////////////////////////////////// + +#include "Async/SuspensionCounter.h" From 651193f2838889a9d60eb2037a7906f785e65045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 16:25:19 +0200 Subject: [PATCH 26/35] Updated comments and added [[deprecated]] attrs --- arangod/GeneralServer/RestHandler.cpp | 7 ++++--- arangod/GeneralServer/RestHandler.h | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index b9a93589e222..5f59c8722299 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -493,9 +493,8 @@ auto RestHandler::prepareExecute(bool isContinue) void RestHandler::shutdownExecute(bool isFinalized) noexcept {} -/// For older RestHandlers, that implement execute() and continueExecute() with -/// WAITING instead of executeAsync(). Calling wakeupHandler() will continue the -/// execution by calling continueExecute(). +// Compatability function for old-style code that uses the +// WAITING/wakeupHandler scheme for async execution. bool RestHandler::wakeupHandler() { return _suspensionCounter.notify(); } auto RestHandler::executeEngine() -> async { @@ -707,6 +706,8 @@ void RestHandler::resetResponse(rest::ResponseCode code) { futures::Future RestHandler::executeAsync() { auto state = execute(); + // After ensuring that no execute() implementation still returns WAITING, + // this can be removed. if (state == RestStatus::WAITING) { co_await waitingFunToCoro( std::bind(&std::decay_t::continueExecute, this)); diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index df6e5463d116..198004299482 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -111,7 +111,7 @@ class RestHandler : public std::enable_shared_from_this { std::function responseCallback); /// Continue execution of a suspended (via WAITING) rest handler state machine - bool wakeupHandler(); + [[deprecated]] bool wakeupHandler(); /// @brief forwards the request to the appropriate server futures::Future forwardRequest(bool& forwarded); @@ -131,7 +131,8 @@ class RestHandler : public std::enable_shared_from_this { -> std::vector>; virtual RestStatus execute(); virtual futures::Future executeAsync(); - virtual RestStatus continueExecute() { return RestStatus::DONE; } + // No longer used + [[deprecated]] static RestStatus continueExecute() { return RestStatus::DONE; } virtual void shutdownExecute(bool isFinalized) noexcept; // you might need to implement this in your handler From a0259e05f92200698fe78c85237dfc141d3b2a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 20 May 2025 16:49:32 +0200 Subject: [PATCH 27/35] Fix bind call to static method --- arangod/GeneralServer/RestHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 5f59c8722299..70df3b3740e7 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -710,7 +710,7 @@ futures::Future RestHandler::executeAsync() { // this can be removed. if (state == RestStatus::WAITING) { co_await waitingFunToCoro( - std::bind(&std::decay_t::continueExecute, this)); + std::bind(&std::decay_t::continueExecute)); } } From ce027cc4556ffc923e8f527a8b3b2f8b55f8fb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Mon, 26 May 2025 23:05:19 +0200 Subject: [PATCH 28/35] Moved locking from the RestAqlHandler to the ExecutionEngine --- arangod/Aql/ExecutionEngine.cpp | 20 +++++++++ arangod/Aql/ExecutionEngine.h | 5 +++ arangod/Aql/QueryContext.h | 6 --- arangod/Aql/RestAqlHandler.cpp | 79 +++++++++++++-------------------- arangod/Aql/RestAqlHandler.h | 4 +- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/arangod/Aql/ExecutionEngine.cpp b/arangod/Aql/ExecutionEngine.cpp index 614ad25a0d48..39732279bb05 100644 --- a/arangod/Aql/ExecutionEngine.cpp +++ b/arangod/Aql/ExecutionEngine.cpp @@ -653,6 +653,9 @@ struct DistributedQueryInstanciator final std::pair ExecutionEngine::initializeCursor( SharedAqlItemBlockPtr&& items, size_t pos) { + // TODO (Tobias) I'm not sure this lock is really necessary here, I put it + // here to keep similar behavior during a refactoring. + auto guard = getQuery().acquireLockGuard(); if (_query.killed()) { THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_KILLED); } @@ -667,6 +670,23 @@ std::pair ExecutionEngine::initializeCursor( return res; } +auto ExecutionEngine::executeRemoteCall(AqlCallStack const& executeCall, + std::string const& shardId) + -> std::tuple { + auto const rootNodeType = root()->getPlanNode()->getType(); + + // shardId is set IFF the root node is scatter or distribute + TRI_ASSERT(shardId.empty() != (rootNodeType == ExecutionNode::SCATTER || + rootNodeType == ExecutionNode::DISTRIBUTE)); + + auto guard = getQuery().acquireLockGuard(); + if (shardId.empty()) { + return execute(executeCall); + } else { + return executeForClient(executeCall, shardId); + } +} + auto ExecutionEngine::execute(AqlCallStack const& stack) -> std::tuple { if (_query.killed()) { diff --git a/arangod/Aql/ExecutionEngine.h b/arangod/Aql/ExecutionEngine.h index b6e0a88bb963..b59ec4eba3a3 100644 --- a/arangod/Aql/ExecutionEngine.h +++ b/arangod/Aql/ExecutionEngine.h @@ -100,11 +100,16 @@ class ExecutionEngine { std::pair initializeCursor( SharedAqlItemBlockPtr&& items, size_t pos); + auto executeRemoteCall(AqlCallStack const& stack, std::string const& shardId) + -> std::tuple; + auto execute(AqlCallStack const& stack) -> std::tuple; + private: auto executeForClient(AqlCallStack const& stack, std::string const& clientId) -> std::tuple; + public: /// @brief whether or not initializeCursor was called bool initializeCursorCalled() const; diff --git a/arangod/Aql/QueryContext.h b/arangod/Aql/QueryContext.h index a95e4cd53f93..d5abe8375e5d 100644 --- a/arangod/Aql/QueryContext.h +++ b/arangod/Aql/QueryContext.h @@ -124,12 +124,6 @@ class QueryContext { std::lock_guard acquireLockGuard() { return std::lock_guard{_mutex}; } - std::unique_lock acquireUniqueLock() { - return std::unique_lock{_mutex}; - } - std::unique_lock acquireUniqueLock(std::defer_lock_t) noexcept { - return std::unique_lock{_mutex, std::defer_lock}; - } void incHttpRequests(unsigned i) noexcept { _numRequests.fetch_add(i, std::memory_order_relaxed); diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index 944e0fb1f3a8..f421ade1038b 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -437,20 +437,8 @@ auto RestAqlHandler::useQuery(std::string const& operation, << " registryId=" << idString; } - auto guard = _engine->getQuery().acquireUniqueLock(std::defer_lock); try { - co_await waitingFunToCoro([&] { - // only keep the lock while actually running the query. Also, note that - // we must never keep a mutex locked while co_awaiting, as we might switch - // threads. - guard.lock(); - auto status = handleUseQuery(operation, querySlice); - if (status == RestStatus::WAITING) { - guard.unlock(); - } - return status; - }); - TRI_ASSERT(guard.owns_lock()); + co_await handleUseQuery(operation, querySlice); } catch (arangodb::basics::Exception const& ex) { generateError(rest::ResponseCode::SERVER_ERROR, ex.code(), ex.what()); } catch (std::exception const& ex) { @@ -718,8 +706,8 @@ auto AqlExecuteCall::fromVelocyPack(VPackSlice const slice) } // handle for useQuery -RestStatus RestAqlHandler::handleUseQuery(std::string const& operation, - VPackSlice querySlice) { +async RestAqlHandler::handleUseQuery(std::string const& operation, + VPackSlice querySlice) { VPackOptions const* opts = &VPackOptions::Defaults; if (_engine) { // might be destroyed on shutdown opts = &_engine->getQuery().vpackOptions(); @@ -733,41 +721,31 @@ RestStatus RestAqlHandler::handleUseQuery(std::string const& operation, auto maybeExecuteCall = AqlExecuteCall::fromVelocyPack(querySlice); if (maybeExecuteCall.fail()) { generateError(std::move(maybeExecuteCall).result()); - return RestStatus::DONE; + co_return; } TRI_IF_FAILURE("RestAqlHandler::getSome") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } auto& executeCall = maybeExecuteCall.get(); - auto items = SharedAqlItemBlockPtr{}; - auto skipped = SkipResult{}; - auto state = ExecutionState::HASMORE; - std::string const& shardId = _request->header(StaticStrings::AqlShardIdHeader); - auto const rootNodeType = _engine->root()->getPlanNode()->getType(); - - // shardId is set IFF the root node is scatter or distribute - TRI_ASSERT(shardId.empty() != (rootNodeType == ExecutionNode::SCATTER || - rootNodeType == ExecutionNode::DISTRIBUTE)); - - if (shardId.empty()) { - std::tie(state, skipped, items) = - _engine->execute(executeCall.callStack()); - } else { - std::tie(state, skipped, items) = - _engine->executeForClient(executeCall.callStack(), shardId); - } + auto [state, skipped, items] = co_await waitingFunToCoro( + [&]() -> std::optional> { + auto res = + _engine->executeRemoteCall(executeCall.callStack(), shardId); + if (std::get<0>(res) == ExecutionState::WAITING) { + TRI_IF_FAILURE("RestAqlHandler::killWhileWaiting") { + _queryRegistry->destroyQuery(_engine->engineId(), + TRI_ERROR_QUERY_KILLED); + } + return std::nullopt; + } + return res; + }); - if (state == ExecutionState::WAITING) { - TRI_IF_FAILURE("RestAqlHandler::killWhileWaiting") { - _queryRegistry->destroyQuery(_engine->engineId(), - TRI_ERROR_QUERY_KILLED); - } - return RestStatus::WAITING; - } TRI_IF_FAILURE("RestAqlHandler::killWhileWritingResult") { _queryRegistry->destroyQuery(_engine->engineId(), TRI_ERROR_QUERY_KILLED); } @@ -779,23 +757,26 @@ RestStatus RestAqlHandler::handleUseQuery(std::string const& operation, } else if (operation == "initializeCursor") { auto items = _engine->itemBlockManager().requestAndInitBlock( querySlice.get("items")); - auto tmpRes = _engine->initializeCursor(std::move(items), /*pos*/ 0); - if (tmpRes.first == ExecutionState::WAITING) { - return RestStatus::WAITING; - } - answerBuilder.add(StaticStrings::Error, VPackValue(tmpRes.second.fail())); - answerBuilder.add(StaticStrings::Code, - VPackValue(tmpRes.second.errorNumber())); + auto const res = co_await waitingFunToCoro([&]() -> std::optional { + auto tmpRes = _engine->initializeCursor(std::move(items), /*pos*/ 0); + if (tmpRes.first == ExecutionState::WAITING) { + return std::nullopt; + } else { + return std::move(tmpRes.second); + } + }); + answerBuilder.add(StaticStrings::Error, VPackValue(res.fail())); + answerBuilder.add(StaticStrings::Code, VPackValue(res.errorNumber())); } else { generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); - return RestStatus::DONE; + co_return; } answerBuilder.close(); generateResult(rest::ResponseCode::OK, std::move(answerBuffer), opts); - return RestStatus::DONE; + co_return; } // handle query finalization for all engines diff --git a/arangod/Aql/RestAqlHandler.h b/arangod/Aql/RestAqlHandler.h index f8637ad4ee1b..e481645b11a6 100644 --- a/arangod/Aql/RestAqlHandler.h +++ b/arangod/Aql/RestAqlHandler.h @@ -129,8 +129,8 @@ class RestAqlHandler : public RestVocbaseBaseHandler { [[nodiscard]] futures::Future setupClusterQuery(); // handle for useQuery - RestStatus handleUseQuery(std::string const&, - arangodb::velocypack::Slice querySlice); + async handleUseQuery(std::string const& operation, + arangodb::velocypack::Slice querySlice); // handle query finalization for all engines auto handleFinishQuery(std::string const& idString) -> async; From e635542de41cce0e77b5492e2d444f79c74a47ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Tue, 27 May 2025 08:55:51 +0200 Subject: [PATCH 29/35] clang-format --- arangod/Aql/ExecutionEngine.h | 2 +- arangod/GeneralServer/RestHandler.h | 4 +++- lib/Async/include/Async/SuspensionCounter.h | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arangod/Aql/ExecutionEngine.h b/arangod/Aql/ExecutionEngine.h index b59ec4eba3a3..01c6681af265 100644 --- a/arangod/Aql/ExecutionEngine.h +++ b/arangod/Aql/ExecutionEngine.h @@ -109,8 +109,8 @@ class ExecutionEngine { private: auto executeForClient(AqlCallStack const& stack, std::string const& clientId) -> std::tuple; - public: + public: /// @brief whether or not initializeCursor was called bool initializeCursorCalled() const; diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 198004299482..871329ca319b 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -132,7 +132,9 @@ class RestHandler : public std::enable_shared_from_this { virtual RestStatus execute(); virtual futures::Future executeAsync(); // No longer used - [[deprecated]] static RestStatus continueExecute() { return RestStatus::DONE; } + [[deprecated]] static RestStatus continueExecute() { + return RestStatus::DONE; + } virtual void shutdownExecute(bool isFinalized) noexcept; // you might need to implement this in your handler diff --git a/lib/Async/include/Async/SuspensionCounter.h b/lib/Async/include/Async/SuspensionCounter.h index 6d1ceeb5a490..f25f223b3fff 100644 --- a/lib/Async/include/Async/SuspensionCounter.h +++ b/lib/Async/include/Async/SuspensionCounter.h @@ -72,7 +72,8 @@ struct SuspensionCounter { bool await_suspend(std::coroutine_handle<> c) { _suspensionCounter->_c = c; auto counter = std::int64_t{}; - return _suspensionCounter->_counter.compare_exchange_strong(counter, -1); + return _suspensionCounter->_counter.compare_exchange_strong(counter, + -1); } SuspensionCounter* _suspensionCounter; From d5458f604e821e01c5288bf5aa8ec798dfd4200b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 28 May 2025 17:12:55 +0200 Subject: [PATCH 30/35] Initialize the custom type handler in the transaction Context immediately, not lazily, to prevent data races --- arangod/Aql/Query.cpp | 2 +- arangod/Transaction/Context.cpp | 25 ++++++++++--------- arangod/Transaction/Context.h | 13 +++++----- arangod/Transaction/SmartContext.cpp | 13 ---------- arangod/Transaction/SmartContext.h | 3 --- arangod/Transaction/V8Context.cpp | 14 ----------- arangod/Transaction/V8Context.h | 3 --- arangod/V8Server/v8-query.cpp | 4 +-- arangod/V8Server/v8-voccursor.cpp | 2 +- .../@arangodb/testutils/san-file-handler.js | 4 +-- 10 files changed, 26 insertions(+), 57 deletions(-) diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index 7303e864c7cd..fa3e050f7450 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -2001,7 +2001,7 @@ std::shared_ptr Query::newTrxContext() const { } velocypack::Options const& Query::vpackOptions() const { - return *(_transactionContext->getVPackOptions()); + return *_transactionContext->getVPackOptions(); } transaction::Methods& Query::trxForOptimization() { diff --git a/arangod/Transaction/Context.cpp b/arangod/Transaction/Context.cpp index 38baf2fc19fe..5f73b6a2fe49 100644 --- a/arangod/Transaction/Context.cpp +++ b/arangod/Transaction/Context.cpp @@ -68,9 +68,12 @@ struct CustomTypeHandler final : public VPackCustomTypeHandler { transaction::Context::Context(TRI_vocbase_t& vocbase, OperationOrigin operationOrigin) : _vocbase(vocbase), - _customTypeHandler(), + _resolver(std::make_unique(_vocbase)), + _customTypeHandler(createCustomTypeHandler(_vocbase, *_resolver)), _options(velocypack::Options::Defaults), - _operationOrigin(operationOrigin) {} + _operationOrigin(operationOrigin) { + _options.customTypeHandler = _customTypeHandler.get(); +} /// @brief destroy the context transaction::Context::~Context() { @@ -162,20 +165,18 @@ void transaction::Context::returnBuilder(VPackBuilder* builder) noexcept { } /// @brief get velocypack options with a custom type handler -VPackOptions* transaction::Context::getVPackOptions() { - if (_customTypeHandler == nullptr) { - // this modifies options! - orderCustomTypeHandler(); - } - +VPackOptions const* transaction::Context::getVPackOptions() const noexcept { return &_options; } +velocypack::CustomTypeHandler* transaction::Context::getCustomTypeHandler() const noexcept { + TRI_ASSERT(_customTypeHandler != nullptr); + return _customTypeHandler.get(); +} + /// @brief create a resolver -CollectionNameResolver const& transaction::Context::resolver() { - if (_resolver == nullptr) { - _resolver = std::make_unique(_vocbase); - } +CollectionNameResolver const& transaction::Context::resolver() const noexcept { + TRI_ASSERT(_resolver != nullptr); return *_resolver; } diff --git a/arangod/Transaction/Context.h b/arangod/Transaction/Context.h index 0bc7b0a49e3d..efb7844a75c9 100644 --- a/arangod/Transaction/Context.h +++ b/arangod/Transaction/Context.h @@ -88,10 +88,10 @@ class Context { TEST_VIRTUAL void returnBuilder(arangodb::velocypack::Builder*) noexcept; /// @brief get velocypack options with a custom type handler - TEST_VIRTUAL velocypack::Options* getVPackOptions(); + TEST_VIRTUAL velocypack::Options const* getVPackOptions() const noexcept; /// @brief get a custom type handler - virtual arangodb::velocypack::CustomTypeHandler* orderCustomTypeHandler() = 0; + velocypack::CustomTypeHandler* getCustomTypeHandler() const noexcept; /// @brief get transaction state, determine commit responsiblity virtual std::shared_ptr acquireState( @@ -130,7 +130,7 @@ class Context { /// @brief whether or not the transaction is embeddable virtual bool isEmbeddable() const = 0; - CollectionNameResolver const& resolver(); + CollectionNameResolver const& resolver() const noexcept; /// @brief unregister the transaction virtual void unregisterTransaction() noexcept = 0; @@ -153,6 +153,9 @@ class Context { transaction::Options const& options); TRI_vocbase_t& _vocbase; +private: + std::unique_ptr _resolver; +protected: std::unique_ptr _customTypeHandler; containers::SmallVector _builders; @@ -162,9 +165,7 @@ class Context { OperationOrigin _operationOrigin; - private: - std::unique_ptr _resolver; - +private: std::shared_ptr _counterGuard; struct { diff --git a/arangod/Transaction/SmartContext.cpp b/arangod/Transaction/SmartContext.cpp index 43ff4479786a..e82aa8a8a4fc 100644 --- a/arangod/Transaction/SmartContext.cpp +++ b/arangod/Transaction/SmartContext.cpp @@ -44,19 +44,6 @@ SmartContext::SmartContext(TRI_vocbase_t& vocbase, TransactionId globalId, SmartContext::~SmartContext() = default; -/// @brief order a custom type handler for the collection -velocypack::CustomTypeHandler* -transaction::SmartContext::orderCustomTypeHandler() { - if (_customTypeHandler == nullptr) { - _customTypeHandler = - transaction::Context::createCustomTypeHandler(_vocbase, resolver()); - _options.customTypeHandler = _customTypeHandler.get(); - } - - TRI_ASSERT(_customTypeHandler != nullptr); - return _customTypeHandler.get(); -} - TransactionId transaction::SmartContext::generateId() const { return _globalId; } diff --git a/arangod/Transaction/SmartContext.h b/arangod/Transaction/SmartContext.h index c456c382b816..f7157606fbf3 100644 --- a/arangod/Transaction/SmartContext.h +++ b/arangod/Transaction/SmartContext.h @@ -51,9 +51,6 @@ class SmartContext : public Context { /// @brief destroy the context ~SmartContext(); - /// @brief order a custom type handler - velocypack::CustomTypeHandler* orderCustomTypeHandler() override final; - /// @brief whether or not the transaction is embeddable bool isEmbeddable() const override final { return true; } diff --git a/arangod/Transaction/V8Context.cpp b/arangod/Transaction/V8Context.cpp index 2f285e3bbe81..9a35cf0b6290 100644 --- a/arangod/Transaction/V8Context.cpp +++ b/arangod/Transaction/V8Context.cpp @@ -53,20 +53,6 @@ transaction::V8Context::~V8Context() noexcept { } } -/// @brief order a custom type handler for the collection -VPackCustomTypeHandler* transaction::V8Context::orderCustomTypeHandler() { - if (_customTypeHandler == nullptr) { - _customTypeHandler = - transaction::Context::createCustomTypeHandler(_vocbase, resolver()); - _options.customTypeHandler = _customTypeHandler.get(); - } - - TRI_ASSERT(_customTypeHandler != nullptr); - TRI_ASSERT(_options.customTypeHandler != nullptr); - - return _customTypeHandler.get(); -} - /// @brief get transaction state, determine commit responsibility /*virtual*/ std::shared_ptr transaction::V8Context::acquireState(transaction::Options const& options, diff --git a/arangod/Transaction/V8Context.h b/arangod/Transaction/V8Context.h index 5e826d21ba24..4fe09bc6ac77 100644 --- a/arangod/Transaction/V8Context.h +++ b/arangod/Transaction/V8Context.h @@ -49,9 +49,6 @@ class V8Context final : public Context { /// @brief destroy the context ~V8Context() noexcept; - /// @brief order a custom type handler - velocypack::CustomTypeHandler* orderCustomTypeHandler() override final; - /// @brief get transaction state, determine commit responsiblity std::shared_ptr acquireState( transaction::Options const& options, bool& responsibleForCommit) override; diff --git a/arangod/V8Server/v8-query.cpp b/arangod/V8Server/v8-query.cpp index b449fc9b9e5d..50207515004c 100644 --- a/arangod/V8Server/v8-query.cpp +++ b/arangod/V8Server/v8-query.cpp @@ -234,7 +234,7 @@ static void JS_AllQuery(v8::FunctionCallbackInfo const& args) { // copy default options VPackOptions resultOptions = VPackOptions::Defaults; resultOptions.customTypeHandler = - transactionContext->orderCustomTypeHandler(); + transactionContext->getCustomTypeHandler(); VPackBuilder resultBuilder; resultBuilder.openArray(); @@ -344,7 +344,7 @@ static void JS_AnyQuery(v8::FunctionCallbackInfo const& args) { // copy default options VPackOptions resultOptions = VPackOptions::Defaults; resultOptions.customTypeHandler = - transactionContext->orderCustomTypeHandler(); + transactionContext->getCustomTypeHandler(); TRI_V8_RETURN(TRI_VPackToV8(isolate, doc.at(0), &resultOptions)); TRI_V8_TRY_CATCH_END } diff --git a/arangod/V8Server/v8-voccursor.cpp b/arangod/V8Server/v8-voccursor.cpp index e4a583572946..ac67f7e7f91b 100644 --- a/arangod/V8Server/v8-voccursor.cpp +++ b/arangod/V8Server/v8-voccursor.cpp @@ -155,7 +155,7 @@ static void JS_JsonCursor(v8::FunctionCallbackInfo const& args) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_CURSOR_NOT_FOUND); } - VPackOptions* opts = cursor->context()->getVPackOptions(); + VPackOptions const* opts = cursor->context()->getVPackOptions(); VPackBuilder builder(opts); builder.openObject(true); // conversion uses sequential iterator, no indexing Result r = cursor->dumpSync(builder); diff --git a/js/client/modules/@arangodb/testutils/san-file-handler.js b/js/client/modules/@arangodb/testutils/san-file-handler.js index 6b20bbb60ec1..5b210a735ebb 100644 --- a/js/client/modules/@arangodb/testutils/san-file-handler.js +++ b/js/client/modules/@arangodb/testutils/san-file-handler.js @@ -156,9 +156,9 @@ exports.registerOptions = function(optionsDefaults, optionsDocumentation, option tu.CopyIntoList(optionsDocumentation, [ ' SUT instrumented binaries', ' - `sanitizer`: if set the programs are run with enabled sanitizer', - ' - `isSan`: doubles oneTestTimeot value if set to true (for ASAN-related builds)', + ' - `isSan`: doubles oneTestTimeout value if set to true (for ASAN-related builds)', ' and need longer timeouts', - ' - `isCov`: doubles oneTestTimeot value if set to true', + ' - `isCov`: doubles oneTestTimeout value if set to true', '' ]); optionHandlers.push(function(options) { From 976cc7446ad20d50b4913c1f9dd0ee574c3c41b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 28 May 2025 17:13:39 +0200 Subject: [PATCH 31/35] Remove superfluous lock --- arangod/Aql/ExecutionEngine.cpp | 4 ---- arangod/Aql/QueryContext.h | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/arangod/Aql/ExecutionEngine.cpp b/arangod/Aql/ExecutionEngine.cpp index 39732279bb05..d8ef6fac855e 100644 --- a/arangod/Aql/ExecutionEngine.cpp +++ b/arangod/Aql/ExecutionEngine.cpp @@ -653,9 +653,6 @@ struct DistributedQueryInstanciator final std::pair ExecutionEngine::initializeCursor( SharedAqlItemBlockPtr&& items, size_t pos) { - // TODO (Tobias) I'm not sure this lock is really necessary here, I put it - // here to keep similar behavior during a refactoring. - auto guard = getQuery().acquireLockGuard(); if (_query.killed()) { THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_KILLED); } @@ -679,7 +676,6 @@ auto ExecutionEngine::executeRemoteCall(AqlCallStack const& executeCall, TRI_ASSERT(shardId.empty() != (rootNodeType == ExecutionNode::SCATTER || rootNodeType == ExecutionNode::DISTRIBUTE)); - auto guard = getQuery().acquireLockGuard(); if (shardId.empty()) { return execute(executeCall); } else { diff --git a/arangod/Aql/QueryContext.h b/arangod/Aql/QueryContext.h index d5abe8375e5d..c0d2abc372c1 100644 --- a/arangod/Aql/QueryContext.h +++ b/arangod/Aql/QueryContext.h @@ -119,12 +119,6 @@ class QueryContext { aql::Ast* ast() const; - /// @brief Acquire a lock_guard on the mutex to serialize concurrent snippet - /// execution - std::lock_guard acquireLockGuard() { - return std::lock_guard{_mutex}; - } - void incHttpRequests(unsigned i) noexcept { _numRequests.fetch_add(i, std::memory_order_relaxed); } @@ -205,12 +199,6 @@ class QueryContext { /// @brief number of HTTP requests executed by the query std::atomic _numRequests; - - /// @brief this mutex is used to serialize execution of potentially concurrent - /// snippets as a result of using parallel gather. - /// In the future we might want to consider using an rwlock instead so that - /// read-only snippets can actually run concurrently. - std::mutex _mutex; }; } // namespace aql From 2af7d8328d10f111c9f173989e8e1ec6b743b77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 28 May 2025 17:34:33 +0200 Subject: [PATCH 32/35] clang-format --- arangod/Transaction/Context.cpp | 3 ++- arangod/Transaction/Context.h | 8 +++++--- arangod/V8Server/v8-query.cpp | 6 ++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arangod/Transaction/Context.cpp b/arangod/Transaction/Context.cpp index 5f73b6a2fe49..3736a593341c 100644 --- a/arangod/Transaction/Context.cpp +++ b/arangod/Transaction/Context.cpp @@ -169,7 +169,8 @@ VPackOptions const* transaction::Context::getVPackOptions() const noexcept { return &_options; } -velocypack::CustomTypeHandler* transaction::Context::getCustomTypeHandler() const noexcept { +velocypack::CustomTypeHandler* transaction::Context::getCustomTypeHandler() + const noexcept { TRI_ASSERT(_customTypeHandler != nullptr); return _customTypeHandler.get(); } diff --git a/arangod/Transaction/Context.h b/arangod/Transaction/Context.h index efb7844a75c9..10e3fcb89ffc 100644 --- a/arangod/Transaction/Context.h +++ b/arangod/Transaction/Context.h @@ -153,9 +153,11 @@ class Context { transaction::Options const& options); TRI_vocbase_t& _vocbase; -private: + + private: std::unique_ptr _resolver; -protected: + + protected: std::unique_ptr _customTypeHandler; containers::SmallVector _builders; @@ -165,7 +167,7 @@ class Context { OperationOrigin _operationOrigin; -private: + private: std::shared_ptr _counterGuard; struct { diff --git a/arangod/V8Server/v8-query.cpp b/arangod/V8Server/v8-query.cpp index 50207515004c..96adc521b268 100644 --- a/arangod/V8Server/v8-query.cpp +++ b/arangod/V8Server/v8-query.cpp @@ -233,8 +233,7 @@ static void JS_AllQuery(v8::FunctionCallbackInfo const& args) { // copy default options VPackOptions resultOptions = VPackOptions::Defaults; - resultOptions.customTypeHandler = - transactionContext->getCustomTypeHandler(); + resultOptions.customTypeHandler = transactionContext->getCustomTypeHandler(); VPackBuilder resultBuilder; resultBuilder.openArray(); @@ -343,8 +342,7 @@ static void JS_AnyQuery(v8::FunctionCallbackInfo const& args) { // copy default options VPackOptions resultOptions = VPackOptions::Defaults; - resultOptions.customTypeHandler = - transactionContext->getCustomTypeHandler(); + resultOptions.customTypeHandler = transactionContext->getCustomTypeHandler(); TRI_V8_RETURN(TRI_VPackToV8(isolate, doc.at(0), &resultOptions)); TRI_V8_TRY_CATCH_END } From 037e284f85744d24b5b2cb59e0b15be476f379c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Wed, 28 May 2025 21:57:41 +0200 Subject: [PATCH 33/35] Fixed compile error --- tests/Aql/Executor/EnumerateCollectionExecutorTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Aql/Executor/EnumerateCollectionExecutorTest.cpp b/tests/Aql/Executor/EnumerateCollectionExecutorTest.cpp index 7a0fd81a1d2e..2320d725c850 100644 --- a/tests/Aql/Executor/EnumerateCollectionExecutorTest.cpp +++ b/tests/Aql/Executor/EnumerateCollectionExecutorTest.cpp @@ -332,8 +332,8 @@ class EnumerateCollectionExecutorTestProduce } // insert amount of documents into the vocbase - VPackOptions* insertDocuments(size_t amount, - std::vector& queryResults) { + VPackOptions const* insertDocuments(size_t amount, + std::vector& queryResults) { // TODO: Can be optimized to not use AQL INSERT (trx object directly // instead) std::string insertQuery = From 5ff87263a7fb9c0f902e7248b02f48e364f89d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Thu, 29 May 2025 11:18:13 +0200 Subject: [PATCH 34/35] Revert "Remove superfluous lock" This reverts commit 976cc7446ad20d50b4913c1f9dd0ee574c3c41b3. --- arangod/Aql/ExecutionEngine.cpp | 4 ++++ arangod/Aql/QueryContext.h | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/arangod/Aql/ExecutionEngine.cpp b/arangod/Aql/ExecutionEngine.cpp index d8ef6fac855e..39732279bb05 100644 --- a/arangod/Aql/ExecutionEngine.cpp +++ b/arangod/Aql/ExecutionEngine.cpp @@ -653,6 +653,9 @@ struct DistributedQueryInstanciator final std::pair ExecutionEngine::initializeCursor( SharedAqlItemBlockPtr&& items, size_t pos) { + // TODO (Tobias) I'm not sure this lock is really necessary here, I put it + // here to keep similar behavior during a refactoring. + auto guard = getQuery().acquireLockGuard(); if (_query.killed()) { THROW_ARANGO_EXCEPTION(TRI_ERROR_QUERY_KILLED); } @@ -676,6 +679,7 @@ auto ExecutionEngine::executeRemoteCall(AqlCallStack const& executeCall, TRI_ASSERT(shardId.empty() != (rootNodeType == ExecutionNode::SCATTER || rootNodeType == ExecutionNode::DISTRIBUTE)); + auto guard = getQuery().acquireLockGuard(); if (shardId.empty()) { return execute(executeCall); } else { diff --git a/arangod/Aql/QueryContext.h b/arangod/Aql/QueryContext.h index c0d2abc372c1..d5abe8375e5d 100644 --- a/arangod/Aql/QueryContext.h +++ b/arangod/Aql/QueryContext.h @@ -119,6 +119,12 @@ class QueryContext { aql::Ast* ast() const; + /// @brief Acquire a lock_guard on the mutex to serialize concurrent snippet + /// execution + std::lock_guard acquireLockGuard() { + return std::lock_guard{_mutex}; + } + void incHttpRequests(unsigned i) noexcept { _numRequests.fetch_add(i, std::memory_order_relaxed); } @@ -199,6 +205,12 @@ class QueryContext { /// @brief number of HTTP requests executed by the query std::atomic _numRequests; + + /// @brief this mutex is used to serialize execution of potentially concurrent + /// snippets as a result of using parallel gather. + /// In the future we might want to consider using an rwlock instead so that + /// read-only snippets can actually run concurrently. + std::mutex _mutex; }; } // namespace aql From a9d523056186c080bcafda0d763d43dab4f44266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=B6dderz?= Date: Thu, 29 May 2025 11:36:55 +0200 Subject: [PATCH 35/35] Handle exceptions, fix compile error --- arangod/Aql/RestAqlHandler.cpp | 9 ++---- arangod/Aql/RestAqlHandler.h | 6 ++-- arangod/GeneralServer/RestHandler.cpp | 32 ++++++++++++++++--- arangod/GeneralServer/RestHandler.h | 3 +- .../RestHandler/RestSimpleQueryHandler.cpp | 13 +++----- lib/Async/CMakeLists.txt | 4 +-- 6 files changed, 40 insertions(+), 27 deletions(-) diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index f421ade1038b..0da49a66e309 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -68,8 +68,6 @@ RestAqlHandler::RestAqlHandler(ArangodServer& server, GeneralRequest* request, TRI_ASSERT(_queryRegistry != nullptr); } -RestAqlHandler::~RestAqlHandler() {} - // POST method for /_api/aql/setup (internal) // Only available on DBServers in the Cluster. // This route sets-up all the query engines required @@ -509,8 +507,7 @@ auto RestAqlHandler::executeAsync() -> futures::Future { co_return; } if (suffixes[0] == "finish") { - co_await handleFinishQuery(suffixes[1]); - co_return; + co_return co_await handleFinishQuery(suffixes[1]); } generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_QUERY_NOT_FOUND, @@ -706,8 +703,8 @@ auto AqlExecuteCall::fromVelocyPack(VPackSlice const slice) } // handle for useQuery -async RestAqlHandler::handleUseQuery(std::string const& operation, - VPackSlice querySlice) { +auto RestAqlHandler::handleUseQuery(std::string const& operation, + VPackSlice querySlice) -> async { VPackOptions const* opts = &VPackOptions::Defaults; if (_engine) { // might be destroyed on shutdown opts = &_engine->getQuery().vpackOptions(); diff --git a/arangod/Aql/RestAqlHandler.h b/arangod/Aql/RestAqlHandler.h index e481645b11a6..e1c79d38e666 100644 --- a/arangod/Aql/RestAqlHandler.h +++ b/arangod/Aql/RestAqlHandler.h @@ -46,7 +46,7 @@ class RestAqlHandler : public RestVocbaseBaseHandler { public: RestAqlHandler(ArangodServer&, GeneralRequest*, GeneralResponse*, QueryRegistry*); - ~RestAqlHandler(); + ~RestAqlHandler() override = default; char const* name() const override final { return "RestAqlHandler"; } RequestLane lane() const override final; @@ -129,8 +129,8 @@ class RestAqlHandler : public RestVocbaseBaseHandler { [[nodiscard]] futures::Future setupClusterQuery(); // handle for useQuery - async handleUseQuery(std::string const& operation, - arangodb::velocypack::Slice querySlice); + auto handleUseQuery(std::string const& operation, + velocypack::Slice querySlice) -> async; // handle query finalization for all engines auto handleFinishQuery(std::string const& idString) -> async; diff --git a/arangod/GeneralServer/RestHandler.cpp b/arangod/GeneralServer/RestHandler.cpp index 70df3b3740e7..4ab15e5768cc 100644 --- a/arangod/GeneralServer/RestHandler.cpp +++ b/arangod/GeneralServer/RestHandler.cpp @@ -723,9 +723,33 @@ void RestHandler::runHandler( std::function responseCallback) { _sendResponseCallback = std::move(responseCallback); - runHandlerStateMachine().thenFinal( - [self = shared_from_this()](auto&& tryResult) noexcept { - // TODO handle exceptions - std::move(tryResult).throwIfFailed(); + runHandlerStateMachine(). + // Swallow all exceptions. It would be desirable to guarantee no unhandled + // exceptions reach this point; so let's at least die in maintainer mode + // for now. + thenFinal([self = shared_from_this()](auto&& tryResult) noexcept { + try { + std::move(tryResult).throwIfFailed(); + } catch (basics::Exception const& exception) { + LOG_TOPIC("e0b25", ERR, Logger::FIXME) + << "Uncaught exception in RestHandler " << self->name() << ": " + << "[" << exception.code() << "] " << exception.message() + << " (at " << exception.location() << ")"; + TRI_ASSERT(false) + << "Uncaught exception in RestHandler " << self->name() << ": " + << "[" << exception.code() << "] " << exception.message() + << " (at " << exception.location() << ")"; + } catch (std::exception const& exception) { + LOG_TOPIC("989d1", ERR, Logger::FIXME) + << "Uncaught exception in RestHandler " << self->name() << ": " + << exception.what(); + TRI_ASSERT(false) << "Uncaught exception in RestHandler " + << self->name() << ": " << exception.what(); + } catch (...) { + LOG_TOPIC("99c0c", ERR, Logger::FIXME) + << "Uncaught exception in RestHandler " << self->name() << "."; + TRI_ASSERT(false) + << "Uncaught exception in RestHandler " << self->name() << "."; + } }); } diff --git a/arangod/GeneralServer/RestHandler.h b/arangod/GeneralServer/RestHandler.h index 871329ca319b..2507250a0690 100644 --- a/arangod/GeneralServer/RestHandler.h +++ b/arangod/GeneralServer/RestHandler.h @@ -107,8 +107,7 @@ class RestHandler : public std::enable_shared_from_this { void setIsAsyncRequest() noexcept { _isAsyncRequest = true; } /// Execute the rest handler state machine - virtual void runHandler( - std::function responseCallback); + void runHandler(std::function responseCallback); /// Continue execution of a suspended (via WAITING) rest handler state machine [[deprecated]] bool wakeupHandler(); diff --git a/arangod/RestHandler/RestSimpleQueryHandler.cpp b/arangod/RestHandler/RestSimpleQueryHandler.cpp index e0197d655750..a489db6e9dac 100644 --- a/arangod/RestHandler/RestSimpleQueryHandler.cpp +++ b/arangod/RestHandler/RestSimpleQueryHandler.cpp @@ -46,21 +46,17 @@ auto RestSimpleQueryHandler::executeAsync() -> futures::Future { // extract the sub-request type auto const type = _request->requestType(); - // TODO remove the RestStatus return value of all callees here std::string const& prefix = _request->requestPath(); if (type == rest::RequestType::PUT) { if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_PATH) { // all query - co_await allDocuments(); - co_return; + co_return co_await allDocuments(); } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_ALL_KEYS_PATH) { // all-keys query - co_await allDocumentKeys(); - co_return; + co_return co_await allDocumentKeys(); } else if (prefix == RestVocbaseBaseHandler::SIMPLE_QUERY_BY_EXAMPLE) { // by-example query - co_await byExample(); - co_return; + co_return co_await byExample(); } } @@ -152,9 +148,8 @@ async RestSimpleQueryHandler::allDocuments() { data.close(); // now run the actual query and handle the result - co_await registerQueryOrCursor( + co_return co_await registerQueryOrCursor( data.slice(), transaction::OperationOriginREST{"fetching all documents"}); - co_return; } ////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Async/CMakeLists.txt b/lib/Async/CMakeLists.txt index 2348d430b8e1..2a09df228830 100644 --- a/lib/Async/CMakeLists.txt +++ b/lib/Async/CMakeLists.txt @@ -13,9 +13,7 @@ add_library(arango_async STATIC src/SuspensionCounter.cpp ) -target_include_directories(arango_async INTERFACE - include) -target_include_directories(arango_async PRIVATE +target_include_directories(arango_async PUBLIC include) target_link_libraries(arango_async INTERFACE