mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 03:45:50 +00:00 
			
		
		
		
	
							
								
								
									
										1
									
								
								CMake/deps/gbench.cmake
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								CMake/deps/gbench.cmake
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
find_package (benchmark REQUIRED)
 | 
			
		||||
@@ -6,6 +6,7 @@ project(clio)
 | 
			
		||||
# ========================================================================== #
 | 
			
		||||
option (verbose   "Verbose build"                                     FALSE)
 | 
			
		||||
option (tests     "Build tests"                                       FALSE)
 | 
			
		||||
option (benchmark "Build benchmarks"                                  FALSE)
 | 
			
		||||
option (docs      "Generate doxygen docs"                             FALSE)
 | 
			
		||||
option (coverage  "Build test coverage report"                        FALSE)
 | 
			
		||||
option (packaging "Create distribution packages"                      FALSE)
 | 
			
		||||
@@ -202,6 +203,7 @@ if (tests)
 | 
			
		||||
    unittests/util/TxUtilTests.cpp
 | 
			
		||||
    unittests/util/StringUtils.cpp
 | 
			
		||||
    unittests/util/LedgerUtilsTests.cpp
 | 
			
		||||
    ## Prometheus support
 | 
			
		||||
    unittests/util/prometheus/CounterTests.cpp
 | 
			
		||||
    unittests/util/prometheus/GaugeTests.cpp
 | 
			
		||||
    unittests/util/prometheus/HistogramTests.cpp
 | 
			
		||||
@@ -210,6 +212,13 @@ if (tests)
 | 
			
		||||
    unittests/util/prometheus/MetricBuilderTests.cpp
 | 
			
		||||
    unittests/util/prometheus/MetricsFamilyTests.cpp
 | 
			
		||||
    unittests/util/prometheus/OStreamTests.cpp
 | 
			
		||||
    ## Async framework
 | 
			
		||||
    unittests/util/async/AnyExecutionContextTests.cpp
 | 
			
		||||
    unittests/util/async/AnyStrandTests.cpp
 | 
			
		||||
    unittests/util/async/AnyOperationTests.cpp
 | 
			
		||||
    unittests/util/async/AnyStopTokenTests.cpp
 | 
			
		||||
    unittests/util/async/AsyncExecutionContextTests.cpp
 | 
			
		||||
    ## Requests framework
 | 
			
		||||
    unittests/util/requests/RequestBuilderTests.cpp
 | 
			
		||||
    unittests/util/requests/SslContextTests.cpp
 | 
			
		||||
    unittests/util/requests/WsConnectionTests.cpp
 | 
			
		||||
@@ -330,6 +339,23 @@ if (tests)
 | 
			
		||||
  endif ()
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
# Benchmarks
 | 
			
		||||
if (benchmark)
 | 
			
		||||
  set (BENCH_TARGET clio_benchmarks)
 | 
			
		||||
  add_executable (${BENCH_TARGET}
 | 
			
		||||
    # Common
 | 
			
		||||
    benchmarks/Main.cpp
 | 
			
		||||
    benchmarks/Playground.cpp
 | 
			
		||||
    # ExecutionContext
 | 
			
		||||
    benchmarks/util/async/ExecutionContextBenchmarks.cpp
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  include (CMake/deps/gbench.cmake)
 | 
			
		||||
 | 
			
		||||
  target_include_directories (${BENCH_TARGET} PRIVATE benchmarks)
 | 
			
		||||
  target_link_libraries (${BENCH_TARGET} PUBLIC clio benchmark::benchmark_main)
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
# Enable selected sanitizer if enabled via `san`
 | 
			
		||||
if (san)
 | 
			
		||||
  target_compile_options (clio
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								benchmarks/Main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								benchmarks/Main.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2023, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include <benchmark/benchmark.h>
 | 
			
		||||
 | 
			
		||||
BENCHMARK_MAIN();
 | 
			
		||||
							
								
								
									
										45
									
								
								benchmarks/Playground.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								benchmarks/Playground.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2023, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Use this file for temporary benchmarks and implementations.
 | 
			
		||||
 * Usage example:
 | 
			
		||||
 * ```
 | 
			
		||||
 * ./clio_benchmarks
 | 
			
		||||
 *     --benchmark_time_unit=ms
 | 
			
		||||
 *     --benchmark_repetitions=10
 | 
			
		||||
 *     --benchmark_display_aggregates_only=true
 | 
			
		||||
 *     --benchmark_min_time=1x
 | 
			
		||||
 *     --benchmark_filter="Playground"
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * Note: Please don't push your temporary work to the repo.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// #include <benchmark/benchmark.h>
 | 
			
		||||
 | 
			
		||||
// static void
 | 
			
		||||
// benchmarkPlaygroundTest1(benchmark::State& state)
 | 
			
		||||
// {
 | 
			
		||||
//     for (auto _ : state) {
 | 
			
		||||
//         // ...
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// BENCHMARK(benchmarkPlaygroundTest1);
 | 
			
		||||
							
								
								
									
										268
									
								
								benchmarks/util/async/ExecutionContextBenchmarks.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								benchmarks/util/async/ExecutionContextBenchmarks.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,268 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "etl/ETLHelpers.hpp"
 | 
			
		||||
#include "util/Random.hpp"
 | 
			
		||||
#include "util/async/AnyExecutionContext.hpp"
 | 
			
		||||
#include "util/async/AnyOperation.hpp"
 | 
			
		||||
#include "util/async/context/BasicExecutionContext.hpp"
 | 
			
		||||
#include "util/async/context/SyncExecutionContext.hpp"
 | 
			
		||||
 | 
			
		||||
#include <benchmark/benchmark.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <latch>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
using namespace util;
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
 | 
			
		||||
class TestThread {
 | 
			
		||||
    std::vector<std::thread> threads_;
 | 
			
		||||
    etl::ThreadSafeQueue<std::optional<uint64_t>> q_;
 | 
			
		||||
    etl::ThreadSafeQueue<uint64_t> res_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    TestThread(std::vector<uint64_t> const& data) : q_(data.size()), res_(data.size())
 | 
			
		||||
    {
 | 
			
		||||
        for (auto el : data)
 | 
			
		||||
            q_.push(el);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~TestThread()
 | 
			
		||||
    {
 | 
			
		||||
        for (auto& t : threads_) {
 | 
			
		||||
            if (t.joinable())
 | 
			
		||||
                t.join();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    run(std::size_t numThreads)
 | 
			
		||||
    {
 | 
			
		||||
        std::latch completion{numThreads};
 | 
			
		||||
        for (std::size_t i = 0; i < numThreads; ++i) {
 | 
			
		||||
            q_.push(std::nullopt);
 | 
			
		||||
            threads_.emplace_back([this, &completion]() { process(completion); });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        completion.wait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void
 | 
			
		||||
    process(std::latch& completion)
 | 
			
		||||
    {
 | 
			
		||||
        while (auto v = q_.pop()) {
 | 
			
		||||
            if (not v.has_value())
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            res_.push(v.value() * v.value());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        completion.count_down(1);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename CtxType>
 | 
			
		||||
class TestExecutionContextBatched {
 | 
			
		||||
    etl::ThreadSafeQueue<std::optional<uint64_t>> q_;
 | 
			
		||||
    etl::ThreadSafeQueue<uint64_t> res_;
 | 
			
		||||
    std::size_t batchSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    TestExecutionContextBatched(std::vector<uint64_t> const& data, std::size_t batchSize = 5000u)
 | 
			
		||||
        : q_(data.size()), res_(data.size()), batchSize_(batchSize)
 | 
			
		||||
    {
 | 
			
		||||
        for (auto el : data)
 | 
			
		||||
            q_.push(el);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    run(std::size_t numThreads)
 | 
			
		||||
    {
 | 
			
		||||
        using OpType = typename CtxType::template StoppableOperation<void>;
 | 
			
		||||
 | 
			
		||||
        CtxType ctx{numThreads};
 | 
			
		||||
        std::vector<OpType> operations;
 | 
			
		||||
 | 
			
		||||
        for (std::size_t i = 0; i < numThreads; ++i) {
 | 
			
		||||
            q_.push(std::nullopt);
 | 
			
		||||
 | 
			
		||||
            operations.push_back(ctx.execute(
 | 
			
		||||
                [this](auto stopRequested) {
 | 
			
		||||
                    bool hasMore = true;
 | 
			
		||||
                    auto doOne = [this] {
 | 
			
		||||
                        auto v = q_.pop();
 | 
			
		||||
                        if (not v.has_value())
 | 
			
		||||
                            return false;
 | 
			
		||||
 | 
			
		||||
                        res_.push(v.value() * v.value());
 | 
			
		||||
                        return true;
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    while (not stopRequested and hasMore) {
 | 
			
		||||
                        for (std::size_t i = 0; i < batchSize_ and hasMore; ++i)
 | 
			
		||||
                            hasMore = doOne();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                std::chrono::seconds{5}
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto& op : operations)
 | 
			
		||||
            op.wait();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename CtxType>
 | 
			
		||||
class TestAnyExecutionContextBatched {
 | 
			
		||||
    etl::ThreadSafeQueue<std::optional<uint64_t>> q_;
 | 
			
		||||
    etl::ThreadSafeQueue<uint64_t> res_;
 | 
			
		||||
    std::size_t batchSize_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    TestAnyExecutionContextBatched(std::vector<uint64_t> const& data, std::size_t batchSize = 5000u)
 | 
			
		||||
        : q_(data.size()), res_(data.size()), batchSize_(batchSize)
 | 
			
		||||
    {
 | 
			
		||||
        for (auto el : data)
 | 
			
		||||
            q_.push(el);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    run(std::size_t numThreads)
 | 
			
		||||
    {
 | 
			
		||||
        CtxType ctx{numThreads};
 | 
			
		||||
        AnyExecutionContext anyCtx{ctx};
 | 
			
		||||
        std::vector<AnyOperation<void>> operations;
 | 
			
		||||
 | 
			
		||||
        for (std::size_t i = 0; i < numThreads; ++i) {
 | 
			
		||||
            q_.push(std::nullopt);
 | 
			
		||||
 | 
			
		||||
            operations.push_back(anyCtx.execute(
 | 
			
		||||
                [this](auto stopRequested) {
 | 
			
		||||
                    bool hasMore = true;
 | 
			
		||||
                    auto doOne = [this] {
 | 
			
		||||
                        auto v = q_.pop();
 | 
			
		||||
                        if (not v.has_value())
 | 
			
		||||
                            return false;
 | 
			
		||||
 | 
			
		||||
                        res_.push(v.value() * v.value());
 | 
			
		||||
                        return true;
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    while (not stopRequested and hasMore) {
 | 
			
		||||
                        for (std::size_t i = 0; i < batchSize_ and hasMore; ++i)
 | 
			
		||||
                            hasMore = doOne();
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                std::chrono::seconds{5}
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (auto& op : operations)
 | 
			
		||||
            op.wait();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static auto
 | 
			
		||||
generateData()
 | 
			
		||||
{
 | 
			
		||||
    constexpr auto TOTAL = 10'000;
 | 
			
		||||
    std::vector<uint64_t> data;
 | 
			
		||||
    data.reserve(TOTAL);
 | 
			
		||||
    for (auto i = 0; i < TOTAL; ++i)
 | 
			
		||||
        data.push_back(util::Random::uniform(1, 100'000'000));
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
benchmarkThreads(benchmark::State& state)
 | 
			
		||||
{
 | 
			
		||||
    auto data = generateData();
 | 
			
		||||
    for (auto _ : state) {
 | 
			
		||||
        TestThread t{data};
 | 
			
		||||
        t.run(state.range(0));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename CtxType>
 | 
			
		||||
void
 | 
			
		||||
benchmarkExecutionContextBatched(benchmark::State& state)
 | 
			
		||||
{
 | 
			
		||||
    auto data = generateData();
 | 
			
		||||
    for (auto _ : state) {
 | 
			
		||||
        TestExecutionContextBatched<CtxType> t{data, state.range(1)};
 | 
			
		||||
        t.run(state.range(0));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename CtxType>
 | 
			
		||||
void
 | 
			
		||||
benchmarkAnyExecutionContextBatched(benchmark::State& state)
 | 
			
		||||
{
 | 
			
		||||
    auto data = generateData();
 | 
			
		||||
    for (auto _ : state) {
 | 
			
		||||
        TestAnyExecutionContextBatched<CtxType> t{data, state.range(1)};
 | 
			
		||||
        t.run(state.range(0));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Simplest implementation using async queues and std::thread
 | 
			
		||||
BENCHMARK(benchmarkThreads)->Arg(1)->Arg(2)->Arg(4)->Arg(8);
 | 
			
		||||
 | 
			
		||||
// Same implementation using each of the available execution contexts
 | 
			
		||||
BENCHMARK(benchmarkExecutionContextBatched<PoolExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
BENCHMARK(benchmarkExecutionContextBatched<CoroExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
BENCHMARK(benchmarkExecutionContextBatched<SyncExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
// Same implementations going thru AnyExecutionContext
 | 
			
		||||
BENCHMARK(benchmarkAnyExecutionContextBatched<PoolExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
BENCHMARK(benchmarkAnyExecutionContextBatched<CoroExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
BENCHMARK(benchmarkAnyExecutionContextBatched<SyncExecutionContext>)
 | 
			
		||||
    ->ArgsProduct({
 | 
			
		||||
        {1, 2, 4, 8},             // threads
 | 
			
		||||
        {500, 1000, 5000, 10000}  // batch size
 | 
			
		||||
    });
 | 
			
		||||
@@ -13,6 +13,7 @@ class Clio(ConanFile):
 | 
			
		||||
        'fPIC': [True, False],
 | 
			
		||||
        'verbose': [True, False],
 | 
			
		||||
        'tests': [True, False],     # build unit tests; create `clio_tests` binary
 | 
			
		||||
        'benchmark': [True, False], # build benchmarks; create `clio_benchmarks` binary
 | 
			
		||||
        'docs': [True, False],      # doxygen API docs; create custom target 'docs'
 | 
			
		||||
        'packaging': [True, False], # create distribution packages
 | 
			
		||||
        'coverage': [True, False],  # build for test coverage report; create custom target `clio_tests-ccov`
 | 
			
		||||
@@ -34,6 +35,7 @@ class Clio(ConanFile):
 | 
			
		||||
        'fPIC': True,
 | 
			
		||||
        'verbose': False,
 | 
			
		||||
        'tests': False,
 | 
			
		||||
        'benchmark': False,
 | 
			
		||||
        'packaging': False,
 | 
			
		||||
        'coverage': False,
 | 
			
		||||
        'lint': False,
 | 
			
		||||
@@ -60,6 +62,8 @@ class Clio(ConanFile):
 | 
			
		||||
    def requirements(self):
 | 
			
		||||
        if self.options.tests:
 | 
			
		||||
            self.requires('gtest/1.14.0')
 | 
			
		||||
        if self.options.benchmark:
 | 
			
		||||
            self.requires('benchmark/1.8.3')
 | 
			
		||||
 | 
			
		||||
    def configure(self):
 | 
			
		||||
        if self.settings.compiler == 'apple-clang':
 | 
			
		||||
 
 | 
			
		||||
@@ -206,6 +206,12 @@ public:
 | 
			
		||||
        cv_.notify_all();
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::size_t
 | 
			
		||||
    size() const
 | 
			
		||||
    {
 | 
			
		||||
        return queue_.size();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -207,8 +207,9 @@ public:
 | 
			
		||||
 | 
			
		||||
                setLastPublishTime();
 | 
			
		||||
                LOG(log_.info()) << "Published ledger " << std::to_string(lgrInfo.seq);
 | 
			
		||||
            } else
 | 
			
		||||
            } else {
 | 
			
		||||
                LOG(log_.info()) << "Skipping publishing ledger " << std::to_string(lgrInfo.seq);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // we track latest publish-requested seq, not necessarily already published
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,6 @@
 | 
			
		||||
// local to compilation unit loggers
 | 
			
		||||
namespace {
 | 
			
		||||
util::Logger gLog{"RPC"};
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
namespace rpc {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										263
									
								
								src/util/async/AnyExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								src/util/async/AnyExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,263 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/AnyOperation.hpp"
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/AnyStrand.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
#include "util/async/impl/ErasedOperation.hpp"
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A type-erased execution context
 | 
			
		||||
 */
 | 
			
		||||
class AnyExecutionContext {
 | 
			
		||||
public:
 | 
			
		||||
    template <typename CtxType>
 | 
			
		||||
        requires(not std::is_same_v<std::decay_t<CtxType>, AnyExecutionContext>)
 | 
			
		||||
    /* implicit */ AnyExecutionContext(CtxType&& ctx)
 | 
			
		||||
        : pimpl_{std::make_unique<Model<CtxType>>(std::forward<CtxType>(ctx))}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AnyExecutionContext() = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Execute a function on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @returns A unstoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWithoutStopToken auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn())>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(pimpl_->execute([fn = std::forward<decltype(fn)>(fn)]() -> impl::Any {
 | 
			
		||||
            if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                fn();
 | 
			
		||||
                return {};
 | 
			
		||||
            } else {
 | 
			
		||||
                return std::make_any<RetType>(fn());
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Execute a function on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @returns A stoppable operation that can be used to wait for the result
 | 
			
		||||
     *
 | 
			
		||||
     * @note The function is expected to take a stop token
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<AnyStopToken> auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>()))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(
 | 
			
		||||
            pimpl_->execute([fn = std::forward<decltype(fn)>(fn)](auto stopToken) -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Execute a function with a timeout
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @param timeout The timeout after which the function should be cancelled
 | 
			
		||||
     * @returns A stoppable operation that can be used to wait for the result
 | 
			
		||||
     *
 | 
			
		||||
     * @note The function is expected to take a stop token
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<AnyStopToken> auto&& fn, SomeStdDuration auto timeout)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>()))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(pimpl_->execute(
 | 
			
		||||
            [fn = std::forward<decltype(fn)>(fn)](auto stopToken) -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            std::chrono::duration_cast<std::chrono::milliseconds>(timeout)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule a function for execution
 | 
			
		||||
     *
 | 
			
		||||
     * @param delay The delay after which the function should be executed
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @returns A stoppable operation that can be used to wait for the result
 | 
			
		||||
     *
 | 
			
		||||
     * @note The function is expected to take a stop token
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    scheduleAfter(SomeStdDuration auto delay, SomeHandlerWith<AnyStopToken> auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>()))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(delay);
 | 
			
		||||
        return AnyOperation<RetType>(pimpl_->scheduleAfter(
 | 
			
		||||
            millis,
 | 
			
		||||
            [fn = std::forward<decltype(fn)>(fn)](auto stopToken) -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule a function for execution
 | 
			
		||||
     *
 | 
			
		||||
     * @param delay The delay after which the function should be executed
 | 
			
		||||
     * @param fn The function to execute
 | 
			
		||||
     * @returns A stoppable operation that can be used to wait for the result
 | 
			
		||||
     *
 | 
			
		||||
     * @note The function is expected to take a stop token and a boolean representing whether the scheduled operation
 | 
			
		||||
     * got cancelled
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    scheduleAfter(SomeStdDuration auto delay, SomeHandlerWith<AnyStopToken, bool> auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>(), true))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(delay);
 | 
			
		||||
        return AnyOperation<RetType>(pimpl_->scheduleAfter(
 | 
			
		||||
            millis,
 | 
			
		||||
            [fn = std::forward<decltype(fn)>(fn)](auto stopToken, auto cancelled) -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn(std::move(stopToken), cancelled);
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn(std::move(stopToken), cancelled));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Make a strand for this execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @return A strand for this execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @note The strand can be used similarly to the execution context and guarantees serial execution of all submitted
 | 
			
		||||
     * operations
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    makeStrand()
 | 
			
		||||
    {
 | 
			
		||||
        return pimpl_->makeStrand();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Concept {
 | 
			
		||||
        virtual ~Concept() = default;
 | 
			
		||||
 | 
			
		||||
        virtual impl::ErasedOperation
 | 
			
		||||
        execute(
 | 
			
		||||
            std::function<impl::Any(AnyStopToken)>,
 | 
			
		||||
            std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
        ) = 0;
 | 
			
		||||
        virtual impl::ErasedOperation execute(std::function<impl::Any()>) = 0;
 | 
			
		||||
        virtual impl::ErasedOperation
 | 
			
		||||
            scheduleAfter(std::chrono::milliseconds, std::function<impl::Any(AnyStopToken)>) = 0;
 | 
			
		||||
        virtual impl::ErasedOperation
 | 
			
		||||
            scheduleAfter(std::chrono::milliseconds, std::function<impl::Any(AnyStopToken, bool)>) = 0;
 | 
			
		||||
        virtual AnyStrand
 | 
			
		||||
        makeStrand() = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <typename CtxType>
 | 
			
		||||
    struct Model : Concept {
 | 
			
		||||
        std::reference_wrapper<std::decay_t<CtxType>> ctx;
 | 
			
		||||
 | 
			
		||||
        Model(CtxType& ctx) : ctx{std::ref(ctx)}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl::ErasedOperation
 | 
			
		||||
        execute(std::function<impl::Any(AnyStopToken)> fn, std::optional<std::chrono::milliseconds> timeout) override
 | 
			
		||||
        {
 | 
			
		||||
            return ctx.get().execute(std::move(fn), timeout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl::ErasedOperation
 | 
			
		||||
        execute(std::function<impl::Any()> fn) override
 | 
			
		||||
        {
 | 
			
		||||
            return ctx.get().execute(std::move(fn));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl::ErasedOperation
 | 
			
		||||
        scheduleAfter(std::chrono::milliseconds delay, std::function<impl::Any(AnyStopToken)> fn) override
 | 
			
		||||
        {
 | 
			
		||||
            return ctx.get().scheduleAfter(delay, std::move(fn));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl::ErasedOperation
 | 
			
		||||
        scheduleAfter(std::chrono::milliseconds delay, std::function<impl::Any(AnyStopToken, bool)> fn) override
 | 
			
		||||
        {
 | 
			
		||||
            return ctx.get().scheduleAfter(delay, std::move(fn));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AnyStrand
 | 
			
		||||
        makeStrand() override
 | 
			
		||||
        {
 | 
			
		||||
            return ctx.get().makeStrand();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::unique_ptr<Concept> pimpl_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										108
									
								
								src/util/async/AnyOperation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/util/async/AnyOperation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
#include "util/async/impl/ErasedOperation.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <fmt/std.h>
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
// TODO: In the future, perhaps cancel and requestStop should be combined into one.
 | 
			
		||||
// Users of the library should not care whether the operation is cancellable or stoppable - users just want to cancel
 | 
			
		||||
// it whatever that means internally.
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A type-erased operation that can be executed via AnyExecutionContext
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType>
 | 
			
		||||
class AnyOperation {
 | 
			
		||||
public:
 | 
			
		||||
    template <SomeOperation OpType>
 | 
			
		||||
        requires std::is_same_v<std::decay_t<OpType>, impl::ErasedOperation>
 | 
			
		||||
    /* implicit */ AnyOperation(OpType&& operation) : operation_{std::forward<OpType>(operation)}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AnyOperation() = default;
 | 
			
		||||
 | 
			
		||||
    AnyOperation(AnyOperation const&) = delete;
 | 
			
		||||
    AnyOperation(AnyOperation&&) = default;
 | 
			
		||||
    AnyOperation&
 | 
			
		||||
    operator=(AnyOperation const&) = delete;
 | 
			
		||||
    AnyOperation&
 | 
			
		||||
    operator=(AnyOperation&&) = default;
 | 
			
		||||
 | 
			
		||||
    /** @brief Wait for the operation to complete */
 | 
			
		||||
    void
 | 
			
		||||
    wait() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        operation_.wait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Request the operation to be stopped as soon as possible */
 | 
			
		||||
    void
 | 
			
		||||
    requestStop() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        operation_.requestStop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Cancel the operation. Used to cancel the timer for scheduled operations */
 | 
			
		||||
    void
 | 
			
		||||
    cancel() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        operation_.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Get the result of the operation */
 | 
			
		||||
    [[nodiscard]] util::Expected<RetType, ExecutionError>
 | 
			
		||||
    get()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            auto data = operation_.get();
 | 
			
		||||
            if (not data)
 | 
			
		||||
                return util::Unexpected(std::move(data).error());
 | 
			
		||||
 | 
			
		||||
            if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                return {};
 | 
			
		||||
            } else {
 | 
			
		||||
                return std::any_cast<RetType>(std::move(data).value());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } catch (std::bad_any_cast const& e) {
 | 
			
		||||
            return util::Unexpected{ExecutionError(fmt::format("{}", std::this_thread::get_id()), "Bad any cast")};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    impl::ErasedOperation operation_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										108
									
								
								src/util/async/AnyStopToken.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/util/async/AnyStopToken.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A type-erased stop token
 | 
			
		||||
 */
 | 
			
		||||
class AnyStopToken {
 | 
			
		||||
public:
 | 
			
		||||
    template <SomeStopToken TokenType>
 | 
			
		||||
        requires(not std::is_same_v<std::decay_t<TokenType>, AnyStopToken>)
 | 
			
		||||
    /* implicit */ AnyStopToken(TokenType&& token)
 | 
			
		||||
        : pimpl_{std::make_unique<Model<TokenType>>(std::forward<TokenType>(token))}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AnyStopToken() = default;
 | 
			
		||||
 | 
			
		||||
    AnyStopToken(AnyStopToken const& other) : pimpl_{other.pimpl_->clone()}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AnyStopToken&
 | 
			
		||||
    operator=(AnyStopToken const& rhs)
 | 
			
		||||
    {
 | 
			
		||||
        AnyStopToken copy{rhs};
 | 
			
		||||
        pimpl_.swap(copy.pimpl_);
 | 
			
		||||
        return *this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AnyStopToken(AnyStopToken&&) = default;
 | 
			
		||||
    AnyStopToken&
 | 
			
		||||
    operator=(AnyStopToken&&) = default;
 | 
			
		||||
 | 
			
		||||
    /** @returns true if stop is requested; false otherwise */
 | 
			
		||||
    [[nodiscard]] bool
 | 
			
		||||
    isStopRequested() const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return pimpl_->isStopRequested();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @returns true if stop is requested; false otherwise */
 | 
			
		||||
    [[nodiscard]] operator bool() const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return isStopRequested();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Concept {
 | 
			
		||||
        virtual ~Concept() = default;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] virtual bool
 | 
			
		||||
        isStopRequested() const noexcept = 0;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] virtual std::unique_ptr<Concept>
 | 
			
		||||
        clone() const = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <SomeStopToken TokenType>
 | 
			
		||||
    struct Model : Concept {
 | 
			
		||||
        TokenType token;
 | 
			
		||||
 | 
			
		||||
        Model(TokenType&& token) : token{std::move(token)}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool
 | 
			
		||||
        isStopRequested() const noexcept override
 | 
			
		||||
        {
 | 
			
		||||
            return token.isStopRequested();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] std::unique_ptr<Concept>
 | 
			
		||||
        clone() const override
 | 
			
		||||
        {
 | 
			
		||||
            return std::make_unique<Model>(*this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::unique_ptr<Concept> pimpl_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										150
									
								
								src/util/async/AnyStrand.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/util/async/AnyStrand.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
#include "util/async/impl/ErasedOperation.hpp"
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A type-erased execution context
 | 
			
		||||
 */
 | 
			
		||||
class AnyStrand {
 | 
			
		||||
public:
 | 
			
		||||
    template <typename StrandType>
 | 
			
		||||
        requires(not std::is_same_v<std::decay_t<StrandType>, AnyStrand>)
 | 
			
		||||
    /* implicit */ AnyStrand(StrandType&& strand)
 | 
			
		||||
        : pimpl_{std::make_unique<Model<StrandType>>(std::forward<StrandType>(strand))}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~AnyStrand() = default;
 | 
			
		||||
 | 
			
		||||
    /** @brief Execute a function without a stop token on the strand */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWithoutStopToken auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn())>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(  //
 | 
			
		||||
            pimpl_->execute([fn = std::forward<decltype(fn)>(fn)]() -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn();
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn());
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Execute a function taking a stop token on the strand */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<AnyStopToken> auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>()))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(  //
 | 
			
		||||
            pimpl_->execute([fn = std::forward<decltype(fn)>(fn)](auto stopToken) -> impl::Any {
 | 
			
		||||
                if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    return {};
 | 
			
		||||
                } else {
 | 
			
		||||
                    return std::make_any<RetType>(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Execute a function taking a stop token on the strand with a timeout */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<AnyStopToken> auto&& fn, SomeStdDuration auto timeout)
 | 
			
		||||
    {
 | 
			
		||||
        using RetType = std::decay_t<decltype(fn(std::declval<AnyStopToken>()))>;
 | 
			
		||||
        static_assert(not std::is_same_v<RetType, impl::Any>);
 | 
			
		||||
 | 
			
		||||
        return AnyOperation<RetType>(  //
 | 
			
		||||
            pimpl_->execute(
 | 
			
		||||
                [fn = std::forward<decltype(fn)>(fn)](auto stopToken) -> impl::Any {
 | 
			
		||||
                    if constexpr (std::is_void_v<RetType>) {
 | 
			
		||||
                        fn(std::move(stopToken));
 | 
			
		||||
                        return {};
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return std::make_any<RetType>(fn(std::move(stopToken)));
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                std::chrono::duration_cast<std::chrono::milliseconds>(timeout)
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Concept {
 | 
			
		||||
        virtual ~Concept() = default;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] virtual impl::ErasedOperation
 | 
			
		||||
        execute(
 | 
			
		||||
            std::function<impl::Any(AnyStopToken)>,
 | 
			
		||||
            std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
        ) = 0;
 | 
			
		||||
        [[nodiscard]] virtual impl::ErasedOperation execute(std::function<impl::Any()>) = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <typename StrandType>
 | 
			
		||||
    struct Model : Concept {
 | 
			
		||||
        StrandType strand;
 | 
			
		||||
 | 
			
		||||
        template <typename SType>
 | 
			
		||||
            requires std::is_same_v<SType, StrandType>
 | 
			
		||||
        Model(SType&& strand) : strand{std::forward<SType>(strand)}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] impl::ErasedOperation
 | 
			
		||||
        execute(std::function<impl::Any(AnyStopToken)> fn, std::optional<std::chrono::milliseconds> timeout) override
 | 
			
		||||
        {
 | 
			
		||||
            return strand.execute(std::move(fn), timeout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] impl::ErasedOperation
 | 
			
		||||
        execute(std::function<impl::Any()> fn) override
 | 
			
		||||
        {
 | 
			
		||||
            return strand.execute(std::move(fn));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::unique_ptr<Concept> pimpl_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										127
									
								
								src/util/async/Concepts.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/util/async/Concepts.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStoppable = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.requestStop()
 | 
			
		||||
    } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeCancellable = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.cancel()
 | 
			
		||||
    } -> std::same_as<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeOperation = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.wait()
 | 
			
		||||
    } -> std::same_as<void>;
 | 
			
		||||
    {
 | 
			
		||||
        v.get()
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStoppableOperation = SomeOperation<T> and SomeStoppable<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeCancellableOperation = SomeOperation<T> and SomeCancellable<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeOutcome = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.getOperation()
 | 
			
		||||
    } -> SomeOperation;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStopToken = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.isStopRequested()
 | 
			
		||||
    } -> std::same_as<bool>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeYieldStopSource = requires(T v, boost::asio::yield_context yield) {
 | 
			
		||||
    {
 | 
			
		||||
        v[yield]
 | 
			
		||||
    } -> SomeStopToken;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeSimpleStopSource = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.getToken()
 | 
			
		||||
    } -> SomeStopToken;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStopSource = (SomeSimpleStopSource<T> or SomeYieldStopSource<T>)and SomeStoppable<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStopSourceProvider = requires(T v) {
 | 
			
		||||
    {
 | 
			
		||||
        v.getStopSource()
 | 
			
		||||
    } -> SomeStopSource;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStoppableOutcome = SomeOutcome<T> and SomeStopSourceProvider<T>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeHandlerWithoutStopToken = requires(T fn) {
 | 
			
		||||
    {
 | 
			
		||||
        std::invoke(fn)
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T, typename... Args>
 | 
			
		||||
concept SomeHandlerWith = requires(T fn) {
 | 
			
		||||
    {
 | 
			
		||||
        std::invoke(fn, std::declval<Args>()...)
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeStdDuration = requires {
 | 
			
		||||
    // Thank you Ed Catmur for this trick.
 | 
			
		||||
    // See https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
 | 
			
		||||
    []<typename Rep, typename Period>( // 
 | 
			
		||||
        std::type_identity<std::chrono::duration<Rep, Period>>
 | 
			
		||||
    ) {}(std::type_identity<T>());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
concept SomeOptStdDuration = requires(T v) { SomeStdDuration<decltype(v.value())>; };
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										55
									
								
								src/util/async/Error.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/util/async/Error.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <fmt/std.h>
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Error channel type for async operation of any ExecutionContext
 | 
			
		||||
 */
 | 
			
		||||
struct ExecutionError {
 | 
			
		||||
    ExecutionError(std::string tid, std::string msg)
 | 
			
		||||
        : message{fmt::format("Thread {} exit with exception: {}", std::move(tid), std::move(msg))}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ExecutionError(ExecutionError const&) = default;
 | 
			
		||||
    ExecutionError(ExecutionError&&) = default;
 | 
			
		||||
    ExecutionError&
 | 
			
		||||
    operator=(ExecutionError&&) = default;
 | 
			
		||||
    ExecutionError&
 | 
			
		||||
    operator=(ExecutionError const&) = default;
 | 
			
		||||
 | 
			
		||||
    operator char const*() const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return message.c_str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string message;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										169
									
								
								src/util/async/Operation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/util/async/Operation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,169 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Outcome.hpp"
 | 
			
		||||
#include "util/async/context/impl/Cancellation.hpp"
 | 
			
		||||
#include "util/async/context/impl/Timer.hpp"
 | 
			
		||||
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
#include <future>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
namespace impl {
 | 
			
		||||
 | 
			
		||||
template <typename OutcomeType>
 | 
			
		||||
class BasicOperation {
 | 
			
		||||
protected:
 | 
			
		||||
    std::future<typename OutcomeType::DataType> future_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    using DataType = typename OutcomeType::DataType;
 | 
			
		||||
 | 
			
		||||
    explicit BasicOperation(OutcomeType* outcome) : future_{outcome->getStdFuture()}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BasicOperation(BasicOperation&&) = default;
 | 
			
		||||
    BasicOperation(BasicOperation const&) = delete;
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    get()
 | 
			
		||||
    {
 | 
			
		||||
        return future_.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    wait()
 | 
			
		||||
    {
 | 
			
		||||
        future_.wait();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename CtxType, typename OpType>
 | 
			
		||||
struct BasicScheduledOperation {
 | 
			
		||||
    struct State {
 | 
			
		||||
        std::mutex m_;
 | 
			
		||||
        std::condition_variable ready_;
 | 
			
		||||
        std::optional<OpType> op_{std::nullopt};
 | 
			
		||||
 | 
			
		||||
        void
 | 
			
		||||
        emplace(auto&& op)
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard lock{m_};
 | 
			
		||||
            op_.emplace(std::forward<decltype(op)>(op));
 | 
			
		||||
            ready_.notify_all();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] OpType&
 | 
			
		||||
        get()
 | 
			
		||||
        {
 | 
			
		||||
            std::unique_lock lock{m_};
 | 
			
		||||
            ready_.wait(lock, [this] { return op_.has_value(); });
 | 
			
		||||
            return op_.value();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<State> state_ = std::make_shared<State>();
 | 
			
		||||
    typename CtxType::Timer timer_;
 | 
			
		||||
 | 
			
		||||
    BasicScheduledOperation(auto& executor, auto delay, auto&& fn)
 | 
			
		||||
        : timer_(executor, delay, [state = state_, fn = std::forward<decltype(fn)>(fn)](auto ec) mutable {
 | 
			
		||||
            state->emplace(fn(ec));
 | 
			
		||||
        })
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    get()
 | 
			
		||||
    {
 | 
			
		||||
        return state_->get().get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    wait() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        state_->get().wait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    cancel() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        timer_.cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    requestStop() noexcept
 | 
			
		||||
        requires(SomeStoppableOperation<OpType>)
 | 
			
		||||
    {
 | 
			
		||||
        state_->get().requestStop();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace impl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The `future` side of async operations that can be stopped
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam RetType The return type of the operation
 | 
			
		||||
 * @tparam StopSourceType The type of the stop source
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType, typename StopSourceType>
 | 
			
		||||
class StoppableOperation : public impl::BasicOperation<StoppableOutcome<RetType, StopSourceType>> {
 | 
			
		||||
    using OutcomeType = StoppableOutcome<RetType, StopSourceType>;
 | 
			
		||||
 | 
			
		||||
    StopSourceType stopSource_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit StoppableOperation(OutcomeType* outcome)
 | 
			
		||||
        : impl::BasicOperation<OutcomeType>(outcome), stopSource_(outcome->getStopSource())
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Requests the operation to stop */
 | 
			
		||||
    void
 | 
			
		||||
    requestStop() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        stopSource_.requestStop();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The `future` side of async operations that cannot be stopped
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam RetType The return type of the operation
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType>
 | 
			
		||||
using Operation = impl::BasicOperation<Outcome<RetType>>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief The `future` side of async operations that can be scheduled
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam CtxType The type of the execution context
 | 
			
		||||
 * @tparam OpType The type of the wrapped operation
 | 
			
		||||
 */
 | 
			
		||||
template <typename CtxType, typename OpType>
 | 
			
		||||
using ScheduledOperation = impl::BasicScheduledOperation<CtxType, OpType>;
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										120
									
								
								src/util/async/Outcome.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/util/async/Outcome.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/context/impl/Cancellation.hpp"
 | 
			
		||||
 | 
			
		||||
#include <future>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
template <typename RetType, typename StopSourceType>
 | 
			
		||||
class StoppableOperation;
 | 
			
		||||
 | 
			
		||||
namespace impl {
 | 
			
		||||
 | 
			
		||||
template <typename RetType>
 | 
			
		||||
class BasicOperation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Base for all `promise` side of async operations
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam RetType The return type of the operation.
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType>
 | 
			
		||||
class BasicOutcome {
 | 
			
		||||
protected:
 | 
			
		||||
    std::promise<RetType> promise_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    using DataType = RetType;
 | 
			
		||||
 | 
			
		||||
    BasicOutcome() = default;
 | 
			
		||||
    BasicOutcome(BasicOutcome&&) = default;
 | 
			
		||||
    BasicOutcome(BasicOutcome const&) = delete;
 | 
			
		||||
 | 
			
		||||
    /** @brief Sets the value on the inner `promise` */
 | 
			
		||||
    void
 | 
			
		||||
    setValue(std::convertible_to<RetType> auto&& val)
 | 
			
		||||
    {
 | 
			
		||||
        promise_.set_value(std::forward<decltype(val)>(val));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Sets the value channel for void operations */
 | 
			
		||||
    void
 | 
			
		||||
    setValue()
 | 
			
		||||
    {
 | 
			
		||||
        promise_.set_value({});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Get the `future` for the inner `promise` */
 | 
			
		||||
    [[nodiscard]] std::future<RetType>
 | 
			
		||||
    getStdFuture()
 | 
			
		||||
    {
 | 
			
		||||
        return promise_.get_future();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace impl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Unstoppable outcome
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam RetType The return type of the operation.
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType>
 | 
			
		||||
class Outcome : public impl::BasicOutcome<RetType> {
 | 
			
		||||
public:
 | 
			
		||||
    /** @brief Gets the unstoppable operation for this outcome */
 | 
			
		||||
    [[nodiscard]] impl::BasicOperation<Outcome>
 | 
			
		||||
    getOperation()
 | 
			
		||||
    {
 | 
			
		||||
        return impl::BasicOperation<Outcome>{this};
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Stoppable outcome
 | 
			
		||||
 *
 | 
			
		||||
 * @tparam RetType The return type of the operation.
 | 
			
		||||
 * @tparam StopSourceType The type of the stop source.
 | 
			
		||||
 */
 | 
			
		||||
template <typename RetType, typename StopSourceType>
 | 
			
		||||
class StoppableOutcome : public impl::BasicOutcome<RetType> {
 | 
			
		||||
private:
 | 
			
		||||
    StopSourceType stopSource_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    /** @brief Gets the stoppable operation for this outcome */
 | 
			
		||||
    [[nodiscard]] StoppableOperation<RetType, StopSourceType>
 | 
			
		||||
    getOperation()
 | 
			
		||||
    {
 | 
			
		||||
        return StoppableOperation<RetType, StopSourceType>{this};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @brief Gets the stop source for this outcome */
 | 
			
		||||
    [[nodiscard]] StopSourceType&
 | 
			
		||||
    getStopSource()
 | 
			
		||||
    {
 | 
			
		||||
        return stopSource_;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										334
									
								
								src/util/async/context/BasicExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								src/util/async/context/BasicExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,334 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/Operation.hpp"
 | 
			
		||||
#include "util/async/Outcome.hpp"
 | 
			
		||||
#include "util/async/context/impl/Cancellation.hpp"
 | 
			
		||||
#include "util/async/context/impl/Execution.hpp"
 | 
			
		||||
#include "util/async/context/impl/Strand.hpp"
 | 
			
		||||
#include "util/async/context/impl/Timer.hpp"
 | 
			
		||||
#include "util/async/context/impl/Utils.hpp"
 | 
			
		||||
#include "util/async/impl/ErrorHandling.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio.hpp>
 | 
			
		||||
#include <boost/asio/error.hpp>
 | 
			
		||||
#include <boost/asio/strand.hpp>
 | 
			
		||||
#include <boost/asio/thread_pool.hpp>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
namespace impl {
 | 
			
		||||
 | 
			
		||||
struct AsioPoolStrandContext {
 | 
			
		||||
    using Executor = boost::asio::strand<boost::asio::thread_pool::executor_type>;
 | 
			
		||||
    using Timer = SteadyTimer<Executor>;
 | 
			
		||||
 | 
			
		||||
    Executor executor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AsioPoolContext {
 | 
			
		||||
    using Executor = boost::asio::thread_pool;
 | 
			
		||||
    using Timer = SteadyTimer<Executor>;
 | 
			
		||||
    using Strand = AsioPoolStrandContext;
 | 
			
		||||
 | 
			
		||||
    Strand
 | 
			
		||||
    makeStrand()
 | 
			
		||||
    {
 | 
			
		||||
        return {boost::asio::make_strand(executor)};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Executor executor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace impl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A highly configurable execution context.
 | 
			
		||||
 *
 | 
			
		||||
 * This execution context is used as the base for all specialized execution contexts.
 | 
			
		||||
 * Return values are handled by capturing them and returning them packaged as util::Expected.
 | 
			
		||||
 * Exceptions may or may not be caught and handled depending on the error strategy. The default behavior is to catch and
 | 
			
		||||
 * package them as the error channel of util::Expected.
 | 
			
		||||
 */
 | 
			
		||||
template <
 | 
			
		||||
    typename ContextType,
 | 
			
		||||
    typename StopSourceType,
 | 
			
		||||
    typename DispatcherType,
 | 
			
		||||
    typename TimerContextProvider = impl::SelfContextProvider,
 | 
			
		||||
    typename ErrorHandlerType = impl::DefaultErrorHandler>
 | 
			
		||||
class BasicExecutionContext {
 | 
			
		||||
    ContextType context_;
 | 
			
		||||
    friend impl::AssociatedExecutorExtractor;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static constexpr bool isNoexcept = noexcept(ErrorHandlerType::wrap([](auto&) { throw 0; }));
 | 
			
		||||
 | 
			
		||||
    using ContextHolderType = ContextType;
 | 
			
		||||
    using ExecutorType = typename ContextHolderType::Executor;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ValueType = util::Expected<T, ExecutionError>;
 | 
			
		||||
 | 
			
		||||
    using StopSource = StopSourceType;
 | 
			
		||||
 | 
			
		||||
    using StopToken = typename StopSourceType::Token;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using StoppableOperation = StoppableOperation<ValueType<T>, StopSourceType>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using Operation = Operation<ValueType<T>>;
 | 
			
		||||
 | 
			
		||||
    using Strand = impl::
 | 
			
		||||
        BasicStrand<BasicExecutionContext, StopSourceType, DispatcherType, TimerContextProvider, ErrorHandlerType>;
 | 
			
		||||
 | 
			
		||||
    using Timer = typename ContextHolderType::Timer;
 | 
			
		||||
 | 
			
		||||
    // note: scheduled operations are always stoppable
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ScheduledOperation = ScheduledOperation<BasicExecutionContext, StoppableOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Create a new execution context with the given number of threads.
 | 
			
		||||
     *
 | 
			
		||||
     * @param numThreads The number of threads to use for the underlying thread pool
 | 
			
		||||
     */
 | 
			
		||||
    explicit BasicExecutionContext(std::size_t numThreads = 1) noexcept : context_{numThreads}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Stops and joins the underlying thread pool.
 | 
			
		||||
     */
 | 
			
		||||
    ~BasicExecutionContext()
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BasicExecutionContext(BasicExecutionContext&&) = delete;
 | 
			
		||||
    BasicExecutionContext(BasicExecutionContext const&) = delete;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule an operation on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param delay The delay after which the operation should be executed
 | 
			
		||||
     * @param fn The block of code to execute with stop token as the only arg
 | 
			
		||||
     * @param timeout The optional timeout duration after which the operation will be cancelled
 | 
			
		||||
     * @return A scheduled stoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    scheduleAfter(
 | 
			
		||||
        SomeStdDuration auto delay,
 | 
			
		||||
        SomeHandlerWith<StopToken> auto&& fn,
 | 
			
		||||
        std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
    ) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        if constexpr (not std::is_same_v<decltype(TimerContextProvider::getContext(*this)), decltype(*this)>) {
 | 
			
		||||
            return TimerContextProvider::getContext(*this).scheduleAfter(
 | 
			
		||||
                delay, std::forward<decltype(fn)>(fn), timeout
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            using FnRetType = std::decay_t<decltype(fn(std::declval<StopToken>()))>;
 | 
			
		||||
            return ScheduledOperation<FnRetType>(
 | 
			
		||||
                impl::extractAssociatedExecutor(*this),
 | 
			
		||||
                delay,
 | 
			
		||||
                [this, timeout, fn = std::forward<decltype(fn)>(fn)](auto) mutable {
 | 
			
		||||
                    return this->execute(
 | 
			
		||||
                        [fn = std::forward<decltype(fn)>(fn)](auto stopToken) {
 | 
			
		||||
                            if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                                fn(std::move(stopToken));
 | 
			
		||||
                            } else {
 | 
			
		||||
                                return fn(std::move(stopToken));
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        timeout
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule an operation on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param delay The delay after which the operation should be executed
 | 
			
		||||
     * @param fn The block of code to execute with stop token as the first arg and cancellation flag as the second arg
 | 
			
		||||
     * @param timeout The optional timeout duration after which the operation will be cancelled
 | 
			
		||||
     * @return A scheduled stoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    scheduleAfter(
 | 
			
		||||
        SomeStdDuration auto delay,
 | 
			
		||||
        SomeHandlerWith<StopToken, bool> auto&& fn,
 | 
			
		||||
        std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
    ) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        if constexpr (not std::is_same_v<decltype(TimerContextProvider::getContext(*this)), decltype(*this)>) {
 | 
			
		||||
            return TimerContextProvider::getContext(*this).scheduleAfter(
 | 
			
		||||
                delay, std::forward<decltype(fn)>(fn), timeout
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            using FnRetType = std::decay_t<decltype(fn(std::declval<StopToken>(), true))>;
 | 
			
		||||
            return ScheduledOperation<FnRetType>(
 | 
			
		||||
                impl::extractAssociatedExecutor(*this),
 | 
			
		||||
                delay,
 | 
			
		||||
                [this, timeout, fn = std::forward<decltype(fn)>(fn)](auto ec) mutable {
 | 
			
		||||
                    return this->execute(
 | 
			
		||||
                        [fn = std::forward<decltype(fn)>(fn),
 | 
			
		||||
                         isAborted = (ec == boost::asio::error::operation_aborted)](auto stopToken) {
 | 
			
		||||
                            if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                                fn(std::move(stopToken), isAborted);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                return fn(std::move(stopToken), isAborted);
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        timeout
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule an operation on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The block of code to execute with stop token as first arg
 | 
			
		||||
     * @param timeout The optional timeout duration after which the operation will be cancelled
 | 
			
		||||
     * @return A stoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(
 | 
			
		||||
        SomeHandlerWith<StopToken> auto&& fn,
 | 
			
		||||
        std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
    ) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return DispatcherType::dispatch(
 | 
			
		||||
            context_,
 | 
			
		||||
            impl::outcomeForHandler<StopSourceType>(fn),
 | 
			
		||||
            ErrorHandlerType::wrap([this, timeout, fn = std::forward<decltype(fn)>(fn)](
 | 
			
		||||
                                       auto& outcome, auto& stopSource, auto stopToken
 | 
			
		||||
                                   ) mutable {
 | 
			
		||||
                [[maybe_unused]] auto timeoutHandler =
 | 
			
		||||
                    impl::getTimeoutHandleIfNeeded(TimerContextProvider::getContext(*this), timeout, stopSource);
 | 
			
		||||
 | 
			
		||||
                using FnRetType = std::decay_t<decltype(fn(std::declval<StopToken>()))>;
 | 
			
		||||
                if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    outcome.setValue();
 | 
			
		||||
                } else {
 | 
			
		||||
                    outcome.setValue(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule an operation on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The block of code to execute with stop token as first arg
 | 
			
		||||
     * @param timeout The timeout duration after which the operation will be cancelled
 | 
			
		||||
     * @return A stoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<StopToken> auto&& fn, SomeStdDuration auto timeout) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return execute(
 | 
			
		||||
            std::forward<decltype(fn)>(fn),
 | 
			
		||||
            std::make_optional(std::chrono::duration_cast<std::chrono::milliseconds>(timeout))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Schedule an operation on the execution context
 | 
			
		||||
     *
 | 
			
		||||
     * @param fn The block of code to execute. Signature is `Type()` where `Type` is the return type.
 | 
			
		||||
     * @param timeout The timeout duration after which the operation will be cancelled
 | 
			
		||||
     * @return A unstoppable operation that can be used to wait for the result
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWithoutStopToken auto&& fn) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return DispatcherType::dispatch(
 | 
			
		||||
            context_,
 | 
			
		||||
            impl::outcomeForHandler<StopSourceType>(fn),
 | 
			
		||||
            ErrorHandlerType::wrap([fn = std::forward<decltype(fn)>(fn)](auto& outcome) mutable {
 | 
			
		||||
                using FnRetType = std::decay_t<decltype(fn())>;
 | 
			
		||||
                if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                    fn();
 | 
			
		||||
                    outcome.setValue();
 | 
			
		||||
                } else {
 | 
			
		||||
                    outcome.setValue(fn());
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Create a strand for this execution context
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] Strand
 | 
			
		||||
    makeStrand()
 | 
			
		||||
    {
 | 
			
		||||
        return Strand(*this, context_.makeStrand());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Stop the execution context as soon as possible
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    stop() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        context_.executor.stop();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A Boost.Coroutine-based (asio yield_context) execution context.
 | 
			
		||||
 *
 | 
			
		||||
 * This execution context uses `asio::spawn` to create a coroutine per executed operation.
 | 
			
		||||
 * The stop token that is sent to the lambda to execute is @ref impl::YieldContextStopSource::Token
 | 
			
		||||
 * and is special in the way that each time your code checks `token.isStopRequested()` the coroutine will
 | 
			
		||||
 * be suspended and other work such as timers and/or other operations in the queue will get a chance to run.
 | 
			
		||||
 * This makes it possible to have 1 thread in the execution context and still be able to execute operations AND timers
 | 
			
		||||
 * at the same time.
 | 
			
		||||
 */
 | 
			
		||||
using CoroExecutionContext =
 | 
			
		||||
    BasicExecutionContext<impl::AsioPoolContext, impl::YieldContextStopSource, impl::SpawnDispatchStrategy>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A asio::thread_pool-based execution context.
 | 
			
		||||
 *
 | 
			
		||||
 * This execution context uses `asio::post` to dispatch operations to the thread pool.
 | 
			
		||||
 * Please note that this execution context can't handle timers and operations at the same time iff you have exactly 1
 | 
			
		||||
 * thread in the thread pool.
 | 
			
		||||
 */
 | 
			
		||||
using PoolExecutionContext =
 | 
			
		||||
    BasicExecutionContext<impl::AsioPoolContext, impl::BasicStopSource, impl::PostDispatchStrategy>;
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										86
									
								
								src/util/async/context/SyncExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/util/async/context/SyncExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/context/BasicExecutionContext.hpp"
 | 
			
		||||
#include "util/async/context/SystemExecutionContext.hpp"
 | 
			
		||||
#include "util/async/context/impl/Cancellation.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/error.hpp>
 | 
			
		||||
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
namespace impl {
 | 
			
		||||
 | 
			
		||||
struct SameThreadContext {
 | 
			
		||||
    struct Executor {
 | 
			
		||||
        Executor(std::size_t)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void
 | 
			
		||||
        stop() noexcept
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Note: these types are not actually used but needed for compilation
 | 
			
		||||
    struct Timer {};
 | 
			
		||||
    struct Strand {
 | 
			
		||||
        struct Executor {};
 | 
			
		||||
        struct Timer {};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Executor executor;
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] Strand
 | 
			
		||||
    makeStrand() noexcept  // NOLINT(readability-convert-member-functions-to-static)
 | 
			
		||||
    {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SystemContextProvider {
 | 
			
		||||
    template <typename CtxType>
 | 
			
		||||
    [[nodiscard]] static constexpr auto&
 | 
			
		||||
    getContext([[maybe_unused]] CtxType& self) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return SystemExecutionContext::instance();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace impl
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A synchronous execution context. Runs on the caller thread.
 | 
			
		||||
 *
 | 
			
		||||
 * This execution context runs the operations on the same thread that requested the operation to run.
 | 
			
		||||
 * Each operation must finish before the corresponding `execute` returns an operation object that can immediately be
 | 
			
		||||
 * queried for value or error as it's guaranteed to have completed. Timer-based operations are scheduled via
 | 
			
		||||
 * SystemExecutionContext, including those that are scheduled from within a strand.
 | 
			
		||||
 */
 | 
			
		||||
using SyncExecutionContext = BasicExecutionContext<
 | 
			
		||||
    impl::SameThreadContext,
 | 
			
		||||
    impl::BasicStopSource,
 | 
			
		||||
    impl::SyncDispatchStrategy,
 | 
			
		||||
    impl::SystemContextProvider>;
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										42
									
								
								src/util/async/context/SystemExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/util/async/context/SystemExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/context/BasicExecutionContext.hpp"
 | 
			
		||||
 | 
			
		||||
namespace util::async {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A execution context that runs tasks on a system thread pool of 1 thread.
 | 
			
		||||
 *
 | 
			
		||||
 * This is useful for timers and system tasks that need to be scheduled on a exececution context that otherwise would
 | 
			
		||||
 * not be able to support them (e.g. a synchronous execution context).
 | 
			
		||||
 */
 | 
			
		||||
class SystemExecutionContext {
 | 
			
		||||
public:
 | 
			
		||||
    [[nodiscard]] static auto&
 | 
			
		||||
    instance()
 | 
			
		||||
    {
 | 
			
		||||
        static util::async::PoolExecutionContext systemExecutionContext{};
 | 
			
		||||
        return systemExecutionContext;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async
 | 
			
		||||
							
								
								
									
										133
									
								
								src/util/async/context/impl/Cancellation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/util/async/context/impl/Cancellation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/post.hpp>
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
class StopState {
 | 
			
		||||
    std::atomic_bool isStopRequested_{false};
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    void
 | 
			
		||||
    requestStop() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        isStopRequested_ = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] bool
 | 
			
		||||
    isStopRequested() const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return isStopRequested_;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using SharedStopState = std::shared_ptr<StopState>;
 | 
			
		||||
 | 
			
		||||
class YieldContextStopSource {
 | 
			
		||||
    SharedStopState shared_ = std::make_shared<StopState>();
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    class Token {
 | 
			
		||||
        friend class YieldContextStopSource;
 | 
			
		||||
        SharedStopState shared_;
 | 
			
		||||
        boost::asio::yield_context yield_;
 | 
			
		||||
 | 
			
		||||
        Token(YieldContextStopSource* source, boost::asio::yield_context yield)
 | 
			
		||||
            : shared_{source->shared_}, yield_{std::move(yield)}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        [[nodiscard]] bool
 | 
			
		||||
        isStopRequested() const noexcept
 | 
			
		||||
        {
 | 
			
		||||
            // yield explicitly
 | 
			
		||||
            boost::asio::post(yield_);
 | 
			
		||||
            return shared_->isStopRequested();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] operator bool() const noexcept
 | 
			
		||||
        {
 | 
			
		||||
            return isStopRequested();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] Token
 | 
			
		||||
    operator[](boost::asio::yield_context yield) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return {this, yield};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    requestStop() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        shared_->requestStop();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BasicStopSource {
 | 
			
		||||
    SharedStopState shared_ = std::make_shared<StopState>();
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    class Token {
 | 
			
		||||
        friend class BasicStopSource;
 | 
			
		||||
        SharedStopState shared_;
 | 
			
		||||
 | 
			
		||||
        explicit Token(BasicStopSource* source) : shared_{source->shared_}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        Token(Token const&) = default;
 | 
			
		||||
        Token(Token&&) = default;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool
 | 
			
		||||
        isStopRequested() const noexcept
 | 
			
		||||
        {
 | 
			
		||||
            return shared_->isStopRequested();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] operator bool() const noexcept
 | 
			
		||||
        {
 | 
			
		||||
            return isStopRequested();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] Token
 | 
			
		||||
    getToken()
 | 
			
		||||
    {
 | 
			
		||||
        return Token{this};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    requestStop()
 | 
			
		||||
    {
 | 
			
		||||
        shared_->requestStop();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										96
									
								
								src/util/async/context/impl/Execution.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/util/async/context/impl/Execution.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/context/impl/Timer.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/asio/strand.hpp>
 | 
			
		||||
#include <boost/asio/thread_pool.hpp>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
struct SpawnDispatchStrategy {
 | 
			
		||||
    template <typename ContextType, SomeOutcome OutcomeType>
 | 
			
		||||
    [[nodiscard]] static auto
 | 
			
		||||
    dispatch(ContextType& ctx, OutcomeType&& outcome, auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        auto op = outcome.getOperation();
 | 
			
		||||
 | 
			
		||||
        boost::asio::spawn(
 | 
			
		||||
            ctx.executor,
 | 
			
		||||
            [outcome = std::forward<decltype(outcome)>(outcome),
 | 
			
		||||
             fn = std::forward<decltype(fn)>(fn)](auto yield) mutable {
 | 
			
		||||
                if constexpr (SomeStoppableOutcome<OutcomeType>) {
 | 
			
		||||
                    auto& stopSource = outcome.getStopSource();
 | 
			
		||||
                    fn(outcome, stopSource, stopSource[yield]);
 | 
			
		||||
                } else {
 | 
			
		||||
                    fn(outcome);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return op;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct PostDispatchStrategy {
 | 
			
		||||
    template <typename ContextType, SomeOutcome OutcomeType>
 | 
			
		||||
    [[nodiscard]] static auto
 | 
			
		||||
    dispatch(ContextType& ctx, OutcomeType&& outcome, auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        auto op = outcome.getOperation();
 | 
			
		||||
 | 
			
		||||
        boost::asio::post(
 | 
			
		||||
            ctx.executor,
 | 
			
		||||
            [outcome = std::forward<decltype(outcome)>(outcome), fn = std::forward<decltype(fn)>(fn)]() mutable {
 | 
			
		||||
                if constexpr (SomeStoppableOutcome<OutcomeType>) {
 | 
			
		||||
                    auto& stopSource = outcome.getStopSource();
 | 
			
		||||
                    fn(outcome, stopSource, stopSource.getToken());
 | 
			
		||||
                } else {
 | 
			
		||||
                    fn(outcome);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return op;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SyncDispatchStrategy {
 | 
			
		||||
    template <typename ContextType, SomeOutcome OutcomeType>
 | 
			
		||||
    [[nodiscard]] static auto
 | 
			
		||||
    dispatch([[maybe_unused]] ContextType& ctx, OutcomeType outcome, auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        auto op = outcome.getOperation();
 | 
			
		||||
 | 
			
		||||
        if constexpr (SomeStoppableOutcome<OutcomeType>) {
 | 
			
		||||
            auto& stopSource = outcome.getStopSource();
 | 
			
		||||
            fn(outcome, stopSource, stopSource.getToken());
 | 
			
		||||
        } else {
 | 
			
		||||
            fn(outcome);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return op;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										120
									
								
								src/util/async/context/impl/Strand.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/util/async/context/impl/Strand.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/context/impl/Cancellation.hpp"
 | 
			
		||||
#include "util/async/context/impl/Execution.hpp"
 | 
			
		||||
#include "util/async/context/impl/Timer.hpp"
 | 
			
		||||
#include "util/async/context/impl/Utils.hpp"
 | 
			
		||||
#include "util/async/impl/ErrorHandling.hpp"
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
template <
 | 
			
		||||
    typename ParentContextType,
 | 
			
		||||
    typename StopSourceType,
 | 
			
		||||
    typename DispatcherType,
 | 
			
		||||
    typename TimerContextProvider = impl::SelfContextProvider,
 | 
			
		||||
    typename ErrorHandlerType = impl::DefaultErrorHandler>
 | 
			
		||||
class BasicStrand {
 | 
			
		||||
    std::reference_wrapper<ParentContextType> parentContext_;
 | 
			
		||||
    typename ParentContextType::ContextHolderType::Strand context_;
 | 
			
		||||
    friend AssociatedExecutorExtractor;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static constexpr bool isNoexcept = noexcept(ErrorHandlerType::wrap([](auto&) { throw 0; }));
 | 
			
		||||
 | 
			
		||||
    using ContextHolderType = typename ParentContextType::ContextHolderType::Strand;
 | 
			
		||||
    using ExecutorType = typename ContextHolderType::Executor;
 | 
			
		||||
    using StopToken = typename StopSourceType::Token;
 | 
			
		||||
    using Timer =
 | 
			
		||||
        typename ParentContextType::ContextHolderType::Timer;  // timers are associated with the parent context
 | 
			
		||||
 | 
			
		||||
    BasicStrand(ParentContextType& parent, auto&& strand)
 | 
			
		||||
        : parentContext_{std::ref(parent)}, context_{std::forward<decltype(strand)>(strand)}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~BasicStrand() = default;
 | 
			
		||||
    BasicStrand(BasicStrand&&) = default;
 | 
			
		||||
    BasicStrand(BasicStrand const&) = delete;
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(
 | 
			
		||||
        SomeHandlerWith<StopToken> auto&& fn,
 | 
			
		||||
        std::optional<std::chrono::milliseconds> timeout = std::nullopt
 | 
			
		||||
    ) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return DispatcherType::dispatch(
 | 
			
		||||
            context_,
 | 
			
		||||
            impl::outcomeForHandler<StopSourceType>(fn),
 | 
			
		||||
            ErrorHandlerType::wrap([this, timeout, fn = std::forward<decltype(fn)>(fn)](
 | 
			
		||||
                                       auto& outcome, auto& stopSource, auto stopToken
 | 
			
		||||
                                   ) mutable {
 | 
			
		||||
                [[maybe_unused]] auto timeoutHandler = impl::getTimeoutHandleIfNeeded(
 | 
			
		||||
                    TimerContextProvider::getContext(parentContext_.get()), timeout, stopSource
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                using FnRetType = std::decay_t<decltype(fn(std::declval<StopToken>()))>;
 | 
			
		||||
                if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                    fn(std::move(stopToken));
 | 
			
		||||
                    outcome.setValue();
 | 
			
		||||
                } else {
 | 
			
		||||
                    outcome.setValue(fn(std::move(stopToken)));
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWith<StopToken> auto&& fn, SomeStdDuration auto timeout) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return execute(
 | 
			
		||||
            std::forward<decltype(fn)>(fn),
 | 
			
		||||
            std::make_optional(std::chrono::duration_cast<std::chrono::milliseconds>(timeout))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] auto
 | 
			
		||||
    execute(SomeHandlerWithoutStopToken auto&& fn) noexcept(isNoexcept)
 | 
			
		||||
    {
 | 
			
		||||
        return DispatcherType::dispatch(
 | 
			
		||||
            context_,
 | 
			
		||||
            impl::outcomeForHandler<StopSourceType>(fn),
 | 
			
		||||
            ErrorHandlerType::wrap([fn = std::forward<decltype(fn)>(fn)](auto& outcome) mutable {
 | 
			
		||||
                using FnRetType = std::decay_t<decltype(fn())>;
 | 
			
		||||
                if constexpr (std::is_void_v<FnRetType>) {
 | 
			
		||||
                    fn();
 | 
			
		||||
                    outcome.setValue();
 | 
			
		||||
                } else {
 | 
			
		||||
                    outcome.setValue(fn());
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										52
									
								
								src/util/async/context/impl/Timer.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/util/async/context/impl/Timer.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/steady_timer.hpp>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
template <typename ExecutorType>
 | 
			
		||||
class SteadyTimer {
 | 
			
		||||
    boost::asio::steady_timer timer_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    SteadyTimer(ExecutorType& executor, auto delay, auto&& fn) : timer_{executor}
 | 
			
		||||
    {
 | 
			
		||||
        timer_.expires_after(delay);
 | 
			
		||||
        timer_.async_wait(std::forward<decltype(fn)>(fn));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~SteadyTimer()
 | 
			
		||||
    {
 | 
			
		||||
        cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SteadyTimer(SteadyTimer&&) = default;
 | 
			
		||||
    SteadyTimer(SteadyTimer const&) = delete;
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    cancel() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        timer_.cancel();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										85
									
								
								src/util/async/context/impl/Utils.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/util/async/context/impl/Utils.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/context/impl/Timer.hpp"
 | 
			
		||||
 | 
			
		||||
#include <boost/asio/spawn.hpp>
 | 
			
		||||
#include <boost/asio/strand.hpp>
 | 
			
		||||
#include <boost/asio/thread_pool.hpp>
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
inline constexpr struct AssociatedExecutorExtractor {
 | 
			
		||||
    template <typename CtxType>
 | 
			
		||||
    [[nodiscard]] typename CtxType::ExecutorType&
 | 
			
		||||
    operator()(CtxType& ctx) const noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return ctx.context_.executor;
 | 
			
		||||
    }
 | 
			
		||||
} extractAssociatedExecutor;
 | 
			
		||||
 | 
			
		||||
template <typename CtxType>
 | 
			
		||||
[[nodiscard]] inline constexpr auto
 | 
			
		||||
getTimeoutHandleIfNeeded(CtxType& ctx, SomeOptStdDuration auto timeout, SomeStopSource auto& stopSource)
 | 
			
		||||
{
 | 
			
		||||
    using TimerType = typename CtxType::Timer;
 | 
			
		||||
    std::optional<TimerType> timer;
 | 
			
		||||
    if (timeout) {
 | 
			
		||||
        timer.emplace(extractAssociatedExecutor(ctx), *timeout, [&stopSource](auto cancelled) {
 | 
			
		||||
            if (not cancelled)
 | 
			
		||||
                stopSource.requestStop();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return timer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <SomeStopSource StopSourceType>
 | 
			
		||||
[[nodiscard]] inline constexpr auto
 | 
			
		||||
outcomeForHandler(auto&& fn)
 | 
			
		||||
{
 | 
			
		||||
    if constexpr (SomeHandlerWith<decltype(fn), typename StopSourceType::Token>) {
 | 
			
		||||
        using FnRetType = decltype(fn(std::declval<typename StopSourceType::Token>()));
 | 
			
		||||
        using RetType = util::Expected<FnRetType, ExecutionError>;
 | 
			
		||||
 | 
			
		||||
        return StoppableOutcome<RetType, StopSourceType>();
 | 
			
		||||
    } else {
 | 
			
		||||
        using FnRetType = decltype(fn());
 | 
			
		||||
        using RetType = util::Expected<FnRetType, ExecutionError>;
 | 
			
		||||
 | 
			
		||||
        return Outcome<RetType>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SelfContextProvider {
 | 
			
		||||
    template <typename CtxType>
 | 
			
		||||
    [[nodiscard]] static constexpr auto&
 | 
			
		||||
    getContext(CtxType& self) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return self;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										55
									
								
								src/util/async/impl/Any.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/util/async/impl/Any.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
// Note: This is a workaround for util::Expected. This is not needed when using std::expected.
 | 
			
		||||
// Will be removed after the migration to std::expected is complete (#1173)
 | 
			
		||||
// Issue to track this removal can be found here: https://github.com/XRPLF/clio/issues/1174
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief A wrapper for std::any to workaround issues with boost.outcome
 | 
			
		||||
 */
 | 
			
		||||
class Any {
 | 
			
		||||
    std::any value_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    Any() = default;
 | 
			
		||||
    Any(Any const&) = default;
 | 
			
		||||
    Any(Any&&) = default;
 | 
			
		||||
 | 
			
		||||
    // note: this needs to be `auto` instead of `std::any` because of a bug in gcc 11.4
 | 
			
		||||
    Any(auto&& v)
 | 
			
		||||
        requires(std::is_same_v<std::decay_t<decltype(v)>, std::any>)
 | 
			
		||||
        : value_{std::forward<decltype(v)>(v)}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    operator std::any&() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return value_;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										145
									
								
								src/util/async/impl/ErasedOperation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/util/async/impl/ErasedOperation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
class ErasedOperation {
 | 
			
		||||
public:
 | 
			
		||||
    template <SomeOperation OpType>
 | 
			
		||||
        requires(not std::is_same_v<std::decay_t<OpType>, ErasedOperation>)
 | 
			
		||||
    /* implicit */ ErasedOperation(OpType&& operation)
 | 
			
		||||
        : pimpl_{std::make_unique<Model<OpType>>(std::forward<OpType>(operation))}
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~ErasedOperation() = default;
 | 
			
		||||
 | 
			
		||||
    ErasedOperation(ErasedOperation const&) = delete;
 | 
			
		||||
    ErasedOperation(ErasedOperation&&) = default;
 | 
			
		||||
    ErasedOperation&
 | 
			
		||||
    operator=(ErasedOperation const&) = delete;
 | 
			
		||||
    ErasedOperation&
 | 
			
		||||
    operator=(ErasedOperation&&) = default;
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    wait() noexcept
 | 
			
		||||
    {
 | 
			
		||||
        pimpl_->wait();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    util::Expected<Any, ExecutionError>
 | 
			
		||||
    get()
 | 
			
		||||
    {
 | 
			
		||||
        return pimpl_->get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Request the operation to be stopped as soon as possible.
 | 
			
		||||
     * @throw std::logic_error if the erased operation is non-stoppable
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    requestStop()
 | 
			
		||||
    {
 | 
			
		||||
        pimpl_->requestStop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Cancel the operation if it is scheduled and not yet started.
 | 
			
		||||
     * @throw std::logic_error if the erased operation is non-cancellable
 | 
			
		||||
     */
 | 
			
		||||
    void
 | 
			
		||||
    cancel()
 | 
			
		||||
    {
 | 
			
		||||
        pimpl_->cancel();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Concept {
 | 
			
		||||
        virtual ~Concept() = default;
 | 
			
		||||
 | 
			
		||||
        virtual void
 | 
			
		||||
        wait() noexcept = 0;
 | 
			
		||||
        virtual util::Expected<Any, ExecutionError>
 | 
			
		||||
        get() = 0;
 | 
			
		||||
        virtual void
 | 
			
		||||
        requestStop() = 0;
 | 
			
		||||
        virtual void
 | 
			
		||||
        cancel() = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    template <SomeOperation OpType>
 | 
			
		||||
    struct Model : Concept {
 | 
			
		||||
        OpType operation;
 | 
			
		||||
 | 
			
		||||
        template <typename OType>
 | 
			
		||||
            requires std::is_same_v<OType, OpType>
 | 
			
		||||
        Model(OType&& operation) : operation{std::forward<OType>(operation)}
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void
 | 
			
		||||
        wait() noexcept override
 | 
			
		||||
        {
 | 
			
		||||
            return operation.wait();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        util::Expected<Any, ExecutionError>
 | 
			
		||||
        get() override
 | 
			
		||||
        {
 | 
			
		||||
            // Note: return type of the operation was already wrapped to impl::Any by AnyExecutionContext
 | 
			
		||||
            return operation.get();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void
 | 
			
		||||
        requestStop() override
 | 
			
		||||
        {
 | 
			
		||||
            if constexpr (SomeStoppableOperation<OpType>) {
 | 
			
		||||
                operation.requestStop();
 | 
			
		||||
            } else {
 | 
			
		||||
                throw std::logic_error("Stop requested on non-stoppable operation");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void
 | 
			
		||||
        cancel() override
 | 
			
		||||
        {
 | 
			
		||||
            if constexpr (SomeCancellableOperation<OpType>) {
 | 
			
		||||
                operation.cancel();
 | 
			
		||||
            } else {
 | 
			
		||||
                throw std::logic_error("Cancellation requested on non-cancellable operation");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::unique_ptr<Concept> pimpl_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										63
									
								
								src/util/async/impl/ErrorHandling.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/util/async/impl/ErrorHandling.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/async/Concepts.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
 | 
			
		||||
#include <fmt/core.h>
 | 
			
		||||
#include <fmt/std.h>
 | 
			
		||||
 | 
			
		||||
#include <exception>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
namespace util::async::impl {
 | 
			
		||||
 | 
			
		||||
struct DefaultErrorHandler {
 | 
			
		||||
    [[nodiscard]] static auto
 | 
			
		||||
    wrap(auto&& fn) noexcept
 | 
			
		||||
    {
 | 
			
		||||
        return
 | 
			
		||||
            [fn = std::forward<decltype(fn)>(fn)]<typename... Args>(SomeOutcome auto& outcome, Args&&... args) mutable {
 | 
			
		||||
                try {
 | 
			
		||||
                    fn(outcome, std::forward<Args>(args)...);
 | 
			
		||||
                } catch (std::exception const& e) {
 | 
			
		||||
                    outcome.setValue(
 | 
			
		||||
                        util::Unexpected(ExecutionError{fmt::format("{}", std::this_thread::get_id()), e.what()})
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (...) {
 | 
			
		||||
                    outcome.setValue(
 | 
			
		||||
                        util::Unexpected(ExecutionError{fmt::format("{}", std::this_thread::get_id()), "unknown"})
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NoErrorHandler {
 | 
			
		||||
    [[nodiscard]] static constexpr auto
 | 
			
		||||
    wrap(auto&& fn)
 | 
			
		||||
    {
 | 
			
		||||
        return std::forward<decltype(fn)>(fn);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace util::async::impl
 | 
			
		||||
							
								
								
									
										80
									
								
								unittests/util/MockExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								unittests/util/MockExecutionContext.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/MockOperation.hpp"
 | 
			
		||||
#include "util/MockStopToken.hpp"
 | 
			
		||||
#include "util/MockStrand.hpp"
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
struct MockExecutionContext {
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ValueType = util::Expected<T, util::async::ExecutionError>;
 | 
			
		||||
 | 
			
		||||
    using StopSource = MockStopSource;
 | 
			
		||||
    using StopToken = MockStopToken;
 | 
			
		||||
    using Strand = MockStrand;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using Operation = MockOperation<T>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using StoppableOperation = MockStoppableOperation<T>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ScheduledOperation = MockScheduledOperation<T>;
 | 
			
		||||
 | 
			
		||||
    MOCK_METHOD(Operation<util::async::impl::Any> const&, execute, (std::function<util::async::impl::Any()>), (const));
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        Operation<util::async::impl::Any> const&,
 | 
			
		||||
        execute,
 | 
			
		||||
        (std::function<util::async::impl::Any()>, std::optional<std::chrono::milliseconds>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        StoppableOperation<util::async::impl::Any> const&,
 | 
			
		||||
        execute,
 | 
			
		||||
        (std::function<util::async::impl::Any(util::async::AnyStopToken)>, std::optional<std::chrono::milliseconds>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        ScheduledOperation<util::async::impl::Any> const&,
 | 
			
		||||
        scheduleAfter,
 | 
			
		||||
        (std::chrono::milliseconds, std::function<util::async::impl::Any(util::async::AnyStopToken)>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        ScheduledOperation<util::async::impl::Any> const&,
 | 
			
		||||
        scheduleAfter,
 | 
			
		||||
        (std::chrono::milliseconds, std::function<util::async::impl::Any(util::async::AnyStopToken, bool)>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(MockStrand const&, makeStrand, (), (const));
 | 
			
		||||
    MOCK_METHOD(void, stop, (), (const));
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										44
									
								
								unittests/util/MockOperation.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								unittests/util/MockOperation.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
 | 
			
		||||
template <typename ValueType>
 | 
			
		||||
struct MockOperation {
 | 
			
		||||
    MOCK_METHOD(void, wait, (), (const));
 | 
			
		||||
    MOCK_METHOD(ValueType, get, (), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename ValueType>
 | 
			
		||||
struct MockStoppableOperation {
 | 
			
		||||
    MOCK_METHOD(void, requestStop, (), (const));
 | 
			
		||||
    MOCK_METHOD(void, wait, (), (const));
 | 
			
		||||
    MOCK_METHOD(ValueType, get, (), (const));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename ValueType>
 | 
			
		||||
struct MockScheduledOperation {
 | 
			
		||||
    MOCK_METHOD(void, cancel, (), (const));
 | 
			
		||||
    MOCK_METHOD(void, requestStop, (), (const));
 | 
			
		||||
    MOCK_METHOD(void, wait, (), (const));
 | 
			
		||||
    MOCK_METHOD(ValueType, get, (), (const));
 | 
			
		||||
    MOCK_METHOD(void, getToken, (), (const));
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										30
									
								
								unittests/util/MockStopToken.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								unittests/util/MockStopToken.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
 | 
			
		||||
struct MockStopSource {
 | 
			
		||||
    MOCK_METHOD(void, requestStop, (), ());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MockStopToken {
 | 
			
		||||
    MOCK_METHOD(bool, isStopRequested, (), (const));
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										63
									
								
								unittests/util/MockStrand.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								unittests/util/MockStrand.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/MockOperation.hpp"
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
#include "util/async/impl/Any.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
struct MockStrand {
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ValueType = util::Expected<T, util::async::ExecutionError>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using Operation = MockOperation<T>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using StoppableOperation = MockStoppableOperation<T>;
 | 
			
		||||
 | 
			
		||||
    MOCK_METHOD(Operation<util::async::impl::Any> const&, execute, (std::function<util::async::impl::Any()>), (const));
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        Operation<util::async::impl::Any> const&,
 | 
			
		||||
        execute,
 | 
			
		||||
        (std::function<util::async::impl::Any()>, std::optional<std::chrono::milliseconds>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        StoppableOperation<util::async::impl::Any> const&,
 | 
			
		||||
        execute,
 | 
			
		||||
        (std::function<util::async::impl::Any(util::async::AnyStopToken)>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
    MOCK_METHOD(
 | 
			
		||||
        StoppableOperation<util::async::impl::Any> const&,
 | 
			
		||||
        execute,
 | 
			
		||||
        (std::function<util::async::impl::Any(util::async::AnyStopToken)>, std::optional<std::chrono::milliseconds>),
 | 
			
		||||
        (const)
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										304
									
								
								unittests/util/async/AnyExecutionContextTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								unittests/util/async/AnyExecutionContextTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,304 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/MockExecutionContext.hpp"
 | 
			
		||||
#include "util/MockOperation.hpp"
 | 
			
		||||
#include "util/MockStrand.hpp"
 | 
			
		||||
#include "util/async/AnyExecutionContext.hpp"
 | 
			
		||||
#include "util/async/AnyOperation.hpp"
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/AnyStrand.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
using namespace ::testing;
 | 
			
		||||
 | 
			
		||||
struct AnyExecutionContextTests : Test {
 | 
			
		||||
    using StrandType = NiceMock<MockStrand>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using OperationType = NiceMock<MockOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using StoppableOperationType = NiceMock<MockStoppableOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using ScheduledOperationType = NiceMock<MockScheduledOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    NiceMock<MockExecutionContext> mockExecutionContext;
 | 
			
		||||
    AnyExecutionContext ctx{static_cast<MockExecutionContext&>(mockExecutionContext)};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithoutTokenAndVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any()>>())).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
    EXPECT_CALL(mockOp, get());
 | 
			
		||||
 | 
			
		||||
    auto op = ctx.execute([] { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithoutTokenAndVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any()>>()))
 | 
			
		||||
        .WillOnce([](auto&&) -> OperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = ctx.execute([] { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithStopTokenAndVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce(ReturnRef(mockOp));
 | 
			
		||||
    EXPECT_CALL(mockOp, get());
 | 
			
		||||
 | 
			
		||||
    auto op = ctx.execute([](auto) { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithStopTokenAndVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = ctx.execute([](auto) { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithStopTokenAndReturnValue)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto op = ctx.execute([](auto) -> int { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<int>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_EQ(op.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, ExecuteWithStopTokenAndReturnValueThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = ctx.execute([](auto) -> int { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, TimerCancellation)
 | 
			
		||||
{
 | 
			
		||||
    auto mockScheduledOp = ScheduledOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, cancel());
 | 
			
		||||
    EXPECT_CALL(
 | 
			
		||||
        mockExecutionContext,
 | 
			
		||||
        scheduleAfter(An<std::chrono::milliseconds>(), An<std::function<impl::Any(AnyStopToken)>>())
 | 
			
		||||
    )
 | 
			
		||||
        .WillOnce(ReturnRef(mockScheduledOp));
 | 
			
		||||
 | 
			
		||||
    auto timer = ctx.scheduleAfter(std::chrono::milliseconds{12}, [](auto) { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(timer), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    timer.cancel();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, TimerExecuted)
 | 
			
		||||
{
 | 
			
		||||
    auto mockScheduledOp = ScheduledOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(
 | 
			
		||||
        mockExecutionContext,
 | 
			
		||||
        scheduleAfter(An<std::chrono::milliseconds>(), An<std::function<impl::Any(AnyStopToken)>>())
 | 
			
		||||
    )
 | 
			
		||||
        .WillOnce([&mockScheduledOp](auto, auto&&) -> ScheduledOperationType<impl::Any> const& {
 | 
			
		||||
            return mockScheduledOp;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    auto timer = ctx.scheduleAfter(std::chrono::milliseconds{12}, [](auto) -> int { throw 0; });
 | 
			
		||||
 | 
			
		||||
    static_assert(std::is_same_v<decltype(timer), AnyOperation<int>>);
 | 
			
		||||
    EXPECT_EQ(timer.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, TimerWithBoolHandlerCancellation)
 | 
			
		||||
{
 | 
			
		||||
    auto mockScheduledOp = ScheduledOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, cancel());
 | 
			
		||||
    EXPECT_CALL(
 | 
			
		||||
        mockExecutionContext,
 | 
			
		||||
        scheduleAfter(An<std::chrono::milliseconds>(), An<std::function<impl::Any(AnyStopToken, bool)>>())
 | 
			
		||||
    )
 | 
			
		||||
        .WillOnce(ReturnRef(mockScheduledOp));
 | 
			
		||||
 | 
			
		||||
    auto timer = ctx.scheduleAfter(std::chrono::milliseconds{12}, [](auto, bool) { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(timer), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    timer.cancel();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, TimerWithBoolHandlerExecuted)
 | 
			
		||||
{
 | 
			
		||||
    auto mockScheduledOp = ScheduledOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(
 | 
			
		||||
        mockExecutionContext,
 | 
			
		||||
        scheduleAfter(An<std::chrono::milliseconds>(), An<std::function<impl::Any(AnyStopToken, bool)>>())
 | 
			
		||||
    )
 | 
			
		||||
        .WillOnce([&mockScheduledOp](auto, auto&&) -> ScheduledOperationType<impl::Any> const& {
 | 
			
		||||
            return mockScheduledOp;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    auto timer = ctx.scheduleAfter(std::chrono::milliseconds{12}, [](auto, bool) -> int { throw 0; });
 | 
			
		||||
 | 
			
		||||
    static_assert(std::is_same_v<decltype(timer), AnyOperation<int>>);
 | 
			
		||||
    EXPECT_EQ(timer.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get());
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>())).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([] { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>()))
 | 
			
		||||
        .WillOnce([](auto&&) -> OperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([] {}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithReturnValue)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>())).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([]() -> int { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<int>>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(op.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithReturnValueThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>()))
 | 
			
		||||
        .WillOnce([](auto&&) -> OperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([]() -> int { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithStopTokenAndVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get());
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _)).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([](auto) { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithStopTokenAndVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([](auto) { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithStopTokenAndReturnValue)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _)).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([](auto) -> int { throw 0; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<int>>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(op.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyExecutionContextTests, StrandExecuteWithStopTokenAndReturnValueThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockStrand = StrandType{};
 | 
			
		||||
    EXPECT_CALL(mockExecutionContext, makeStrand()).WillOnce(ReturnRef(mockStrand));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    static_assert(std::is_same_v<decltype(strand), AnyStrand>);
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([](auto) -> int { throw 0; }));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								unittests/util/async/AnyOperationTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								unittests/util/async/AnyOperationTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/MockOperation.hpp"
 | 
			
		||||
#include "util/async/AnyOperation.hpp"
 | 
			
		||||
#include "util/async/Error.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
using namespace ::testing;
 | 
			
		||||
 | 
			
		||||
struct AnyOperationTests : Test {
 | 
			
		||||
    using OperationType = MockOperation<util::Expected<impl::Any, ExecutionError>>;
 | 
			
		||||
    using ScheduledOperationType = MockScheduledOperation<util::Expected<impl::Any, ExecutionError>>;
 | 
			
		||||
 | 
			
		||||
    NaggyMock<OperationType> mockOp;
 | 
			
		||||
    NaggyMock<ScheduledOperationType> mockScheduledOp;
 | 
			
		||||
 | 
			
		||||
    AnyOperation<void> voidOp{impl::ErasedOperation(static_cast<OperationType&>(mockOp))};
 | 
			
		||||
    AnyOperation<int> intOp{impl::ErasedOperation(static_cast<OperationType&>(mockOp))};
 | 
			
		||||
    AnyOperation<void> scheduledVoidOp{impl::ErasedOperation(static_cast<ScheduledOperationType&>(mockScheduledOp))};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, VoidDataYieldsNoError)
 | 
			
		||||
{
 | 
			
		||||
    auto const noError = util::Expected<impl::Any, ExecutionError>(impl::Any{});
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(noError));
 | 
			
		||||
    auto res = voidOp.get();
 | 
			
		||||
    ASSERT_TRUE(res);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, GetIntData)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    auto res = intOp.get();
 | 
			
		||||
    EXPECT_EQ(res.value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, WaitCallPropagated)
 | 
			
		||||
{
 | 
			
		||||
    StrictMock<MockFunction<void()>> callback;
 | 
			
		||||
    EXPECT_CALL(callback, Call());
 | 
			
		||||
    EXPECT_CALL(mockOp, wait()).WillOnce([&] { callback.Call(); });
 | 
			
		||||
    voidOp.wait();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, CancelCallPropagated)
 | 
			
		||||
{
 | 
			
		||||
    StrictMock<MockFunction<void()>> callback;
 | 
			
		||||
    EXPECT_CALL(callback, Call());
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, cancel()).WillOnce([&] { callback.Call(); });
 | 
			
		||||
    scheduledVoidOp.cancel();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, RequestStopCallPropagated)
 | 
			
		||||
{
 | 
			
		||||
    StrictMock<MockFunction<void()>> callback;
 | 
			
		||||
    EXPECT_CALL(callback, Call());
 | 
			
		||||
    EXPECT_CALL(mockScheduledOp, requestStop()).WillOnce([&] { callback.Call(); });
 | 
			
		||||
    scheduledVoidOp.requestStop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, GetPropagatesError)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(util::Unexpected(ExecutionError{"tid", "Not good"})));
 | 
			
		||||
    auto res = intOp.get();
 | 
			
		||||
    ASSERT_FALSE(res);
 | 
			
		||||
    EXPECT_TRUE(res.error().message.ends_with("Not good"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyOperationTests, GetIncorrectDataReturnsError)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<double>(4.2)));
 | 
			
		||||
    auto res = intOp.get();
 | 
			
		||||
 | 
			
		||||
    ASSERT_FALSE(res);
 | 
			
		||||
    EXPECT_TRUE(res.error().message.ends_with("Bad any cast"));
 | 
			
		||||
    EXPECT_TRUE(std::string{res.error()}.ends_with("Bad any cast"));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								unittests/util/async/AnyStopTokenTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								unittests/util/async/AnyStopTokenTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
using namespace ::testing;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
struct FakeStopToken {
 | 
			
		||||
    bool isStopRequested_ = false;
 | 
			
		||||
    bool
 | 
			
		||||
    isStopRequested() const
 | 
			
		||||
    {
 | 
			
		||||
        return isStopRequested_;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
struct AnyStopTokenTests : public TestWithParam<bool> {};
 | 
			
		||||
 | 
			
		||||
INSTANTIATE_TEST_CASE_P(AnyStopTokenGroup, AnyStopTokenTests, ValuesIn({true, false}), [](auto const& info) {
 | 
			
		||||
    return info.param ? "true" : "false";
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
TEST_P(AnyStopTokenTests, CanCopy)
 | 
			
		||||
{
 | 
			
		||||
    AnyStopToken stopToken{FakeStopToken{GetParam()}};
 | 
			
		||||
    AnyStopToken token = stopToken;
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(token, stopToken);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_P(AnyStopTokenTests, IsStopRequestedCallPropagated)
 | 
			
		||||
{
 | 
			
		||||
    auto const flag = GetParam();
 | 
			
		||||
    AnyStopToken stopToken{FakeStopToken{flag}};
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(stopToken.isStopRequested(), flag);
 | 
			
		||||
    EXPECT_EQ(stopToken, flag);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								unittests/util/async/AnyStrandTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								unittests/util/async/AnyStrandTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/Expected.hpp"
 | 
			
		||||
#include "util/MockOperation.hpp"
 | 
			
		||||
#include "util/MockStrand.hpp"
 | 
			
		||||
#include "util/async/AnyOperation.hpp"
 | 
			
		||||
#include "util/async/AnyStopToken.hpp"
 | 
			
		||||
#include "util/async/AnyStrand.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
#include <any>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
using namespace ::testing;
 | 
			
		||||
 | 
			
		||||
struct AnyStrandTests : ::testing::Test {
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using OperationType = ::testing::NiceMock<MockOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    using StoppableOperationType = ::testing::NiceMock<MockStoppableOperation<T>>;
 | 
			
		||||
 | 
			
		||||
    ::testing::NaggyMock<MockStrand> mockStrand;
 | 
			
		||||
    AnyStrand strand{static_cast<MockStrand&>(mockStrand)};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithoutTokenAndVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>())).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([] {});
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithoutTokenAndVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = OperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any()>>()))
 | 
			
		||||
        .WillOnce([](auto&&) -> OperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([] {}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithStopTokenAndVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _)).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([](auto) {});
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<void>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(op.get());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithStopTokenAndVoidThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([](auto) {}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithStopTokenAndReturnValue)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _)).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([](auto) { return 42; });
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<int>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_EQ(op.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithStopTokenAndReturnValueThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW([[maybe_unused]] auto unused = strand.execute([](auto) { return 42; }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithTimeoutAndStopTokenAndReturnValue)
 | 
			
		||||
{
 | 
			
		||||
    auto mockOp = StoppableOperationType<impl::Any>{};
 | 
			
		||||
    EXPECT_CALL(mockOp, get()).WillOnce(Return(std::make_any<int>(42)));
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _)).WillOnce(ReturnRef(mockOp));
 | 
			
		||||
 | 
			
		||||
    auto op = strand.execute([](auto) { return 42; }, std::chrono::milliseconds{1});
 | 
			
		||||
    static_assert(std::is_same_v<decltype(op), AnyOperation<int>>);
 | 
			
		||||
 | 
			
		||||
    ASSERT_EQ(op.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_F(AnyStrandTests, ExecuteWithTimoutAndStopTokenAndReturnValueThrowsException)
 | 
			
		||||
{
 | 
			
		||||
    EXPECT_CALL(mockStrand, execute(An<std::function<impl::Any(AnyStopToken)>>(), _))
 | 
			
		||||
        .WillOnce([](auto&&, auto) -> StoppableOperationType<impl::Any> const& { throw 0; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_ANY_THROW(
 | 
			
		||||
        [[maybe_unused]] auto unused = strand.execute([](auto) { return 42; }, std::chrono::milliseconds{1})
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										240
									
								
								unittests/util/async/AsyncExecutionContextTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								unittests/util/async/AsyncExecutionContextTests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of clio: https://github.com/XRPLF/clio
 | 
			
		||||
    Copyright (c) 2024, the clio developers.
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and distribute this software for any
 | 
			
		||||
    purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include "util/async/context/BasicExecutionContext.hpp"
 | 
			
		||||
#include "util/async/context/SyncExecutionContext.hpp"
 | 
			
		||||
 | 
			
		||||
#include <gmock/gmock.h>
 | 
			
		||||
#include <gtest/gtest.h>
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <semaphore>
 | 
			
		||||
#include <stdexcept>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
using namespace util::async;
 | 
			
		||||
using ::testing::Types;
 | 
			
		||||
 | 
			
		||||
using ExecutionContextTypes = Types<CoroExecutionContext, PoolExecutionContext, SyncExecutionContext>;
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct ExecutionContextTests : public ::testing::Test {
 | 
			
		||||
    using ExecutionContextType = T;
 | 
			
		||||
    ExecutionContextType ctx{2};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TYPED_TEST_CASE(ExecutionContextTests, ExecutionContextTypes);
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, execute)
 | 
			
		||||
{
 | 
			
		||||
    auto res = this->ctx.execute([]() { return 42; });
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, executeVoid)
 | 
			
		||||
{
 | 
			
		||||
    auto value = 0;
 | 
			
		||||
    auto res = this->ctx.execute([&value]() { value = 42; });
 | 
			
		||||
 | 
			
		||||
    res.wait();
 | 
			
		||||
    ASSERT_EQ(value, 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, executeStdException)
 | 
			
		||||
{
 | 
			
		||||
    auto res = this->ctx.execute([]() { throw std::runtime_error("test"); });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("test"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("test"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, executeUnknownException)
 | 
			
		||||
{
 | 
			
		||||
    auto res = this->ctx.execute([]() { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("unknown"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("unknown"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// note: this fails on pool context with 1 thread
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, executeWithTimeout)
 | 
			
		||||
{
 | 
			
		||||
    auto res = this->ctx.execute(
 | 
			
		||||
        [](auto stopRequested) {
 | 
			
		||||
            while (not stopRequested)
 | 
			
		||||
                ;
 | 
			
		||||
            return 42;
 | 
			
		||||
        },
 | 
			
		||||
        std::chrono::milliseconds{1}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, timer)
 | 
			
		||||
{
 | 
			
		||||
    auto res =
 | 
			
		||||
        this->ctx.scheduleAfter(std::chrono::milliseconds(1), []([[maybe_unused]] auto stopRequested, auto cancelled) {
 | 
			
		||||
            if (not cancelled)
 | 
			
		||||
                return 42;
 | 
			
		||||
            return 0;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, timerWithStopToken)
 | 
			
		||||
{
 | 
			
		||||
    auto res = this->ctx.scheduleAfter(std::chrono::milliseconds(1), [](auto stopRequested) {
 | 
			
		||||
        while (not stopRequested)
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
        return 42;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    res.requestStop();
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, timerCancel)
 | 
			
		||||
{
 | 
			
		||||
    auto value = 0;
 | 
			
		||||
    std::binary_semaphore sem{0};
 | 
			
		||||
 | 
			
		||||
    auto res = this->ctx.scheduleAfter(
 | 
			
		||||
        std::chrono::milliseconds(10),
 | 
			
		||||
        [&value, &sem]([[maybe_unused]] auto stopRequested, auto cancelled) {
 | 
			
		||||
            if (cancelled)
 | 
			
		||||
                value = 42;
 | 
			
		||||
 | 
			
		||||
            sem.release();
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    res.cancel();
 | 
			
		||||
    sem.acquire();
 | 
			
		||||
    EXPECT_EQ(value, 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, timerStdException)
 | 
			
		||||
{
 | 
			
		||||
    auto res =
 | 
			
		||||
        this->ctx.scheduleAfter(std::chrono::milliseconds(1), []([[maybe_unused]] auto stopRequested, auto cancelled) {
 | 
			
		||||
            if (not cancelled)
 | 
			
		||||
                throw std::runtime_error("test");
 | 
			
		||||
            return 0;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("test"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("test"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, timerUnknownException)
 | 
			
		||||
{
 | 
			
		||||
    auto res =
 | 
			
		||||
        this->ctx.scheduleAfter(std::chrono::milliseconds(1), []([[maybe_unused]] auto stopRequested, auto cancelled) {
 | 
			
		||||
            if (not cancelled)
 | 
			
		||||
                throw 0;
 | 
			
		||||
            return 0;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("unknown"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("unknown"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, strand)
 | 
			
		||||
{
 | 
			
		||||
    auto strand = this->ctx.makeStrand();
 | 
			
		||||
    auto res = strand.execute([] { return 42; });
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, strandStdException)
 | 
			
		||||
{
 | 
			
		||||
    auto strand = this->ctx.makeStrand();
 | 
			
		||||
    auto res = strand.execute([]() { throw std::runtime_error("test"); });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("test"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("test"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, strandUnknownException)
 | 
			
		||||
{
 | 
			
		||||
    auto strand = this->ctx.makeStrand();
 | 
			
		||||
    auto res = strand.execute([]() { throw 0; });
 | 
			
		||||
 | 
			
		||||
    auto const err = res.get().error();
 | 
			
		||||
    EXPECT_TRUE(err.message.ends_with("unknown"));
 | 
			
		||||
    EXPECT_TRUE(std::string{err}.ends_with("unknown"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// note: this fails on pool context with 1 thread
 | 
			
		||||
TYPED_TEST(ExecutionContextTests, strandWithTimeout)
 | 
			
		||||
{
 | 
			
		||||
    auto strand = this->ctx.makeStrand();
 | 
			
		||||
    auto res = strand.execute(
 | 
			
		||||
        [](auto stopRequested) {
 | 
			
		||||
            while (not stopRequested)
 | 
			
		||||
                ;
 | 
			
		||||
            return 42;
 | 
			
		||||
        },
 | 
			
		||||
        std::chrono::milliseconds{1}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    EXPECT_EQ(res.get().value(), 42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
using NoErrorHandlerSyncExecutionContext = BasicExecutionContext<
 | 
			
		||||
    impl::SameThreadContext,
 | 
			
		||||
    impl::BasicStopSource,
 | 
			
		||||
    impl::SyncDispatchStrategy,
 | 
			
		||||
    impl::SelfContextProvider,
 | 
			
		||||
    impl::NoErrorHandler>;
 | 
			
		||||
 | 
			
		||||
TEST(NoErrorHandlerSyncExecutionContextTests, executeStdException)
 | 
			
		||||
{
 | 
			
		||||
    auto ctx = NoErrorHandlerSyncExecutionContext{};
 | 
			
		||||
    EXPECT_THROW(ctx.execute([] { throw std::runtime_error("test"); }).wait(), std::runtime_error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(NoErrorHandlerSyncExecutionContextTests, executeUnknownException)
 | 
			
		||||
{
 | 
			
		||||
    auto ctx = NoErrorHandlerSyncExecutionContext{};
 | 
			
		||||
    EXPECT_ANY_THROW(ctx.execute([] { throw 0; }).wait());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(NoErrorHandlerSyncExecutionContextTests, executeStdExceptionInStrand)
 | 
			
		||||
{
 | 
			
		||||
    auto ctx = NoErrorHandlerSyncExecutionContext{};
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    EXPECT_THROW(strand.execute([] { throw std::runtime_error("test"); }).wait(), std::runtime_error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(NoErrorHandlerSyncExecutionContextTests, executeUnknownExceptionInStrand)
 | 
			
		||||
{
 | 
			
		||||
    auto ctx = NoErrorHandlerSyncExecutionContext{};
 | 
			
		||||
    auto strand = ctx.makeStrand();
 | 
			
		||||
    EXPECT_ANY_THROW(strand.execute([] { throw 0; }).wait());
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user