diff --git a/clang-format b/.clang-format similarity index 92% rename from clang-format rename to .clang-format index e1e1295d..e11dd9b8 100644 --- a/clang-format +++ b/.clang-format @@ -51,5 +51,10 @@ SpacesInContainerLiterals: 'true' SpacesInParentheses: 'false' SpacesInSquareBrackets: 'false' Standard: Cpp11 - +ColumnLimit: 165 +IndentWidth: 4 +BinPackArguments: 'false' +BinPackParameters: 'false' +AllowAllArgumentsOnNextLine: 'false' +NamespaceIndentation: All ... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d1a750bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: Continuous Integration + +on: + push: + branches: + - master + + pull_request: + branches: + - master + + workflow_dispatch: ~ + +env: + CMAKE_VERSION: 3.18.4 + NINJA_VERSION: 1.10.1 + CTEST_OUTPUT_ON_FAILURE: 1 + NINJA_STATUS: '[%f/%t %o/sec] ' + +jobs: + tests: + strategy: + matrix: + conf: + - name: Ubuntu (Clang 10 - TSAN) + os: ubuntu-20.04 + cc: clang-10 + cxx: clang++-10 + tsan: YES + + - name: Ubuntu (Clang 10 - no TSAN) + os: ubuntu-20.04 + cc: clang-10 + cxx: clang++-10 + tsan: NO + + - name: macOS (Clang 10 - no TSAN) + os: macos-latest + cc: clang + cxx: clang++ + tsan: NO + + - name: Windows (Visual Studio Enterprise 2019) + os: windows-latest + cc: cl + cxx: cl + tsan: NO + + name: ${{ matrix.conf.name }} + + runs-on: ${{ matrix.conf.os }} + + env: + CC: ${{ matrix.conf.cc }} + CXX: ${{ matrix.conf.cxx }} + + steps: + - uses: actions/checkout@v1 + + - uses: friendlyanon/fetch-core-count@v1 + id: cores + + - run: cmake -E make_directory build/tools + + - name: Install CMake and Ninja + id: tools + working-directory: build/tools + run: cmake -D RUNNER_OS=${{ runner.os }} + -P ../../cmake/ciToolsUpdate.cmake + + - name: Combine CI variables + id: args + shell: cmake -P {0} + run: > + message([==[::set-output name=args::${{ matrix.conf.os }} + "${{ steps.tools.outputs.cmake }}" + "${{ steps.tools.outputs.ninja }}" + ${{ steps.cores.outputs.plus_one }}]==]) + + - name: Build examples + run: cmake -P cmake/ciBuild.cmake -- example build/example + ${{ steps.args.outputs.args }} + + - name: Build tests + id: build_tests + continue-on-error: ${{ startsWith(matrix.conf.os, 'macos') }} + run: cmake -P cmake/ciBuild.cmake -- test build/test + ${{ steps.args.outputs.args }} + -D ENABLE_THREAD_SANITIZER:BOOL=${{ matrix.conf.tsan }} + + - name: Run tests + continue-on-error: ${{ startsWith(matrix.conf.os, 'macos') }} + if: steps.build_tests.outcome == 'success' + working-directory: build/test + shell: cmake -P {0} + run: > + include(../../cmake/exec.cmake) + + exec("${{ steps.tools.outputs.ctest }}" -C Release -V + -j ${{ steps.cores.outputs.plus_one }}) diff --git a/.gitignore b/.gitignore index aa0863db..a614c2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -37,10 +37,14 @@ # End of https://www.toptal.com/developers/gitignore/api/c++ -#Others +# Others .vs -#Specific directories -/build -/msvc_intermediate +# Specific directories +build/ +msvc_intermediate/ out/ + +# CLion +.idea/ +**/cmake-build-*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a91efb16..20065aa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,113 @@ cmake_minimum_required(VERSION 3.16) -project(concurrencpp) -set(CMAKE_CXX_STANDARD 20) +project(concurrencpp + VERSION 0.0.8 + LANGUAGES CXX) -if (MSVC) - add_compile_options(/W3) -else() - add_compile_options(-Wall -Wextra) +include(cmake/coroutineOptions.cmake) - option(CONCURRENCPP_ENABLE_TSAN "Enables thread-sanitizer checks" OFF) - if (CONCURRENCPP_ENABLE_TSAN) - add_compile_options(-fsanitize=thread) - add_link_options(-fsanitize=thread) - endif() +# ---- Warning guard ---- + +# Protect dependents from this project's warnings if the guard isn't disabled +set(concurrencpp_warning_guard "SYSTEM") +if(concurrencpp_INCLUDE_WITHOUT_SYSTEM) + set(concurrencpp_warning_guard "") endif() -add_subdirectory(concurrencpp) -add_subdirectory(tests) -add_subdirectory(sandbox) -add_subdirectory(examples) +# ---- Declare library ---- -if (CONCURRENCPP_ENABLE_TSAN) - add_subdirectory(thread_sanitizers) -endif() +set(concurrencpp_sources + source/executors/executor.cpp + source/executors/manual_executor.cpp + source/executors/thread_executor.cpp + source/executors/thread_pool_executor.cpp + source/executors/worker_thread_executor.cpp + source/results/promises.cpp + source/results/result_core.cpp + source/runtime/runtime.cpp + source/threads/thread.cpp + source/timers/timer.cpp + source/timers/timer_queue.cpp) + +set(concurrencpp_headers + include/concurrencpp/concurrencpp.h + include/concurrencpp/errors.h + include/concurrencpp/forward_declerations.h + include/concurrencpp/platform_defs.h + include/concurrencpp/executors/constants.h + include/concurrencpp/executors/derivable_executor.h + include/concurrencpp/executors/executor.h + include/concurrencpp/executors/executor_all.h + include/concurrencpp/executors/inline_executor.h + include/concurrencpp/executors/manual_executor.h + include/concurrencpp/executors/thread_executor.h + include/concurrencpp/executors/thread_pool_executor.h + include/concurrencpp/executors/worker_thread_executor.h + include/concurrencpp/results/constants.h + include/concurrencpp/results/executor_exception.h + include/concurrencpp/results/make_result.h + include/concurrencpp/results/promises.h + include/concurrencpp/results/result.h + include/concurrencpp/results/result_awaitable.h + include/concurrencpp/results/result_core.h + include/concurrencpp/results/result_fwd_declerations.h + include/concurrencpp/results/when_result.h + include/concurrencpp/runtime/constants.h + include/concurrencpp/runtime/runtime.h + include/concurrencpp/threads/thread.h + include/concurrencpp/timers/constants.h + include/concurrencpp/timers/timer.h + include/concurrencpp/timers/timer_queue.h + include/concurrencpp/utils/bind.h) + +add_library(concurrencpp STATIC ${concurrencpp_headers} ${concurrencpp_sources}) +add_library(concurrencpp::concurrencpp ALIAS concurrencpp) + +target_include_directories(concurrencpp + ${concurrencpp_warning_guard} + PUBLIC + "$") + +target_compile_features(concurrencpp PUBLIC cxx_std_20) + +target_coroutine_options(concurrencpp) + +find_library(LIBRT NAMES rt DOC "Path to the Real Time shared library") +target_link_libraries(concurrencpp PUBLIC "$<$:${LIBRT}>") + +# ---- Install ---- + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +set(concurrencpp_directory "concurrencpp-${PROJECT_VERSION}") +set(concurrencpp_include_directory "${CMAKE_INSTALL_INCLUDEDIR}/${concurrencpp_directory}") + +install(TARGETS concurrencpp + EXPORT concurrencppTargets + ARCHIVE # + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + COMPONENT concurrencpp_Development + INCLUDES # + DESTINATION "${concurrencpp_include_directory}") + +set(concurrencpp_install_cmakedir + "${CMAKE_INSTALL_LIBDIR}/cmake/${concurrencpp_directory}") + +write_basic_package_version_file( + concurrencppConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMinorVersion + ARCH_INDEPENDENT) + +install(EXPORT concurrencppTargets + NAMESPACE concurrencpp:: + DESTINATION "${concurrencpp_install_cmakedir}") + +install(FILES + "${PROJECT_SOURCE_DIR}/cmake/concurrencppConfig.cmake" + "${PROJECT_BINARY_DIR}/concurrencppConfigVersion.cmake" + DESTINATION "${concurrencpp_install_cmakedir}") + +install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" + DESTINATION "${concurrencpp_include_directory}") diff --git a/README.md b/README.md index d92c5f11..07c167d7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ - # concurrencpp, the C++ concurrency library -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Latest Release](https://img.shields.io/github/v/release/David-Haim/concurrencpp.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + concurrencpp allows applications to write asynchronous code easily and safely by using executors and coroutines. By using concurrencpp applications can break down big procedures that need to be processed asynchronously into smaller tasks that run concurrently and work in a co-operative manner to achieve the wanted result. @@ -17,18 +17,16 @@ concurrencpp main advantages are: * Applications automatically scale-up to use all hardware processors (cores). concurrencpp is task-centric. A task is an asynchronous operation. Tasks provide higher level of abstraction for concurrent code than traditional thread-centric approaches. Tasks can be chained together, meaning that tasks pass their asynchronous result -from one to another, where the result of one task is used as if it were a parameter or an intermediate value of another ongoing task. -In concurrencpp, the concept of tasks is represented by coroutines. This allows tasks to be suspended, waiting for other tasks to finish, and chained using `co_await`, and thus solving the consumer-producer problem elegantly by giving the concurrent code a synchronous look. +from one to another, where the result of one task is used as if it were a parameter or an intermediate value of another ongoing task. In concurrencpp, the concept of tasks is represented by coroutines. This allows tasks to be suspended, waiting for other tasks to finish, and chained using `co_await`, and thus solving the consumer-producer problem elegantly by giving the concurrent code a synchronous look. -concurrencpp is built around the RAII concept. In order to use tasks and executors, applications create a `runtime` instance in the beginning of the `main` function. The runtime is then used to acquire existing executors and register new user-defined executors. -Executors are used to schedule new tasks to run, and they might return a `result` object that can be used to marshal the asynchronous result to another task that acts as its consumer. +concurrencpp is built around the RAII concept. In order to use tasks and executors, applications create a `runtime` instance in the beginning of the `main` function. The runtime is then used to acquire existing executors and register new user-defined executors. Executors are used to schedule new tasks to run, and they might return a `result` object that can be used to marshal the asynchronous result to another task that acts as its consumer. Results can be awaited and resolved in a non-blocking manner, and even switch the underlying executor in the process. - When the runtime is destroyed, it iterates over every stored executor and calls its `shutdown` method. Every executor then exits gracefully. Unscheduled tasks are destroyed, and attempts to create new tasks will throw an exception. +When the runtime is destroyed, it iterates over every stored executor and calls its `shutdown` method. Every executor then exits gracefully. Unscheduled tasks are destroyed, and attempts to create new tasks will throw an exception. #### *"Hello world" program using concurrencpp:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include int main() { @@ -47,7 +45,7 @@ In this basic example, we created a runtime object, then we acquired the thread #### *Concurrent even-number counting:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include #include @@ -58,60 +56,67 @@ In this basic example, we created a runtime object, then we acquired the thread using namespace concurrencpp; std::vector make_random_vector() { - std::vector vec(64 * 1'024); + std::vector vec(64 * 1'024); - std::srand(std::time(nullptr)); - for (auto& i : vec) { - i = ::rand(); - } + std::srand(std::time(nullptr)); + for (auto& i : vec) { + i = ::rand(); + } - return vec; + return vec; } -result count_even(std::shared_ptr tpe, const std::vector& vector) { - const auto vecor_size = vector.size(); - const auto concurrency_level = tpe->max_concurrency_level(); - const auto chunk_size = vecor_size / concurrency_level; - - std::vector> chunk_count; - - for (auto i = 0; i < concurrency_level; i++) { - const auto chunk_begin = i * chunk_size; - const auto chunk_end = chunk_begin + chunk_size; - auto result = tpe->submit([&vector, chunk_begin, chunk_end]() -> size_t { - return std::count_if( - vector.begin() + chunk_begin, - vector.begin() + chunk_end, - [](auto i) { return i % 2 == 0; }); - }); +result count_even(std::shared_ptr tpe, const std::vector& vector) { + const auto vecor_size = vector.size(); + const auto concurrency_level = tpe->max_concurrency_level(); + const auto chunk_size = vecor_size / concurrency_level; - chunk_count.emplace_back(std::move(result)); - } + std::vector> chunk_count; - size_t total_count = 0; + for (auto i = 0; i < concurrency_level; i++) { + const auto chunk_begin = i * chunk_size; + const auto chunk_end = chunk_begin + chunk_size; + auto result = tpe->submit([&vector, chunk_begin, chunk_end]() -> size_t { + return std::count_if(vector.begin() + chunk_begin, vector.begin() + chunk_end, [](auto i) { + return i % 2 == 0; + }); + }); - for(auto& result : chunk_count) { - total_count += co_await result; - } + chunk_count.emplace_back(std::move(result)); + } + + size_t total_count = 0; + + for (auto& result : chunk_count) { + total_count += co_await result; + } - std::cout << "there are " << total_count << " even numbers in the vector" << std::endl; + co_return total_count; } int main() { - concurrencpp::runtime runtime; - const auto vector = make_random_vector(); - auto result = count_even(runtime.thread_pool_executor(), vector); - result.get(); - return 0; + concurrencpp::runtime runtime; + const auto vector = make_random_vector(); + auto result = count_even(runtime.thread_pool_executor(), vector); + const auto total_count = result.get(); + std::cout << "there are " << total_count << " even numbers in the vector" << std::endl; + return 0; } ``` -In this example, we again start the program by creating a runtime object. We create a vector filled with random numbers, then we acquire the `thread_pool_executor` from the runtime and call `count_even`. `count_even` is a coroutine (task) that spawns more coroutines and `co_await`s for them to finish inside. `max_concurrency_level` returns the maximum amount of workers that the executor supports, In the threadpool executor case, the number of workers is calculated from the number of cores. We then partition the array to match the number of workers and send every chunk to be processed in its own task. Asynchronously, the workers count how many even numbers each chunk contains, and `co_return` the result. `count_even` iterates every result, pulling the asynchronous count by using `co_await`. The final result is then printed and `count_even` finishes. `main` which was blocked by calling `get` is unblocked and the program terminates gracefully. +In this example, we start the program by creating a runtime object. We create a vector filled with random numbers, then we acquire the `thread_pool_executor` from the runtime and call `count_even`. +`count_even` is a coroutine that spawns more coroutines and `co_await`s for them to finish inside. +`max_concurrency_level` returns the maximum amount of workers that the executor supports, In the threadpool executor case, the number of workers is calculated from the number of cores. +We then partition the array to match the number of workers and send every chunk to be processed in its own task. +Asynchronously, the workers count how many even numbers each chunk contains, and `co_return` the result. +`count_even` sums every result by pulling the count using `co_await`, the final result is then `co_return`ed. +The main thread, which was blocked by calling `get` is unblocked and the total count is returned. +main prints the number of even numbers and the program terminates gracefully. ---- ### Table of contents - +* [concurrencpp coroutines](#concurrencpp-coroutines) * [Executors](#executors) * [`executor` API](#executor-api) * [Executor types](#executor-types) @@ -137,26 +142,40 @@ In this example, we again start the program by creating a runtime object. We cre * [`runtime` API](#runtime-api) * [Creating user-defined executors](#creating-user-defined-executors) * [Using a user-defined executor example](#example-using-a-user-defined-executor) -* [Supported platforms](#supported-platforms) +* [Supported platforms and tools](#supported-platforms-and-tools) ---- +### concurrencpp coroutines + +concurrencpp coroutines are eager. they start to run the moment they are invoked (as opposed to lazy coroutines, which start to run only when `co_await`ed). concurrencpp coroutines can return any of `concurrencpp::result` or `concurrencpp::null_result`. + +`concurrencpp::result` tells the coroutine to marshal the returned value or the thrown exception while `concurrencpp::null_result` tells the coroutine to drop and ignore any of them. + +Coroutines can be created and scheduled using concurrencpp in many ways. The easiest way to create a coroutine is to submit a callable to one of the concurrencpp executors. Executors then wrap the callable in a thin coroutine layer and schedule it to run. +Coroutines can also be created from scratch - when a function returns any of `concurrencpp::result` or `concurrencpp::null_result`and contains at least one `co_await` or `co_return` in it's body, the function is a concurrencpp coroutine. In our count-even example above, `count_even` is such coroutine. We first spawned `count_even`, then inside it the threadpool executor spawned more child coroutines, that were eventually joined using `co_await`. + +Coroutines can start to run synchronously, in the caller thread. This kind of coroutines is called "regular coroutines". +Concurrencpp coroutines can also start to run in parallel, inside a given executor, this kind of coroutines is called "parallel coroutines". + ### Executors -In the context of concurrencpp, an executor is an object that is able to schedule and run coroutines. +A concurrencpp executor is an object that is able to schedule and run coroutines. Executors simplify the work of managing resources such as threads, thread pools and task queues by decoupling them away from application code. Executors provide a unified way of scheduling and executing coroutines, since they all extend `concurrencpp::executor`. -The main power of concurrencpp executors comes from their ability to schedule user provided callables by turning them into coroutines first, scheduling them to run asynchronously, and finally marshal the asynchronous result (or exception) back to the caller. #### `executor` API ```cpp class executor { /* - Initializes a new executor and gives it a name. + Initializes a new executor and gives it a name. */ executor(std::string_view name); + /* + Destroys this executor. + */ virtual ~executor() noexcept = default; /* @@ -177,23 +196,23 @@ class executor { virtual void enqueue(std::span> tasks) = 0; /* - The maximum amount of real OS threads this executor supports. - The actual number might be smaller than this. - returns numeric_limits::max if the executor does not have a limit for OS threads + Returns the maximum count of real OS threads this executor supports. + The actual count of threads this executor is running might be smaller than this number. + returns numeric_limits::max if the executor does not have a limit for OS threads. */ virtual int max_concurrency_level() const noexcept = 0; /* - Returns true if shutdown was called before, false otherwise + Returns true if shutdown was called before, false otherwise. */ virtual bool shutdown_requested() const noexcept = 0; /* Shuts down the executor: - - Tells underlying threads to exit their work loop - - Destroyed unexecuted coroutines + - Tells underlying threads to exit their work loop and joins them. + - Destroyes unexecuted coroutines. - Makes subsequent calls to enqueue, post, submit, bulk_post and - bulk_submit to throw concurrencpp::errors::executor_shutdown exception + bulk_submit to throw concurrencpp::errors::executor_shutdown exception. - Makes shutdown_requested return true. */ virtual void shutdown() noexcept = 0; @@ -211,7 +230,7 @@ class executor { Throws errors::executor_shutdown exception if shutdown has been called before. */ template - auto submit(callable_type&& callable, argument_types&& ... arguments); + result submit(callable_type&& callable, argument_types&& ... arguments); /* Turns an array of callables into an array of suspended coroutines and schedules them to run in this executor using enqueue. @@ -222,7 +241,6 @@ class executor { /* Like bulk_post, but returns an array of result objects that marshal the asynchronous results. - Order is preserved. Throws errors::executor_shutdown exception if shutdown has been called before. */ template @@ -235,8 +253,8 @@ class executor { As mentioned above, concurrencpp provides commonly used executors. These executor types are: * **thread pool executor** - a general purpose executor that maintains a pool of threads. -The thread pool executor is suitable for relatively short cpu-bound tasks that don't block. Applications are encouraged to use this executor as the default executor for non-blocking functions. -The concurrencpp thread pool provides dynamic thread injection and dynamic work balancing for its enqueued tasks. +The thread pool executor is suitable for short cpu-bound tasks that don't block. Applications are encouraged to use this executor as the default executor for non-blocking tasks. +The concurrencpp thread pool provides dynamic thread injection and dynamic work balancing. * **blocking executor** - a threadpool executor with a larger pool of threads. Suitable for launching short blocking tasks like file io and db queries. @@ -253,29 +271,51 @@ This executor is good for long running tasks, like objects that run a work loop, #### Using executors -The bare mechanism of an executor is encapsulated in its `enqueue` method. This method enqueues a suspended coroutine for execution and has two flavors: one flavor that receives a single `coroutine_handle<>` as an argument, and another that receives a `span>`. The second flavor is used to enqueue a batch of suspended coroutines. This allows better scheduling heuristics and decreased contention. +The bare mechanism of an executor is encapsulated in its `enqueue` method. +This method enqueues a suspended coroutine for execution and has two overloads: +One overload receives a single `coroutine_handle<>` as an argument, and another that receives a `span>`. +The second overload is used to enqueue a batch of suspended coroutines. This allows better scheduling heuristics and decreased contention. -Of course, Applications don't need to use these low-level methods by themselves. `concurrencpp::executor` provides an API for scheduling non-coroutines by converting them to a suspended coroutine first and then scheduling them to run. +Applications don't have to rely on `enqueue` alone, `concurrencpp::executor` provides an API for scheduling non-coroutines by converting them to a suspended coroutine first. Applications can request executors to return a result object that marshals the asynchronous result of the provided callable. This is done by calling `executor::submit` and `execuor::bulk_submit`. `submit` gets a callable, and returns a result object. `executor::bulk_submit` gets a `span` of callables and returns a `vector`of result objects in a similar way `submit` works. -In many cases, applications are not interested in the asynchronous value or exception. In this case, applications can use `executor:::post` and `executor::bulk_post` to schedule a callable or a `span` of callables to execute, but also tells the task to drop any returned value or thrown exception. Not marshaling the asynchronous result is faster than marshaling, but then we have no way of knowing the status of the ongoing task. +In many cases, applications are not interested in the asynchronous value or exception. In this case, applications can use `executor:::post` and `executor::bulk_post` to schedule a callable or a `span` of callables to execute, but also tells the task to drop any returned value or thrown exception. Not marshaling the asynchronous result is faster than marshaling, but then we have no way of knowing the status or the result of the ongoing task. `post`, `bulk_post`, `submit` and `bulk_submit` use `enqueue` behind the scenes for the underlying scheduling mechanism. ### Result objects Asynchronous values and exceptions can be consumed using the concurrencpp result objects. -A result object is a conduit for the asynchronous coroutine result, like `std::future`. +A result object is a conduit for the asynchronous result, like `std::future`. When a coroutine finishes execution, it either returns a valid value or throws an exception. In either case, this asynchronous result is marshaled to the consumer of the result object. -The result state therefore, vary from `idle` (the asynchronous result or exception aren't ready yet) to `value` (the coroutine terminated by returning a valid value) to `exception` (the coroutine terminated by throwing an exception). -Result objects can be polled for their state, waited, resolved or awaited. -Awaiting a result object by using `co_await` (and by doing so, turning the current function into a coroutine as well) is the preferred way of consuming result objects. +The result status therefore, vary from `idle` (the asynchronous result or exception aren't ready yet) to `value` (the coroutine terminated by returning a valid value) to `exception` (the coroutine terminated by throwing an exception). Result objects are a move-only type, and as such, they cannot be used after their content was moved to another result object. In this case, the result object is considered to be empty and attempts to call any method other than `operator bool` and `operator = ` will throw. After the asynchronous result has been pulled out of the result object (by calling `get`, `await` or `await_via`), the result object becomes empty. Emptiness can be tested with `operator bool`. -concurrencpp also provide the `null_result` type. This type can be used as a return type from a parallel coroutine. In this case, any returned value or thrown exception will be dropped. It's logically equivalent of returning `void`. +Results can be polled, waited, awaited or resolved. + +Result objects can be polled for their status by calling `result::status`. + +Results can be waited by calling any of `result::wait`, `result::wait_for`, `result::wait_until` or `result::get`. +Waiting a result is a blocking operation (in the case the asynchronous result is not ready), and will suspend the entire thread of execution waiting for the asynchronous result to become available. Waiting operations are generally discouraged and only allowed in root-level tasks and in contexts which allow it, like blocking the main thread waiting for the rest of the application to finish gracefully, or using `concurrencpp::blocking_executor` or `concurrencpp::thread_executor`. + +Awaiting a result means to suspend the current coroutine until the asynchronous result is ready. If a valid value was returned from the coroutine, it is returend from the result object. If the coroutine threw an exception, it is re-thrown. +At the moment of awaiting, if the result is already ready, the coroutine resumes immediately. Otherwise, it is resumed by the thread that sets the asynchronous result or exception. + +The behaviour of awaiting a result object can be further fine tuned by using `await_via`. +This method accepts an executor and a boolean flag (`force_rescheduling`). +If, at the moment of awaiting, the result is already ready, the behavior depends on the value of `force_rescheduling`. +If `force_rescheduling` is true, the coroutine is forcefully suspended and resumed inside the given executor. +If `force_rescheduling` is false, the coroutine is resumed immediately in the calling thread. +If the asynchronous result is not ready at the moment of awaiting, the coroutine resumed after the result is set, by scheduling it to run in the given exector. + +Resolving a result is similar to awaiting it. The different is that the `co_await` expression will return the result object itself, +in a non empty form, in a ready state. The asynchronous result can then be pulled by using `get` or `co_await`. +Just like `await_via`, `resolve_via` fine tunes the control flow of the coroutine by passing an executor and a flag suggesting how to behave when the result is already ready. + +Awaiting a result object by using `co_await` (and by doing so, turning the current function into a coroutine as well) is the preferred way of consuming result objects. #### `result` API @@ -288,6 +328,7 @@ class result{ /* Destroyes the result. Associated tasks are not cancelled. + The destructor does not block waiting for the asynchronous result to become ready. */ ~result() noexcept = default; @@ -302,19 +343,20 @@ class result{ result& operator = (result&& rhs) noexcept = default; /* - Returns true if this is a non-empty result. Applications must not use this object if static_cast(*this) is false. + Returns true if this is a non-empty result. + Applications must not use this object if this->operator bool() is false. */ operator bool() const noexcept; /* - Queries the status of *this. The return value might be any of result_status::idle, - result_status::value or result_status::exception. + Queries the status of *this. + The return value is any of result_status::idle, result_status::value or result_status::exception. Throws concurrencpp::errors::empty_result if *this is empty. */ - result_status status() const ; + result_status status() const; /* - Blocks the current thread of execution until this result is ready, when its status is either result_status::value or result_status::exception. + Blocks the current thread of execution until this result is ready, when status() != result_status::idle. Throws concurrencpp::errors::empty_result if *this is empty. */ void wait(); @@ -327,14 +369,14 @@ class result{ result_status wait_for(std::chrono::duration duration); /* - Blocks until this result is ready or timeout_time is reached. Returns the status of this result after unblocking. + Blocks until this result is ready or timeout_time has reached. Returns the status of this result after unblocking. Throws concurrencpp::errors::empty_result if *this is empty. */ template< class clock, class duration > result_status wait_until(std::chrono::time_point timeout_time); /* - Blocks the current thread of execution until this result is ready, when its status is either result_status::value or result_status::exception. + Blocks the current thread of execution until this result is ready, when status() != result_status::idle. If the result is a valid value, it is returned, otherwise, get rethrows the asynchronous exception. Throws concurrencpp::errors::empty_result if *this is empty. */ @@ -343,7 +385,8 @@ class result{ /* Returns an awaitable used to await this result. If the result is already ready - the current coroutine resumes immediately in the calling thread of execution. - If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, by the thread which had set the asynchronous value or exception. + If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, + by the thread which had set the asynchronous value or exception. In either way, after resuming, if the result is a valid value, it is returned. Otherwise, operator co_await rethrows the asynchronous exception. Throws concurrencpp::errors::empty_result if *this is empty. @@ -352,11 +395,11 @@ class result{ /* Returns an awaitable used to await this result. - If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, by scheduling the current coroutine via executor. + If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, + by scheduling the current coroutine via executor. If the result is already ready - the behaviour depends on the value of force_rescheduling: - if force_rescheduling = true, then the current coroutine is forcefully suspended and resumed via execution - if force_rescheduling = false, then the current coroutine resumes immediately in the calling thread of execution. - + If force_rescheduling = true, then the current coroutine is forcefully suspended and resumed via executor. + If force_rescheduling = false, then the current coroutine resumes immediately in the calling thread of execution. In either way, after resuming, if the result is a valid value, it is returned. Otherwise, operator co_await rethrows the asynchronous exception. Throws concurrencpp::errors::empty_result if *this is empty. @@ -369,48 +412,49 @@ class result{ /* Returns an awaitable used to resolve this result. - Resolving a result means awaiting it to become ready, but unlike awaiting, resolving does not return or re-throw the asynchronous result. - *this is returned in a non-empty form and guaranteed that its status is not result_status::idle. + After co_await expression finishes, *this is returned in a non-empty form, in a ready state. Throws concurrencpp::errors::empty_result if *this is empty. */ auto resolve(); /* Returns an awaitable used to resolve this result. - If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, by scheduling the current coroutine via executor. + If the result is not ready yet, the current coroutine is suspended and resumed when the asynchronous result is ready, + by scheduling the current coroutine via executor. If the result is already ready - the behaviour depends on the value of force_rescheduling: - if force_rescheduling = true, then the current coroutine is forcefully suspended and resumed via execution - if force_rescheduling = false, then the current coroutine resumes immediately in the calling thread of execution. + If force_rescheduling = true, then the current coroutine is forcefully suspended and resumed via executor. + If force_rescheduling = false, then the current coroutine resumes immediately in the calling thread of execution. In either way, after resuming, *this is returned in a non-empty form and guaranteed that its status is not result_status::idle. Throws concurrencpp::errors::empty_result if *this is empty. Throws std::invalid_argument if executor is null. If this result is ready and force_rescheduling=true, throws any exception that executor::enqueue may throw. */ auto resolve_via( - std::shared_ptr executor, bool force_rescheduling = true); + std::shared_ptr executor, + bool force_rescheduling = true); }; ``` ### Parallel coroutines -Using executors with OOP style is great. We can launch new tasks by posting or submitting them to an executor object. In some cases, such as in parallel algorithms, recursive algorithms and concurrent algorithms that use the fork-join model, this style can cause inconvenience. -Instead of returning strange result types like `result>` and doing functional style `join`/`unwrap`, we can just use parallel coroutines: +Regular coroutines start to run synchronously in the calling thread of execution. Execution might shift to another thread of execution if the coroutine undergoes a rescheduling, for example by awaiting an unready result object inside it. +concurrencpp also provide parallel coroutines, which start to run inside a given executor, not in the invoking thread of execution. This style of scheduling coroutines is especially helpful when writing parallel algorithms, recursive algorithms and concurrent algorithms that use the fork-join model. -In concurrencpp, a parallel coroutine is a function that: +Every parallel coroutine must meet the following preconditions: 1. Returns any of `result` / `null_result` . 1. Gets `executor_tag` as its first argument . 1. Gets any of `type*` / `type&` / `std::shared_ptr`, where `type` is a concrete class of `executor` as its second argument. 1. Contains any of `co_await` or `co_return` in its body. -In this case, the function is a parallel coroutine: +If all the above applies, the function is a parallel coroutine: concurrencpp will start the function suspended and immediately re-schedule it to run in the provided executor. `concurrencpp::executor_tag` is a dummy placeholder to tell the concurrencpp runtime that this function is not a regular function, it needs to start running inside the given executor. Applications can then consume the result of the parallel coroutine by using the returned result object. #### *Parallel Fibonacci example:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include using namespace concurrencpp; @@ -448,14 +492,62 @@ int main() { In this example, we calculate the 30-th member of the Fibonacci sequence in a parallel manner. We start launching each Fibonacci step in its own parallel coroutine. The first argument is a dummy `executor_tag` and the second argument is the threadpool executor. -Every recursive step invokes a new parallel coroutine that runs in parallel. The result is returned to the parent task and is acquired by using `co_await`. +Every recursive step invokes a new parallel coroutine that runs in parallel. Each result is `co_return`ed to its parent task and acquired by using `co_await`. When we deem the input to be small enough to be calculated synchronously (when `curr <= 10`), we stop executing each recursive step in its own task and just solve the algorithm synchronously. +To compare, this is how the same code is written without using parallel coroutines, and relying on `exector::submit` alone. +Since `fibonacci` returns a `result`, submitting it recursively via `executor::submit` will result a `result>`. + +```cpp +#include "concurrencpp/concurrencpp.h" +#include + +using namespace concurrencpp; + +int fibonacci_sync(int i) { + if (i == 0) { + return 0; + } + + if (i == 1) { + return 1; + } + + return fibonacci_sync(i - 1) + fibonacci_sync(i - 2); +} + +result fibonacci(std::shared_ptr tpe, const int curr) { + if (curr <= 10) { + co_return fibonacci_sync(curr); + } + + auto fib_1 = tpe->submit(fibonacci, tpe, curr - 1); + auto fib_2 = tpe->submit(fibonacci, tpe, curr - 2); + + co_return + co_await co_await fib_1 + + co_await co_await fib_2; +} + +int main() { + concurrencpp::runtime runtime; + auto fibb_30 = fibonacci(runtime.thread_pool_executor(), 30).get(); + std::cout << "fibonacci(30) = " << fibb_30 << std::endl; + return 0; +} +``` + ### Result-promises -Sometimes we want to use the capabilities of result objects with non-coroutines, for example when using a third-party library. In this case, we can complete a result object by using a `result_promise`. `result_promise` resembles a `std::promise` object - applications can manually set the result or exception and make `result` objects become ready (using the `co_return` keyword). +Result objects are the main way to pass data between threads in concurrencpp and we've seen how executors and coroutines produce such objects. +Sometimes we want to use the capabilities of a result object with non-coroutines, for example when using a third-party library. In this case, we can complete a result object by using a `result_promise`. +`result_promise` resembles a `std::promise` object - applications can manually set the asynchronous result or exception and make the associated `result` object become ready. + Just like result objects, result-promises are a move only type that becomes empty after move. Similarly, after setting a result or an exception, the result promise becomes empty as well. -If a result-promise gets out of scope and no result/exception has been set in it, the result-promise destructor sets a `concurrencpp::errors::broken_task` exception using the `set_exception` method. Suspended and blocked tasks waiting for the associated result are resumed/unblocked. +If a result-promise gets out of scope and no result/exception has been set, the result-promise destructor sets a `concurrencpp::errors::broken_task` exception using the `set_exception` method. +Suspended and blocked tasks waiting for the associated result object are resumed/unblocked. + +Result promises can convert callback style of code into `async/await` style of code: whenever a component requires a callback to marshal the asynchronous result, we can pass a callback that calls `set_result` or `set_exception` (depending on the asynchronous result itself) on the passed result promise, and return the associated result. #### `result_promise` API @@ -484,14 +576,15 @@ class result_promise { result_promise& operator = (result_promise&& rhs) noexcept; /* - Returns true if this is a non-empty result-promise. Applications must not use this object if static_cast(*this) is false. + Returns true if this is a non-empty result-promise. + Applications must not use this object if this->operator bool() is false. */ explicit operator bool() const noexcept; /* - Sets a result value by constructing <> from arguments... in-place. Makes the associated - result object become ready - tasks waiting for this promise to become ready are unblocked. Suspended tasks are resumed either - inline or by an executor that was provided by calling result::await_via or result::resolve_via. + Sets a value by constructing <> from arguments... in-place. + Makes the associated result object become ready - tasks waiting for it to become ready are unblocked. + Suspended tasks are resumed either inline or via the executor that was provided by calling result::await_via or result::resolve_via. After this call, *this becomes empty. If *this is empty, a concurrencpp::errors::empty_result_promise exception is thrown. */ @@ -499,8 +592,9 @@ class result_promise { void set_result(argument_types&& ... arguments); /* - Sets an exception. Makes the associated result object become ready - tasks waiting for this promise to become ready are unblocked. Suspended tasks are resumed either - inline or by an executor that was provided by calling result::await_via or result::resolve_via. + Sets an exception. + Makes the associated result object become ready - tasks waiting for it to become ready are unblocked. + Suspended tasks are resumed either inline or via the executor that was provided by calling result::await_via or result::resolve_via. After this call, *this becomes empty. If *this is empty, a concurrencpp::errors::empty_result_promise exception is thrown. If exception_ptr is null, an std::invalid_argument exception is thrown. @@ -508,13 +602,13 @@ class result_promise { void set_exception(std::exception_ptr exception_ptr); /* - A convenience method that invokes callable with args... and calls set_result with the result of the invocation. + A convenience method that invokes callable with arguments... and calls set_result with the result of the invocation. If an exception is thrown, the thrown exception is caught and set instead by calling set_exception. After this call, *this becomes empty. If *this is empty, a concurrencpp::errors::empty_result_promise exception is thrown. */ template - void set_from_function(callable_type&& callable, argument_types&& ... args); + void set_from_function(callable_type&& callable, argument_types&& ... arguments); /* Gets the associated result object. @@ -527,7 +621,7 @@ class result_promise { #### *Example: Marshaling asynchronous result using* `result_promise`: ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include @@ -548,22 +642,18 @@ int main() { ``` In this example, We use `std::thread` as a third-party executor. This represents a scenario when a non-concurrencpp executor is used as part of the application life-cycle. We extract the result object before we pass the promise and block the main thread until the result becomes ready. In `my_3_party_executor`, we set a result as if we `co_return`ed it. -### Summery: creating and scheduling coroutines - -There are few ways to create and schedule coroutines in concurrencpp +### Summery: using coroutines -- By calling any of `executor::post`, `executor::submit`, `executor::bulk_post` or `executor::bulk_submit`. -In this case the application provides a callable and depending on the selected method, might return a result object to the caller. The callable is then turned into a coroutine and scheduled to run in the executor. +A concurrencpp coroutine is a C++ suspendable function. It is eager, meaning it starts to run the moment it is invoked. +It returns one of `concurrencpp::result` / `concurrencpp::null_result` and contains any of `co_await` or `co_return` in its body. Parallel coroutines are a special kind of coroutines, that start run in another thread, by passing a `concurrencpp::executor_tag` and an instance of a valid concurrencpp executor as the first arguments. +Coroutines might return a result object that marshals the asynchronous result or exception. This way we can chain coroutines together, creating a bigger, asynchronous flow graph that doesn't block. +Result objects might also be created by converting callables into coroutines using executor methods, or by using the `result_promise` type. -- By using a parallel coroutine. -- By returning a result object. -When a function returns any of `concurrencpp::result` / `concurrencpp::null_result` and has at least one of `co_await`/`co_return`, then this function is a coroutine that starts executing immediately in the caller thread without suspension. +### Result auxiliary functions -- By using a `result_promise`. +concurrencpp provides helper functions that help manipulate result objects directly: -### Result auxiliary functions -concurrencpp provides helper functions that manipulate results objects. ```cpp /* Creates a ready result object by building <> from arguments&&... in-place. @@ -578,14 +668,14 @@ result make_ready_result(); /* Creates a ready result object from an exception pointer. - The result object will re-throw exception_ptr when calling get, await or await_via. + The returned result object will re-throw exception_ptr when calling get, await or await_via. Throws std::invalid_argument if exception_ptr is null. */ template result make_exceptional_result(std::exception_ptr exception_ptr); /* - Overload. Similar to make_exceptional_result(std::exception_ptr) + Overload. Similar to make_exceptional_result(std::exception_ptr), but gets an exception object directly. */ template @@ -593,7 +683,7 @@ result make_exceptional_result(exception_type exception); /* Creates a result object that becomes ready when all the input results become ready. - Passed result objects are emptied and returned as a tuple when every result is ready. + Passed result objects are emptied and returned as a tuple. Throws std::invalid_argument if any of the passed result objects is empty. */ template @@ -601,13 +691,13 @@ result::type...>> when_all(result_t /* Overload. Similar to when_all(result_types&& ...) but receives a pair of iterators referencing a range. - Each result object is emptied and returned in a ready state as a vector. + Passed result objects are emptied and returned as a vector. If begin == end, the function returns immediately with an empty vector. Throws std::invalid_argument if any of the passed result objects is empty. */ template result::value_type>> -when_all(iterator_type begin, iterator_type end) { +when_all(iterator_type begin, iterator_type end); /* Overload. Returns a ready result object that doesn't monitor any asynchronous result. @@ -626,8 +716,8 @@ struct when_any_result { }; /* - Creates a result object that becomes ready when at least one of the input results become ready. - Passed result objects are emptied and returned as a tuple when at least one result object is ready. + Creates a result object that becomes ready when at least one of the input results is ready. + Passed result objects are emptied and returned as a tuple. Throws std::invalid_argument if any of the passed result objects is empty. */ template @@ -635,7 +725,7 @@ result>> when_any(result_types&& ... /* Overload. Similar to when_any(result_types&& ...) but receives a pair of iterators referencing a range. - Each result object is emptied and returned as a vector when at least one result object is ready. + Passed result objects are emptied and returned as a vector. Throws std::invalid_argument if begin == end. Throws std::invalid_argument if any of the passed result objects is empty. */ @@ -647,33 +737,34 @@ when_any(iterator_type begin, iterator_type end); ### Timers and Timer queues concurrencpp also provides timers and timer queues. -Timers are objects that define actions which run on an executor within a well-defined interval of time. There are three types of timers - *regular timers*, *onshot-timers* and *delay objects*. +Timers are objects that define actions running on an executor within a well-defined interval of time. +There are three types of timers - *regular timers*, *onshot-timers* and *delay objects*. -Timers have four properties that describe them: +Regular timers have four properties that define them: -1. A callable (and potentially its arguments) - a callable that will be scheduled to run periodically. -1. An executor - in which the callable will be scheduled to run as a coroutine -1. Due time - from the time of creation, the interval in milliseconds in which the timer will be scheduled to run for the first time -1. Frequency - from the time the timer callable was scheduled for the first time, the interval in milliseconds the callable will be schedule to run periodically, until the timer is destructed or cancelled. +1. Callable - a callable that will be scheduled to run periodically. +1. Executor - an executor that schedules the callable to run periodically. +1. Due time - from the time of creation, the interval in milliseconds the timer will be scheduled to run for the first time. +1. Frequency - from the time the timer was scheduled to run for the first time, the interval in milliseconds the callable will be schedule to run periodically, until the timer is destructed or cancelled. -A timer queue is a concurrencpp worker that manages a collection of timers and processes them in just one thread of execution. -When a timer deadline (whether its due-time or frequency) has reached, the timer queue "fires" the timer by scheduling its callable to run on the timer given executor. -Just like executors, timer queues also adhere to the RAII concpet. When the runtime object gets out of scope, It shuts down the timer queue, cancelling all pending timers. -After a timer queue has been shut down, any subsequent call to `make_timer`, `make_onshot_timer` and `make_delay_object` will throw an `errors::timer_queue_shutdown` exceptions. +A timer queue is a concurrencpp worker that manages a collection of timers and processes them in just one thread of execution. It is also the agent used to create new timers. +When a timer deadline (whether it is due-time or frequency) has reached, the timer queue "fires" the timer by scheduling its callable to run on the associated executor. +Just like executors, timer queues also adhere to the RAII concpet. When the runtime object gets out of scope, It shuts down the timer queue, cancelling all pending timers. After a timer queue has been shut down, any subsequent call to `make_timer`, `make_onshot_timer` and `make_delay_object` will throw an `errors::timer_queue_shutdown` exception. Applications must not try to shut down timer queues by themselves. #### `timer_queue` API: ```cpp class timer_queue { /* - Destroyes *this and cancels all associated timers. + Destroyes this timer_queue. */ ~timer_queue() noexcept; /* - Shuts down this timer_queue. - After this call, invocation of any method besides shutdown - and shutdown_requested will throw an errors::timer_queue_shutdown. + Shuts down this timer_queue: + Tells the underlying thread of execution to quit and joins it. + Cancells all pending timers. + After this call, invocation of any method besides shutdown and shutdown_requested will throw an errors::timer_queue_shutdown. If shutdown had been called before, this method has no effect. */ void shutdown() noexcept; @@ -697,7 +788,7 @@ class timer_queue { argumet_types&& ... arguments); /* - Creates a new one shot timer where *this is associated timer_queue. + Creates a new one-shot timer where *this is associated timer_queue. Throws std::invalid_argument if executor is null. Throws errors::timer_queue_shutdown if shutdown had been called before. */ @@ -713,7 +804,9 @@ class timer_queue { Throws std::invalid_argument if executor is null. Throws errors::timer_queue_shutdown if shutdown had been called before. */ - result make_delay_object(std::chrono::milliseconds due_time, std::shared_ptr executor); + result make_delay_object( + std::chrono::milliseconds due_time, + std::shared_ptr executor); }; ``` @@ -722,7 +815,7 @@ class timer_queue { ```cpp class timer { /* - Creates an empty timer that does not run. + Creates an empty timer. */ timer() noexcept = default; @@ -745,44 +838,46 @@ class timer { timer& operator = (timer&& rhs) noexcept; /* - Cancels this timer. After this call, the associated timer_queue will not schedule *this to run again. - After this call, *this is empty. - Has no effect if *this is empty or the associated timer_queue has expired. + Cancels this timer. + After this call, the associated timer_queue will not schedule *this to run again and *this becomes empty. + This method has no effect if *this is empty or the associated timer_queue has already expired. */ void cancel(); /* - Returns the due time in milliseconds the timer was defined with. + Returns the associated executor of this timer. Throws concurrencpp::errors::empty_timer is *this is empty. */ - std::chrono::milliseconds get_due_time() const; + std::shared_ptr get_executor() const; /* - Returns the executor the timer was defined with. + Returns the associated timer_queue of this timer. Throws concurrencpp::errors::empty_timer is *this is empty. */ - std::shared_ptr get_executor() const; + std::weak_ptr get_timer_queue() const; /* - Returns the timer_queue the timer was defined with. + Returns the due time of this timer. Throws concurrencpp::errors::empty_timer is *this is empty. */ - std::weak_ptr get_timer_queue() const; + std::chrono::milliseconds get_due_time() const; /* - Returns the frequency in milliseconds the timer was defined with. + Returns the frequency of this timer. Throws concurrencpp::errors::empty_timer is *this is empty. */ std::chrono::milliseconds get_frequency() const; /* - Sets new frequency for this timer. Callables that have been scheduled to run at this point are not affected. + Sets new frequency for this timer. + Callables already scheduled to run at the time of invocation are not affected. Throws concurrencpp::errors::empty_timer is *this is empty. */ void set_frequency(std::chrono::milliseconds new_frequency); /* - Returns true is *this is not an empty timer, false otherwise. + Returns true is *this is not an empty timer, false otherwise. + The timer should not be used if this->operator bool() is false. */ operator bool() const noexcept; }; @@ -790,7 +885,7 @@ class timer { #### *Regular timer example:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include @@ -800,8 +895,8 @@ int main() { concurrencpp::runtime runtime; std::atomic_size_t counter = 1; concurrencpp::timer timer = runtime.timer_queue()->make_timer( - 1'500ms, - 2'000ms, + 1500ms, + 2000ms, runtime.thread_pool_executor(), [&] { const auto c = counter.fetch_add(1); @@ -820,7 +915,7 @@ A oneshot timer is a one-time timer with only a due time - after it schedules it #### *Oneshot timer example:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include @@ -829,7 +924,7 @@ using namespace std::chrono_literals; int main() { concurrencpp::runtime runtime; concurrencpp::timer timer = runtime.timer_queue()->make_one_shot_timer( - 3'000ms, + 3000ms, runtime.thread_executor(), [&] { std::cout << "hello and goodbye" << std::endl; @@ -839,15 +934,15 @@ int main() { return 0; } ``` -In this example, we create a timer that runs only once - after 3 seconds from its creation, the timer will schedule to run its callable on a new thread of execution (using `concurrencpp::thread_executor instance`). +In this example, we create a timer that runs only once - after 3 seconds from its creation, the timer will schedule to run its callable on a new thread of execution (using `concurrencpp::thread_executor`). #### Delay objects -A delay object is a result object that becomes ready when its due time is reached. Applications can `co_await` this result object to delay the current task in a non-blocking way. The current task is resumed in the executor that was passed to `make_delay_object`. +A delay object is a result object that becomes ready when its due time is reached. Applications can `co_await` this result object to delay the current task in a non-blocking way. The current coroutine is resumed by the executor that was passed to `make_delay_object`. #### *Delay object example:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include @@ -862,7 +957,7 @@ concurrencpp::null_result delayed_task( std::cout << "task was invoked " << counter << " times." << std::endl; counter++; - co_await tq->make_delay_object(1'500ms, ex); + co_await tq->make_delay_object(1500ms, ex); } } @@ -875,12 +970,15 @@ int main() { } ``` -### The runtime object +In this example, we created a coroutine (that does not marshal any result or thrown exception), which delays itself in a loop by calling `co_await` on a delay object. -The concurrencpp runtime object is the glue that sticks all the components above to a complete and cohesive mechanism of managing asynchronous actions. -The concurrencpp runtime object is the agent used to acquire, store and create new executors. The runtime must be created as a value type as soon as the main function starts to run. -When the concurrencpp runtime gets out of scope, it iterates over its stored executors and shuts them down one by one by calling `executor::shutdown`. Executors then exit their inner work loop and any subsequent attempt to schedule a new task will throw a `concurrencpp::executor_shutdown` exception. The runtime also contains the global timer queue used to create timers and delay objects. Upon destruction, stored executors will wait for ongoing tasks to finish. If an ongoing task tries to use an executor to spawn new tasks or schedule its own task continuation - an exception will be thrown. In this case, ongoing tasks need to quit as soon as possible, allowing their underlying executors to quit. With this RAII style of code, no tasks can be processed before the creation of the runtime object, and while/after the runtime gets out of scope. -This frees concurrent applications from needing to communicate termination messages explicitly. Tasks are free use executors as long as the runtime object is alive. +### The runtime object + +The concurrencpp runtime object is the agent used to acquire, store and create new executors. +The runtime must be created as a value type as soon as the main function starts to run. +When the concurrencpp runtime gets out of scope, it iterates over its stored executors and shuts them down one by one by calling `executor::shutdown`. Executors then exit their inner work loop and any subsequent attempt to schedule a new task will throw a `concurrencpp::executor_shutdown` exception. The runtime also contains the global timer queue used to create timers and delay objects. +Upon destruction, stored executors will destroy unexecuted tasks, and wait for ongoing tasks to finish. If an ongoing task tries to use an executor to spawn new tasks or schedule its own task continuation - an exception will be thrown. In this case, ongoing tasks need to quit as soon as possible, allowing their underlying executors to quit. The timer queue will also be shut down, cancelling all running timers. With this RAII style of code, no tasks can be processed before the creation of the runtime object, and while/after the runtime gets out of scope. +This frees concurrent applications from needing to communicate termination messages explicitly. Tasks are free use executors as long as the runtime object is alive. #### `runtime` API @@ -899,6 +997,7 @@ class runtime { /* Destroys this runtime object. Calls executor::shutdown on each monitored executor. + Calls timer_queue::shutdown on the global timer queue. */ ~runtime() noexcept; @@ -929,17 +1028,21 @@ class runtime { /* Creates a new concurrencpp::worker_thread_executor and registers it in this runtime. + Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired. */ std::shared_ptr make_worker_thread_executor(); /* Creates a new concurrencpp::manual_executor and registers it in this runtime. + Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired. */ std::shared_ptr make_manual_executor(); /* Creates a new user defined executor and registers it in this runtime. - executor_type must be a valid concrete class of concurrencpp::executor + executor_type must be a valid concrete class of concurrencpp::executor. + Might throw std::bad_alloc if no memory is available. + Might throw any exception that the constructor of <> might throw. */ template std::shared_ptr make_executor(argument_types&& ... arguments); @@ -953,18 +1056,21 @@ class runtime { #### Creating user-defined executors -As mentioned before, Applications can create their own custom executor type by inheriting the `derivable_executor` class. There are a few points to consider when implementing user defined executors: +As mentioned before, Applications can create their own custom executor type by inheriting the `derivable_executor` class. +There are a few points to consider when implementing user defined executors: The most important thing is to remember that executors are used from multiple threads, so implemented methods must be thread-safe. -Another important thing is to handle shutdown correctly: `shutdown`, `shutdown_requested` and `enqueue` should all monitor the executor state and behave accordingly when invoked: +Another important point is to handle shutdown correctly: `shutdown`, `shutdown_requested` and `enqueue` should all monitor the executor state and behave accordingly when invoked: * `shutdown` should tell underlying threads to quit and then join them. `shutdown` must also destroy each unexecuted `coroutine_handle` by calling `coroutine_handle::destroy`. `shutdown` might be called multiple times, and the method must handle this scenario by ignoring any subsequent call to `shutdown` after the first invocation. * `enqueue` must throw a `concurrencpp::errors::executor_shutdown` exception if `shutdown` had been called before. - Coroutine handles that are passed to `enqueue` are expected to be non-finally-suspending coroutines, meaning that when a coroutine ends, it cleans after itself. Executors **must not** call `coroutine_handle::destroy` on coroutines that finished execution gracefully. Explicit destruction of coroutines happens only to unexecuted suspended-coroutines, when shutting down the executor. + Coroutine handles that are passed to `enqueue` are expected to be non-finally-suspending coroutines, meaning that when a coroutine ends, it cleans after itself. Executors **must not** call `coroutine_handle::destroy` on coroutines that finished execution gracefully. Explicit destruction of coroutines happens only to unexecuted suspended-coroutines, when shutting down the executor. + +In any case, new executors can be created using `runtime::make_executor`. Applications must not create new executors with plain instantiation (such as `std::make_shared` or plain `new`), only by using `runtime::make_executor`. Also, applications must not try to re-instantiate the built-in concurrencpp executors, like the threadpool or the thread executors, those executors must only be accessed through their instance in the runtime object. #### *Example: using a user-defined executor:* ```cpp -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include #include @@ -1088,7 +1194,7 @@ int main() { In this example, we created an executor which logs actions like enqueuing a task or executing it. We implement the `executor` interface, and we request the runtime to create, store and monitor an instance of it by calling `runtime::make_executor`. The rest of the application behaves exactly the same as if we were to use non user-defined executors. -### Supported platforms -* Linux (requires clang-9 and above). -* macOS (requires clang-9 and above). -* Windows (requires Windows 10, visual studio 2019 and above). +### Supported platforms and tools + +* **Operating systems:** Linux, macOS, Windows (Windows 10 and above) +* **Compilers:** MSVC (Visual Studio 2019 and above), Clang (clang-9 and above, recommended version: clang-11 and above) diff --git a/cmake/ciBuild.cmake b/cmake/ciBuild.cmake new file mode 100644 index 00000000..7389570c --- /dev/null +++ b/cmake/ciBuild.cmake @@ -0,0 +1,28 @@ +# Don't ignore empty list elements +cmake_policy(SET CMP0007 NEW) + +set(args "") +foreach(n RANGE ${CMAKE_ARGC}) + if(NOT "${CMAKE_ARGV${n}}" STREQUAL "") + list(APPEND args "${CMAKE_ARGV${n}}") + endif() +endforeach() + +list(FIND args "--" index) +if(index EQUAL -1) + message(FATAL_ERROR "No -- divider found in arguments list") +else() + set(temp "${args}") + math(EXPR index "${index} + 1") + list(SUBLIST temp ${index} -1 args) +endif() + +list(POP_FRONT args source build os cmake ninja cores) + +include(cmake/exec.cmake) +include(cmake/setCiVars.cmake) + +exec(${cmake} -S ${source} -B ${build} -G Ninja -D CMAKE_MAKE_PROGRAM=${ninja} +-D CMAKE_BUILD_TYPE=RelWithDebInfo -D CMAKE_INSTALL_PREFIX=build/prefix ${flags} ${args}) + +exec(${cmake} --build ${build} --config RelWithDebInfo -j ${cores}) diff --git a/cmake/ciToolsUpdate.cmake b/cmake/ciToolsUpdate.cmake new file mode 100644 index 00000000..85fb0f16 --- /dev/null +++ b/cmake/ciToolsUpdate.cmake @@ -0,0 +1,53 @@ +set(cmake_version $ENV{CMAKE_VERSION}) +set(ninja_version $ENV{NINJA_VERSION}) + +if(RUNNER_OS STREQUAL "Windows") + set(ninja_suffix "win.zip") + set(cmake_suffix "win64-x64.zip") + set(cmake_dir "cmake-${cmake_version}-win64-x64/bin") +elseif(RUNNER_OS STREQUAL "Linux") + set(ninja_suffix "linux.zip") + set(cmake_suffix "Linux-x86_64.tar.gz") + set(cmake_dir "cmake-${cmake_version}-Linux-x86_64/bin") +elseif(RUNNER_OS STREQUAL "macOS") + set(ninja_suffix "mac.zip") + set(cmake_suffix "Darwin-x86_64.tar.gz") + set(cmake_dir "cmake-${cmake_version}-Darwin-x86_64/CMake.app/Contents/bin") +endif() + +set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}") +file(DOWNLOAD "${cmake_url}" ./cmake.zip) +execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip OUTPUT_QUIET) +message(STATUS "Installed CMake") + +set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}") +file(DOWNLOAD "${ninja_url}" ./ninja.zip) +execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip OUTPUT_QUIET) +message(STATUS "Installed Ninja") + +set(export_script "#!/bin/sh\n") + +file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}/${cmake_dir}" cmake_dir) +file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}/ninja" ninja_out) + +function(echo MESSAGE) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${MESSAGE}") +endfunction() + +set(export_script "${export_script}export CTEST=\"${cmake_dir}/ctest\"\n") +echo("::set-output name=ctest::${cmake_dir}/ctest") +message(STATUS "ctest path: ${cmake_dir}/ctest") + +set(export_script "${export_script}export CMAKE=\"${cmake_dir}/cmake\"\n") +echo("::set-output name=cmake::${cmake_dir}/cmake") +message(STATUS "cmake path: ${cmake_dir}/cmake") + +set(export_script "${export_script}export NINJA=\"${ninja_out}\"\n") +echo("::set-output name=ninja::${ninja_out}") +message(STATUS "ninja path: ${ninja_out}") + +file(WRITE export.sh "${export_script}") + +if (NOT RUNNER_OS STREQUAL "Windows") + execute_process(COMMAND chmod +x ninja export.sh "${cmake_dir}/cmake" "${cmake_dir}/ctest") +endif() diff --git a/cmake/concurrencppConfig.cmake b/cmake/concurrencppConfig.cmake new file mode 100644 index 00000000..dfa304f7 --- /dev/null +++ b/cmake/concurrencppConfig.cmake @@ -0,0 +1 @@ +include("${CMAKE_CURRENT_LIST_DIR}/concurrencppTargets.cmake") diff --git a/cmake/concurrencppInjectTSAN.cmake b/cmake/concurrencppInjectTSAN.cmake new file mode 100644 index 00000000..4fa0f9f0 --- /dev/null +++ b/cmake/concurrencppInjectTSAN.cmake @@ -0,0 +1,9 @@ +# Inject sanitizer flag as a build requirement +# +macro(add_library TARGET) + _add_library(${ARGV}) + + if("${TARGET}" STREQUAL "concurrencpp") + target_compile_options(concurrencpp PRIVATE -fsanitize=thread) + endif() +endmacro() diff --git a/cmake/coroutineOptions.cmake b/cmake/coroutineOptions.cmake new file mode 100644 index 00000000..73b95245 --- /dev/null +++ b/cmake/coroutineOptions.cmake @@ -0,0 +1,20 @@ +# Sets the required compiler options for the given target, or stops CMake if the +# current compiler doesn't support coroutines. +# +function(target_coroutine_options TARGET) + if(MSVC) + target_compile_options(${TARGET} PUBLIC /await /permissive-) + return() + endif() + + find_package(Threads REQUIRED) + target_link_libraries(${TARGET} PRIVATE Threads::Threads) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${TARGET} PUBLIC -stdlib=libc++ -fcoroutines-ts) + target_link_options(${TARGET} PUBLIC -stdlib=libc++) + set_target_properties(${TARGET} PROPERTIES CXX_EXTENSIONS NO) + else() + message(FATAL_ERROR "Compiler not supported: ${CMAKE_CXX_COMPILER_ID}") + endif() +endfunction() diff --git a/cmake/exec.cmake b/cmake/exec.cmake new file mode 100644 index 00000000..0bb7e2d3 --- /dev/null +++ b/cmake/exec.cmake @@ -0,0 +1,21 @@ +function(exec) + set(args "") + foreach(arg IN LISTS ARGN) + string(FIND "${arg}" " " index) + if(index EQUAL -1) + list(APPEND args "${arg}") + else() + list(APPEND args "\"${arg}\"") + endif() + endforeach() + + string(ASCII 27 Esc) + list(JOIN args " " args) + message(STATUS "${Esc}[36mExecuting: ${args}${Esc}[m") + + execute_process(COMMAND ${ARGN} RESULT_VARIABLE result) + + if(NOT result EQUAL 0) + message(FATAL_ERROR "${Esc}[1;31mBad exit status (${result})${Esc}[m") + endif() +endfunction() diff --git a/cmake/setCiVars.cmake b/cmake/setCiVars.cmake new file mode 100644 index 00000000..6699772d --- /dev/null +++ b/cmake/setCiVars.cmake @@ -0,0 +1,12 @@ +if (os MATCHES "^windows") + execute_process( + COMMAND "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && set + OUTPUT_FILE environment_script_output.txt + ) + file(STRINGS environment_script_output.txt output_lines) + foreach(line IN LISTS output_lines) + if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") + set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") + endif() + endforeach() +endif() diff --git a/concurrencpp/CMakeLists.txt b/concurrencpp/CMakeLists.txt deleted file mode 100644 index acb839a4..00000000 --- a/concurrencpp/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(concurrencpp) - -add_library( - concurrencpp - - src/runtime/runtime.cpp - src/threads/thread.cpp - src/results/promises.cpp - src/results/result_core.cpp - src/executors/executor.cpp - src/executors/executor.cpp - src/executors/manual_executor.cpp - src/executors/thread_executor.cpp - src/executors/thread_pool_executor.cpp - src/executors/worker_thread_executor.cpp - src/timers/timer_queue.cpp - src/timers/timer.cpp - ) - -target_include_directories(concurrencpp PUBLIC include) - -if (MSVC) - target_compile_options(concurrencpp PUBLIC /await /permissive-) -else() - target_compile_options(concurrencpp PUBLIC -stdlib=libc++ -fcoroutines-ts) - target_link_options(concurrencpp PUBLIC -stdlib=libc++) - target_link_libraries(concurrencpp PUBLIC pthread) -endif() - -find_library(LIBRT rt) -if(LIBRT) - target_link_libraries(concurrencpp PUBLIC ${LIBRT}) -endif() diff --git a/concurrencpp/include/concurrencpp.h b/concurrencpp/include/concurrencpp.h deleted file mode 100644 index 5b200548..00000000 --- a/concurrencpp/include/concurrencpp.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CONCURRENCPP_H -#define CONCURRENCPP_H - -#include "../src/forward_declerations.h" -#include "../src/platform_defs.h" - -#include "../src/timers/timer.h" -#include "../src/timers/timer_queue.h" -#include "../src/runtime/runtime.h" -#include "../src/results/result.h" -#include "../src/results/make_result.h" -#include "../src/results/when_result.h" -#include "../src/executors/executor_all.h" - -#endif \ No newline at end of file diff --git a/concurrencpp/src/errors.h b/concurrencpp/src/errors.h deleted file mode 100644 index 8776eb0e..00000000 --- a/concurrencpp/src/errors.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CONCURRENCPP_ERRORS_H -#define CONCURRENCPP_ERRORS_H - -#include -#include - -namespace concurrencpp::errors { - struct empty_object : public std::runtime_error { - empty_object(const std::string& message) : runtime_error(message) {} - }; - - struct empty_result : public empty_object { - empty_result(const std::string& message) : empty_object(message) {} - }; - - struct empty_result_promise : public empty_object { - empty_result_promise(const std::string& message) : empty_object(message) {} - }; - - struct empty_awaitable : public empty_object { - empty_awaitable(const std::string& message) : empty_object(message) {} - }; - - struct empty_timer : public empty_object { - empty_timer(const std::string& error_messgae) : empty_object(error_messgae) {} - }; - - struct broken_task : public std::runtime_error { - broken_task(const std::string& message) : runtime_error(message) {} - }; - - struct result_already_retrieved : public std::runtime_error { - result_already_retrieved(const std::string& message) : runtime_error(message) {} - }; - - struct executor_shutdown : public std::runtime_error { - executor_shutdown(const std::string& message) : runtime_error(message) {} - }; - - struct timer_queue_shutdown : public std::runtime_error { - timer_queue_shutdown(const std::string& message) : runtime_error(message) {} - }; -} - -#endif //ERRORS_H diff --git a/concurrencpp/src/executors/constants.h b/concurrencpp/src/executors/constants.h deleted file mode 100644 index e033efde..00000000 --- a/concurrencpp/src/executors/constants.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTORS_CONSTS_H -#define CONCURRENCPP_EXECUTORS_CONSTS_H - -namespace concurrencpp::details::consts { - inline const char* k_inline_executor_name = "concurrencpp::inline_executor"; - constexpr int k_inline_executor_max_concurrency_level = 0; - - inline const char* k_thread_executor_name = "concurrencpp::thread_executor"; - constexpr int k_thread_executor_max_concurrency_level = std::numeric_limits::max(); - - - inline const char* k_thread_pool_executor_name = "concurrencpp::thread_pool_executor"; - inline const char* k_background_executor_name = "concurrencpp::background_executor"; - - constexpr int k_worker_thread_max_concurrency_level = 1; - inline const char* k_worker_thread_executor_name = "concurrencpp::worker_thread_executor"; - - inline const char* k_manual_executor_name = "concurrencpp::manual_executor"; - constexpr int k_manual_executor_max_concurrency_level = std::numeric_limits::max(); - - inline const char* k_executor_shutdown_err_msg = " - shutdown has been called on this executor."; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/derivable_executor.h b/concurrencpp/src/executors/derivable_executor.h deleted file mode 100644 index 57a11e56..00000000 --- a/concurrencpp/src/executors/derivable_executor.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CONCURRENCPP_DERIVABLE_EXECUTOR_H -#define CONCURRENCPP_DERIVABLE_EXECUTOR_H - -#include "executor.h" - -namespace concurrencpp { - template - class derivable_executor : public executor { - - private: - concrete_executor_type* self() noexcept { - return static_cast(this); - } - - public: - derivable_executor(std::string_view name) : executor(name) {} - - template - void post(callable_type&& callable, argument_types&& ... arguments) { - return do_post(self(), std::forward(callable), std::forward(arguments)...); - } - - template - auto submit(callable_type&& callable, argument_types&& ... arguments) { - return do_submit(self(), std::forward(callable), std::forward(arguments)...); - } - - template - void bulk_post(std::span callable_list) { - return do_bulk_post(self(), callable_list); - } - - template> - std::vector> bulk_submit(std::span callable_list) { - return do_bulk_submit(self(), callable_list); - } - }; -} - -#endif diff --git a/concurrencpp/src/executors/executor.cpp b/concurrencpp/src/executors/executor.cpp deleted file mode 100644 index d2c937e2..00000000 --- a/concurrencpp/src/executors/executor.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "executor.h" -#include "constants.h" - -#include "../errors.h" -#include "../threads/thread.h" - -void concurrencpp::details::throw_executor_shutdown_exception(std::string_view executor_name) { - const auto error_msg = std::string(executor_name) + consts::k_executor_shutdown_err_msg; - throw errors::executor_shutdown(error_msg); -} - -std::string concurrencpp::details::make_executor_worker_name(std::string_view executor_name) { - return std::string(executor_name) + " worker"; -} diff --git a/concurrencpp/src/executors/executor.h b/concurrencpp/src/executors/executor.h deleted file mode 100644 index 363c704d..00000000 --- a/concurrencpp/src/executors/executor.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTOR_H -#define CONCURRENCPP_EXECUTOR_H - -#include "../results/result.h" - -#include -#include -#include - -namespace concurrencpp::details { - [[noreturn]] void throw_executor_shutdown_exception(std::string_view executor_name); - std::string make_executor_worker_name(std::string_view executor_name); -} - -namespace concurrencpp { - class executor { - - private: - template - static null_result post_bridge( - executor_tag, - executor_type*, - callable_type callable, - argument_types... arguments) { - callable(arguments...); - co_return; - } - - template - static null_result bulk_post_bridge( - details::executor_bulk_tag, - std::vector>* accumulator, - callable_type callable) { - callable(); - co_return; - } - - template - static result submit_bridge( - executor_tag, - executor_type*, - callable_type callable, - argument_types... arguments) { - co_return callable(arguments...); - } - - template> - static result bulk_submit_bridge( - details::executor_bulk_tag, - std::vector>* accumulator, - callable_type callable) { - co_return callable(); - } - - protected: - template - static void do_post(executor_type* executor_ptr, callable_type&& callable, argument_types&& ... arguments) { - static_assert( - std::is_invocable_v, - "concurrencpp::executor::post - <> is not invokable with <>"); - - post_bridge( - {}, - executor_ptr, - std::forward(callable), - std::forward(arguments)...); - } - - template - static auto do_submit(executor_type* executor_ptr, callable_type&& callable, argument_types&& ... arguments) { - static_assert( - std::is_invocable_v, - "concurrencpp::executor::submit - <> is not invokable with <>"); - - using return_type = typename std::invoke_result_t; - - return submit_bridge( - {}, - executor_ptr, - std::forward(callable), - std::forward(arguments)...); - } - - template - static void do_bulk_post(executor_type* executor_ptr, std::span callable_list) { - std::vector> accumulator; - accumulator.reserve(callable_list.size()); - - for (auto& callable : callable_list) { - bulk_post_bridge({}, &accumulator, std::move(callable)); - } - - assert(!accumulator.empty()); - executor_ptr->enqueue(accumulator); - } - - template> - static std::vector> - do_bulk_submit(executor_type* executor_ptr, std::span callable_list) { - std::vector> accumulator; - accumulator.reserve(callable_list.size()); - - std::vector> results; - results.reserve(callable_list.size()); - - for (auto& callable : callable_list) { - results.emplace_back(bulk_submit_bridge({}, &accumulator, std::move(callable))); - } - - assert(!accumulator.empty()); - executor_ptr->enqueue(accumulator); - return results; - } - - public: - executor(std::string_view name) : name(name) {} - - virtual ~executor() noexcept = default; - - const std::string name; - - virtual void enqueue(std::experimental::coroutine_handle<> task) = 0; - virtual void enqueue(std::span> tasks) = 0; - - virtual int max_concurrency_level() const noexcept = 0; - - virtual bool shutdown_requested() const noexcept = 0; - virtual void shutdown() noexcept = 0; - - template - void post(callable_type&& callable, argument_types&& ... arguments) { - return do_post(this, std::forward(callable), std::forward(arguments)...); - } - - template - auto submit(callable_type&& callable, argument_types&& ... arguments) { - return do_submit(this, std::forward(callable), std::forward(arguments)...); - } - - template - void bulk_post(std::span callable_list) { - return do_bulk_post(this, callable_list); - } - - template> - std::vector> bulk_submit(std::span callable_list) { - return do_bulk_submit(this, callable_list); - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/executor_all.h b/concurrencpp/src/executors/executor_all.h deleted file mode 100644 index 26af453c..00000000 --- a/concurrencpp/src/executors/executor_all.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTORS_ALL_H -#define CONCURRENCPP_EXECUTORS_ALL_H - -#include "derivable_executor.h" -#include "inline_executor.h" -#include "thread_pool_executor.h" -#include "thread_executor.h" -#include "worker_thread_executor.h" -#include "manual_executor.h" - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/inline_executor.h b/concurrencpp/src/executors/inline_executor.h deleted file mode 100644 index a09df907..00000000 --- a/concurrencpp/src/executors/inline_executor.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef CONCURRENCPP_INLINE_EXECUTOR_H -#define CONCURRENCPP_INLINE_EXECUTOR_H - -#include "executor.h" -#include "constants.h" - -namespace concurrencpp { - class inline_executor final : public executor{ - - private: - std::atomic_bool m_abort; - - void throw_if_aborted() const { - if (!m_abort.load(std::memory_order_relaxed)) { - return; - } - - details::throw_executor_shutdown_exception(name); - } - - public: - inline_executor() noexcept : - executor(details::consts::k_inline_executor_name), - m_abort(false) {} - - void enqueue(std::experimental::coroutine_handle<> task) override { - throw_if_aborted(); - task(); - } - - void enqueue(std::span> tasks) override { - throw_if_aborted(); - - for (auto& task : tasks) { - task(); - } - } - - int max_concurrency_level() const noexcept override { - return details::consts::k_inline_executor_max_concurrency_level; - } - - void shutdown() noexcept override { - m_abort.store(true, std::memory_order_relaxed); - } - - bool shutdown_requested() const noexcept override { - return m_abort.load(std::memory_order_relaxed); - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/manual_executor.cpp b/concurrencpp/src/executors/manual_executor.cpp deleted file mode 100644 index 87a51760..00000000 --- a/concurrencpp/src/executors/manual_executor.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "manual_executor.h" -#include "constants.h" - -using concurrencpp::manual_executor; - -void manual_executor::destroy_tasks(std::unique_lock& lock) noexcept { - assert(lock.owns_lock()); - (void)lock; - - for (auto task : m_tasks) { - task.destroy(); - } - - m_tasks.clear(); -} - -manual_executor::manual_executor(): - derivable_executor(details::consts::k_manual_executor_name), - m_abort(false), - m_atomic_abort(false) {} - -void manual_executor::enqueue(std::experimental::coroutine_handle<> task) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - m_tasks.emplace_back(task); - lock.unlock(); - m_condition.notify_all(); -} - -void manual_executor::enqueue(std::span> tasks) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - m_tasks.insert(m_tasks.end(), tasks.begin(), tasks.end()); - lock.unlock(); - - m_condition.notify_all(); -} - -int manual_executor::max_concurrency_level() const noexcept { - return details::consts::k_manual_executor_max_concurrency_level; -} - -size_t manual_executor::size() const noexcept { - std::unique_lock lock(m_lock); - return m_tasks.size(); -} - -bool manual_executor::empty() const noexcept { - return size() == 0; -} - -bool manual_executor::loop_once() { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - if (m_tasks.empty()) { - return false; - } - - auto task = m_tasks.front(); - m_tasks.pop_front(); - lock.unlock(); - - task(); - return true; -} - -bool manual_executor::loop_once(std::chrono::milliseconds max_waiting_time) { - std::unique_lock lock(m_lock); - m_condition.wait_for(lock, max_waiting_time, [this] { - return !m_tasks.empty() || m_abort; - }); - - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - if (m_tasks.empty()) { - return false; - } - - auto task = m_tasks.front(); - m_tasks.pop_front(); - lock.unlock(); - - task(); - return true; -} - -size_t manual_executor::loop(size_t max_count) { - size_t executed = 0; - for (; executed < max_count; ++executed) { - if (!loop_once()) { - break; - } - } - - return executed; -} - -size_t manual_executor::clear() noexcept { - std::unique_lock lock(m_lock); - auto tasks = std::move(m_tasks); - lock.unlock(); - - for (auto task : tasks) { - task.destroy(); - } - - return tasks.size(); -} - -void manual_executor::wait_for_task() { - std::unique_lock lock(m_lock); - m_condition.wait(lock, [this] { - return !m_tasks.empty() || m_abort; - }); - - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } -} - -bool manual_executor::wait_for_task(std::chrono::milliseconds max_waiting_time) { - std::unique_lock lock(m_lock); - const auto res = m_condition.wait_for(lock, max_waiting_time, [this] { return - !m_tasks.empty() || m_abort; - }); - - if (!res) { - assert(!m_abort && m_tasks.empty()); - return false; - } - - if (!m_abort) { - assert(!m_tasks.empty()); - return true; - } - - details::throw_executor_shutdown_exception(name); -} - -void manual_executor::shutdown() noexcept { - const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); - if (abort) { - return; //shutdown had been called before. - } - - { - std::unique_lock lock(m_lock); - m_abort = true; - - destroy_tasks(lock); - } - - m_condition.notify_all(); -} - -bool manual_executor::shutdown_requested() const noexcept { - return m_atomic_abort.load(std::memory_order_relaxed); -} \ No newline at end of file diff --git a/concurrencpp/src/executors/manual_executor.h b/concurrencpp/src/executors/manual_executor.h deleted file mode 100644 index b56ebe62..00000000 --- a/concurrencpp/src/executors/manual_executor.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef CONCURRENCPP_MANUAL_EXECUTOR_H -#define CONCURRENCPP_MANUAL_EXECUTOR_H - -#include "derivable_executor.h" -#include "constants.h" - -#include - -namespace concurrencpp { - class alignas(64) manual_executor final : public derivable_executor { - - private: - mutable std::mutex m_lock; - std::deque> m_tasks; - std::condition_variable m_condition; - bool m_abort; - std::atomic_bool m_atomic_abort; - - void destroy_tasks(std::unique_lock& lock) noexcept; - - public: - manual_executor(); - - void enqueue(std::experimental::coroutine_handle<> task) override; - void enqueue(std::span> tasks) override; - - int max_concurrency_level() const noexcept override; - - void shutdown() noexcept override; - bool shutdown_requested() const noexcept override; - - size_t size() const noexcept; - bool empty() const noexcept; - - bool loop_once(); - bool loop_once(std::chrono::milliseconds max_waiting_time); - - size_t loop(size_t max_count); - - size_t clear() noexcept; - - void wait_for_task(); - bool wait_for_task(std::chrono::milliseconds max_waiting_time); - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/thread_executor.cpp b/concurrencpp/src/executors/thread_executor.cpp deleted file mode 100644 index df108798..00000000 --- a/concurrencpp/src/executors/thread_executor.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "thread_executor.h" -#include "constants.h" - -using concurrencpp::thread_executor; -using concurrencpp::details::thread_worker; - -thread_worker::thread_worker(thread_executor& parent_pool) noexcept : - m_parent_pool(parent_pool) {} - -thread_worker::~thread_worker() noexcept { - m_thread.join(); -} - -void thread_worker::execute_and_retire( - std::experimental::coroutine_handle<> task, - typename std::list::iterator self_it) { - task(); - m_parent_pool.retire_worker(self_it); -} - -void thread_worker::start( - std::string worker_name, - std::experimental::coroutine_handle<> task, - std::list::iterator self_it) { - m_thread = thread(std::move(worker_name), [this, task, self_it] { - execute_and_retire(task, self_it); - }); -} - -thread_executor::thread_executor() : - derivable_executor(details::consts::k_thread_executor_name), - m_abort(false), - m_atomic_abort(false) {} - -thread_executor::~thread_executor() noexcept { - assert(m_workers.empty()); - assert(m_last_retired.empty()); -} - -void thread_executor::enqueue_impl(std::experimental::coroutine_handle<> task) { - m_workers.emplace_front(*this); - m_workers.front().start(details::make_executor_worker_name(name), task, m_workers.begin()); -} - -void thread_executor::enqueue(std::experimental::coroutine_handle<> task) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - enqueue_impl(task); -} - -void thread_executor::enqueue(std::span> tasks) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - for (auto task : tasks) { - enqueue_impl(task); - } -} - -int thread_executor::max_concurrency_level() const noexcept { - return details::consts::k_thread_executor_max_concurrency_level; -} - -bool thread_executor::shutdown_requested() const noexcept { - return m_atomic_abort.load(std::memory_order_relaxed); -} - -void thread_executor::shutdown() noexcept { - const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); - if (abort) { - return; //shutdown had been called before. - } - - std::unique_lock lock(m_lock); - m_abort = true; - m_condition.wait(lock, [this] { return m_workers.empty(); }); - m_last_retired.clear(); -} - -void thread_executor::retire_worker(std::list::iterator it) { - std::unique_lock lock(m_lock); - auto last_retired = std::move(m_last_retired); - m_last_retired.splice(m_last_retired.begin(), m_workers, it); - - lock.unlock(); - m_condition.notify_one(); - - last_retired.clear(); -} \ No newline at end of file diff --git a/concurrencpp/src/executors/thread_executor.h b/concurrencpp/src/executors/thread_executor.h deleted file mode 100644 index 1d31c51f..00000000 --- a/concurrencpp/src/executors/thread_executor.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CONCURRENCPP_THREAD_EXECUTOR_H -#define CONCURRENCPP_THREAD_EXECUTOR_H - -#include "derivable_executor.h" -#include "constants.h" - -#include "../threads/thread.h" - -#include -#include -#include -#include -#include - -namespace concurrencpp::details { - class thread_worker { - - private: - thread m_thread; - thread_executor& m_parent_pool; - - void execute_and_retire( - std::experimental::coroutine_handle<> task, - typename std::list::iterator self_it); - - public: - thread_worker(thread_executor& parent_pool) noexcept; - ~thread_worker() noexcept; - - void start( - const std::string worker_name, - std::experimental::coroutine_handle<> task, - std::list::iterator self_it); - }; -} - -namespace concurrencpp { - class alignas(64) thread_executor final : public derivable_executor { - - friend class ::concurrencpp::details::thread_worker; - - private: - std::mutex m_lock; - std::list m_workers; - std::condition_variable m_condition; - std::list m_last_retired; - bool m_abort; - std::atomic_bool m_atomic_abort; - - void enqueue_impl(std::experimental::coroutine_handle<> task); - - void retire_worker(std::list::iterator it); - - public: - thread_executor(); - ~thread_executor() noexcept; - - void enqueue(std::experimental::coroutine_handle<> task) override; - void enqueue(std::span> tasks) override; - - int max_concurrency_level() const noexcept override; - - bool shutdown_requested() const noexcept override; - void shutdown() noexcept override; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/thread_pool_executor.cpp b/concurrencpp/src/executors/thread_pool_executor.cpp deleted file mode 100644 index cc8adcbd..00000000 --- a/concurrencpp/src/executors/thread_pool_executor.cpp +++ /dev/null @@ -1,466 +0,0 @@ -#include "thread_pool_executor.h" -#include "constants.h" - -#include - -#include -#include - -using concurrencpp::thread_pool_executor; -using concurrencpp::details::idle_worker_set; -using concurrencpp::details::thread_pool_worker; - -namespace concurrencpp::details { - class randomizer { - - private: - uint32_t m_seed; - - public: - randomizer(uint32_t seed) noexcept : m_seed(seed) {} - - uint32_t operator()() noexcept { - m_seed = (214013 * m_seed + 2531011); - return (m_seed >> 16) & 0x7FFF; - } - }; - - struct thread_pool_per_thread_data { - thread_pool_worker* this_worker; - randomizer randomizer; - size_t this_thread_index; - - thread_pool_per_thread_data() noexcept : - this_worker(nullptr), - randomizer(static_cast(std::rand())), - this_thread_index(static_cast(-1)){} - }; - - static thread_local thread_pool_per_thread_data s_tl_thread_pool_data; -} - -idle_worker_set::idle_worker_set(size_t size) : - m_approx_size(0), - m_idle_flags(std::make_unique(size)), - m_size(size) { - for (size_t i = 0; i < size; i++) { - m_idle_flags[i].flag = status::active; - } -} - -void idle_worker_set::set_idle(size_t idle_thread) noexcept { - m_idle_flags[idle_thread].flag.store(status::idle, std::memory_order_relaxed); - m_approx_size.fetch_add(1, std::memory_order_release); //the mo is in order for the addition to happen AFTER flagging. -} - -void idle_worker_set::set_active(size_t idle_thread) noexcept { - auto expected = status::idle; - const auto swapped = m_idle_flags[idle_thread].flag.compare_exchange_strong( - expected, - status::active, - std::memory_order_relaxed); - - if (!swapped) { - return; - } - - m_approx_size.fetch_sub(1, std::memory_order_release); -} - -bool idle_worker_set::try_acquire_flag(size_t index) noexcept { - const auto worker_status = m_idle_flags[index].flag.load(std::memory_order_relaxed); - if (worker_status == status::active) { - return false; - } - - auto expected = status::idle; - const auto swapped = m_idle_flags[index].flag.compare_exchange_strong( - expected, - status::active, - std::memory_order_relaxed); - - if (swapped) { - m_approx_size.fetch_sub(1, std::memory_order_relaxed); - } - - return swapped; -} - -size_t idle_worker_set::find_idle_worker(size_t caller_index) noexcept { - if (m_approx_size.load(std::memory_order_relaxed) <= 0) { - return static_cast(-1); - } - - const auto starting_pos = s_tl_thread_pool_data.randomizer() % m_size; - for (size_t i = 0; i < m_size; i++) { - const auto index = (starting_pos + i) % m_size; - if (index == caller_index) { - continue; - } - - if (try_acquire_flag(index)) { - return index; - } - } - - return static_cast(-1); -} - -void idle_worker_set::find_idle_workers( - size_t caller_index, - std::vector& result_buffer, - size_t max_count) noexcept { - assert(result_buffer.capacity() >= max_count); - - if (m_approx_size.load(std::memory_order_relaxed) <= 0) { - return; - } - - const auto starting_pos = s_tl_thread_pool_data.randomizer() % m_size; - size_t count = 0; - - for (size_t i = 0; (i < m_size) && (count < max_count); i++) { - const auto index = (starting_pos + i) % m_size; - if (index == caller_index) { - continue; - } - - if (try_acquire_flag(index)) { - result_buffer.emplace_back(index); - ++count; - } - } -} - -thread_pool_worker::thread_pool_worker( - thread_pool_executor& parent_pool, - size_t index, - size_t pool_size, - std::chrono::seconds max_idle_time) : - m_atomic_abort(false), - m_parent_pool(parent_pool), - m_index(index), - m_pool_size(pool_size), - m_max_idle_time(max_idle_time), - m_worker_name(details::make_executor_worker_name(parent_pool.name)), - m_status(status::idle), - m_abort(false) { - m_idle_worker_list.reserve(pool_size); -} - -thread_pool_worker::thread_pool_worker(thread_pool_worker&& rhs) noexcept : - m_parent_pool(rhs.m_parent_pool), - m_index(rhs.m_index), - m_pool_size(rhs.m_pool_size), - m_max_idle_time(rhs.m_max_idle_time) { - std::abort(); //shouldn't be called -} - -thread_pool_worker::~thread_pool_worker() noexcept { - assert(m_status == status::idle); - assert(!m_thread.joinable()); -} - -void thread_pool_worker::balance_work() { - const auto task_count = m_private_queue.size(); - if (task_count == 0) { - return; - } - - //we assume all threads but us are idle - const auto idle_worker_count = std::min(m_pool_size - 1, task_count); - if (idle_worker_count == 0) { - return; // a thread-pool with a single thread - } - - m_parent_pool.find_idle_workers(m_index, m_idle_worker_list, idle_worker_count); - if (m_idle_worker_list.empty()) { - return; - } - - for (auto idle_worker_index : m_idle_worker_list) { - assert(idle_worker_index != m_index); - assert(idle_worker_index < m_pool_size); - - const auto task = m_private_queue.front(); - m_private_queue.pop_front(); - m_parent_pool.worker_at(idle_worker_index).enqueue_foreign(task); - } - - m_idle_worker_list.clear(); -} - - -bool thread_pool_worker::wait_for_task(std::unique_lock& lock) noexcept { - assert(lock.owns_lock()); - - m_parent_pool.mark_worker_idle(m_index); - m_status = status::waiting; - - const auto timeout = !m_condition.wait_for(lock, m_max_idle_time, [this] { - return !m_public_queue.empty() || m_abort; - }); - - if (timeout || m_abort) { - m_status = status::idle; - lock.unlock(); - return false; - } - - assert(!m_public_queue.empty()); - m_status = status::working; - m_parent_pool.mark_worker_active(m_index); - return true; -} - -bool thread_pool_worker::drain_queue_impl() { - while (!m_private_queue.empty()) { - auto task = m_private_queue.back(); - m_private_queue.pop_back(); - - balance_work(); - - if (m_atomic_abort.load(std::memory_order_relaxed)) { - std::unique_lock lock(m_lock); - m_status = status::idle; - return false; - } - - task(); - } - - return true; -} - -bool thread_pool_worker::drain_queue() { - std::unique_lock lock(m_lock); - if (m_public_queue.empty() && m_abort == false) { - if (!wait_for_task(lock)) { - return false; - } - } - - assert(lock.owns_lock()); - - if (m_abort) { - m_status = status::idle; - return false; - } - - assert(m_private_queue.empty()); - std::swap(m_private_queue, m_public_queue); //reuse underlying allocations. - lock.unlock(); - - return drain_queue_impl(); -} - -void thread_pool_worker::work_loop() noexcept { - s_tl_thread_pool_data.this_worker = this; - s_tl_thread_pool_data.this_thread_index = m_index; - - while (true) { - if (!drain_queue()) { - return; - } - } -} - -void thread_pool_worker::destroy_tasks() noexcept { - std::unique_lock lock(m_lock); - for (auto task : m_private_queue) { - task.destroy(); - } - - m_private_queue.clear(); - - for (auto task : m_public_queue) { - task.destroy(); - } - - m_public_queue.clear(); -} - -void thread_pool_worker::ensure_worker_active(std::unique_lock& lock) { - assert(lock.owns_lock()); - const auto status = m_status; - - switch (status) { - case status::working: { - lock.unlock(); - return; - } - - case status::waiting: { - lock.unlock(); - m_condition.notify_one(); - return; - } - - case status::idle: { - auto stale_worker = std::move(m_thread); - m_thread = thread(m_worker_name, [this] { - work_loop(); - }); - - m_status = status::working; - lock.unlock(); - - if (stale_worker.joinable()) { - stale_worker.join(); - } - } - } -} - -void thread_pool_worker::enqueue_foreign(std::experimental::coroutine_handle<> task) { - std::unique_lock lock(m_lock); - if (m_abort) { - throw_executor_shutdown_exception(m_parent_pool.name); - } - - m_public_queue.emplace_back(task); - ensure_worker_active(lock); -} - -void thread_pool_worker::enqueue_foreign(std::span> tasks) { - std::unique_lock lock(m_lock); - if (m_abort) { - throw_executor_shutdown_exception(m_parent_pool.name); - } - - m_public_queue.insert(m_public_queue.end(), tasks.begin(), tasks.end()); - ensure_worker_active(lock); -} - -void thread_pool_worker::enqueue_local(std::experimental::coroutine_handle<> task) { - if (m_atomic_abort.load(std::memory_order_relaxed)) { - throw_executor_shutdown_exception(m_parent_pool.name); - } - - m_private_queue.emplace_back(task); -} - -void thread_pool_worker::enqueue_local(std::span> tasks) { - if (m_atomic_abort.load(std::memory_order_relaxed)) { - throw_executor_shutdown_exception(m_parent_pool.name); - } - - m_private_queue.insert(m_private_queue.end(), tasks.begin(), tasks.end()); -} - -void thread_pool_worker::abort() noexcept { - assert(m_atomic_abort.load(std::memory_order_relaxed) == false); - m_atomic_abort.store(true, std::memory_order_relaxed); - - { - std::unique_lock lock(m_lock); - m_abort = true; - } - - m_condition.notify_all(); -} - -void thread_pool_worker::join() noexcept { - if (m_thread.joinable()) { - m_thread.join(); - } - - destroy_tasks(); -} - -thread_pool_executor::thread_pool_executor(std::string_view name, size_t size, std::chrono::seconds max_idle_time) : - derivable_executor(name), - m_round_robin_cursor(0), - m_idle_workers(size), - m_abort(false) { - m_workers.reserve(size); - - std::srand(static_cast(std::time(nullptr))); - - for (size_t i = 0; i < size; i++) { - m_workers.emplace_back(*this, i, size, max_idle_time); - } - - for (size_t i = 0; i < size; i++) { - m_idle_workers.set_idle(i); - } -} - -thread_pool_executor::~thread_pool_executor() noexcept {} - -void thread_pool_executor::find_idle_workers(size_t caller_index, std::vector& buffer, size_t max_count) noexcept { - m_idle_workers.find_idle_workers(caller_index, buffer, max_count); -} - -void thread_pool_executor::mark_worker_idle(size_t index) noexcept { - assert(index < m_workers.size()); - m_idle_workers.set_idle(index); -} - -void thread_pool_executor::mark_worker_active(size_t index) noexcept { - assert(index < m_workers.size()); - m_idle_workers.set_active(index); -} - -void thread_pool_executor::enqueue(std::experimental::coroutine_handle<> task) { - const auto idle_worker_pos = m_idle_workers.find_idle_worker(details::s_tl_thread_pool_data.this_thread_index); - if (idle_worker_pos != static_cast(-1)) { - return m_workers[idle_worker_pos].enqueue_foreign(task); - } - - if (details::s_tl_thread_pool_data.this_worker != nullptr) { - return details::s_tl_thread_pool_data.this_worker->enqueue_local(task); - } - - const auto next_worker = m_round_robin_cursor.fetch_add(1, std::memory_order_relaxed) % m_workers.size(); - m_workers[next_worker].enqueue_foreign(task); -} - -void thread_pool_executor::enqueue(std::span> tasks) { - if (details::s_tl_thread_pool_data.this_worker != nullptr) { - return details::s_tl_thread_pool_data.this_worker->enqueue_local(tasks); - } - - if (tasks.size() < m_workers.size() * 2) { - for (auto task : tasks) { - enqueue(task); - } - - return; - } - - const auto approx_bulk_size = static_cast(tasks.size()) / static_cast(m_workers.size()); - const auto bulk_size = static_cast(std::ceil(approx_bulk_size)); - - size_t worker_index = 0; - auto cursor = tasks.data(); - const auto absolute_end = tasks.data() + tasks.size(); - - while (cursor < absolute_end) { - auto end = (cursor + bulk_size > absolute_end) ? absolute_end : (cursor + bulk_size); - std::span> range = { cursor, end }; - m_workers[worker_index].enqueue_foreign(range); - cursor += bulk_size; - ++worker_index; - } -} - -int thread_pool_executor::max_concurrency_level() const noexcept { - return static_cast(m_workers.size()); -} - -bool thread_pool_executor::shutdown_requested() const noexcept { - return m_abort.load(std::memory_order_relaxed); -} - -void concurrencpp::thread_pool_executor::shutdown() noexcept { - const auto abort = m_abort.exchange(true, std::memory_order_relaxed); - if (abort) { - return; //shutdown had been called before. - } - - for (auto& worker : m_workers) { - worker.abort(); - worker.join(); - } -} diff --git a/concurrencpp/src/executors/thread_pool_executor.h b/concurrencpp/src/executors/thread_pool_executor.h deleted file mode 100644 index 444265ea..00000000 --- a/concurrencpp/src/executors/thread_pool_executor.h +++ /dev/null @@ -1,146 +0,0 @@ -#ifndef CONCURRENCPP_THREAD_POOL_EXECUTOR_H -#define CONCURRENCPP_THREAD_POOL_EXECUTOR_H - -#include "derivable_executor.h" - -#include -#include -#include -#include -#include -#include - -#include "../threads/thread.h" - -namespace concurrencpp::details { - class idle_worker_set; - class thread_pool_worker; -} - -namespace concurrencpp::details { - class idle_worker_set { - - enum class status { - active, - idle - }; - - struct alignas(64) padded_flag { - std::atomic flag; - const char padding[64 - sizeof(flag)] = {}; - }; - - private: - std::atomic_intptr_t m_approx_size; - const std::unique_ptr m_idle_flags; - const size_t m_size; - - bool try_acquire_flag(size_t index) noexcept; - - public: - idle_worker_set(size_t size); - - void set_idle(size_t idle_thread) noexcept; - void set_active(size_t idle_thread) noexcept; - - size_t find_idle_worker(size_t caller_index) noexcept; - void find_idle_workers(size_t caller_index, std::vector& result_buffer, size_t max_count) noexcept; - }; -} - -namespace concurrencpp::details { - class alignas(64) thread_pool_worker { - - enum class status { - working, - waiting, - idle - }; - - private: - std::deque> m_private_queue; - std::vector m_idle_worker_list; - std::atomic_bool m_atomic_abort; - thread_pool_executor& m_parent_pool; - const size_t m_index; - const size_t m_pool_size; - const std::chrono::seconds m_max_idle_time; - const std::string m_worker_name; - const char m_padding[64] = {}; - std::mutex m_lock; - status m_status; - std::deque> m_public_queue; - thread m_thread; - std::condition_variable m_condition; - bool m_abort; - - void balance_work(); - - bool wait_for_task(std::unique_lock& lock) noexcept; - bool drain_queue_impl(); - bool drain_queue(); - - void work_loop() noexcept; - - void destroy_tasks() noexcept; - void ensure_worker_active(std::unique_lock& lock); - - public: - thread_pool_worker( - thread_pool_executor& parent_pool, - size_t index, - size_t pool_size, - std::chrono::seconds max_idle_time); - - thread_pool_worker(thread_pool_worker&& rhs) noexcept; - ~thread_pool_worker() noexcept; - - void enqueue_foreign(std::experimental::coroutine_handle<> task); - void enqueue_foreign(std::span> tasks); - - void enqueue_local(std::experimental::coroutine_handle<> task); - void enqueue_local(std::span> tasks); - - void abort() noexcept; - void join() noexcept; - }; -} - -namespace concurrencpp { - class alignas (64) thread_pool_executor final : public derivable_executor { - - friend class details::thread_pool_worker; - - private: - std::vector m_workers; - const char m_padding_0[64 - sizeof(m_workers)] = {}; - std::atomic_size_t m_round_robin_cursor; - const char m_padding_1[64 - sizeof(m_round_robin_cursor)] = {}; - details::idle_worker_set m_idle_workers; - const char m_padding_2[64 - sizeof(m_idle_workers)] = {}; - std::atomic_bool m_abort; - - void mark_worker_idle(size_t index) noexcept; - void mark_worker_active(size_t index) noexcept; - - void find_idle_workers(size_t caller_index, std::vector& buffer, size_t max_count) noexcept; - - details::thread_pool_worker& worker_at(size_t index) noexcept { - return m_workers[index]; - } - - public: - thread_pool_executor(std::string_view name, size_t size, std::chrono::seconds max_idle_time); - ~thread_pool_executor() noexcept; - - void enqueue(std::experimental::coroutine_handle<> task) override; - void enqueue(std::span> tasks) override; - - int max_concurrency_level() const noexcept override; - - bool shutdown_requested() const noexcept override; - void shutdown() noexcept override; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/executors/worker_thread_executor.cpp b/concurrencpp/src/executors/worker_thread_executor.cpp deleted file mode 100644 index 98246404..00000000 --- a/concurrencpp/src/executors/worker_thread_executor.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "worker_thread_executor.h" -#include "constants.h" - -#include "../errors.h" - -namespace concurrencpp::details { - static thread_local worker_thread_executor* s_tl_this_worker = nullptr; -} - -using concurrencpp::worker_thread_executor; - -worker_thread_executor::worker_thread_executor() : - derivable_executor(details::consts::k_worker_thread_executor_name), - m_private_atomic_abort(false), - m_abort(false), - m_atomic_abort(false) { - m_thread = details::thread(details::make_executor_worker_name(name), [this] { work_loop(); }); -} - -worker_thread_executor::~worker_thread_executor() noexcept { - assert(!m_thread.joinable()); -} - -void worker_thread_executor::destroy_tasks() noexcept { - std::unique_lock lock(m_lock); - for (auto task : m_private_queue) { - task.destroy(); - } - - m_private_queue.clear(); - - for (auto task : m_public_queue) { - task.destroy(); - } - - m_public_queue.clear(); -} - -bool worker_thread_executor::drain_queue_impl() { - while (!m_private_queue.empty()) { - auto task = m_private_queue.front(); - m_private_queue.pop_front(); - - if (m_private_atomic_abort.load(std::memory_order_relaxed)) { - return false; - } - - task(); - } - - return true; -} - -bool worker_thread_executor::drain_queue() { - std::unique_lock lock(m_lock); - m_condition.wait(lock, [this] { - return !m_public_queue.empty() || m_abort; - }); - - if (m_abort) { - return false; - } - - assert(m_private_queue.empty()); - std::swap(m_private_queue, m_public_queue); //reuse underlying allocations. - lock.unlock(); - - return drain_queue_impl(); -} - -void worker_thread_executor::work_loop() noexcept { - details::s_tl_this_worker = this; - - while (true) { - if (!drain_queue()) { - return; - } - } -} - -void worker_thread_executor::enqueue_local(std::experimental::coroutine_handle<> task) { - if (m_private_atomic_abort.load(std::memory_order_relaxed)) { - details::throw_executor_shutdown_exception(name); - } - - m_private_queue.emplace_back(task); -} - -void worker_thread_executor::enqueue_local(std::span> tasks) { - if (m_private_atomic_abort.load(std::memory_order_relaxed)) { - details::throw_executor_shutdown_exception(name); - } - - m_private_queue.insert(m_private_queue.end(), tasks.begin(), tasks.end()); -} - -void worker_thread_executor::enqueue_foreign(std::experimental::coroutine_handle<> task) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - m_public_queue.emplace_back(task); - lock.unlock(); - - m_condition.notify_one(); -} - -void worker_thread_executor::enqueue_foreign(std::span> tasks) { - std::unique_lock lock(m_lock); - if (m_abort) { - details::throw_executor_shutdown_exception(name); - } - - m_public_queue.insert(m_public_queue.end(), tasks.begin(), tasks.end()); - lock.unlock(); - - m_condition.notify_one(); -} - -void worker_thread_executor::enqueue(std::experimental::coroutine_handle<> task) { - if (details::s_tl_this_worker == this) { - return enqueue_local(task); - } - - enqueue_foreign(task); -} - -void worker_thread_executor::enqueue(std::span> tasks) { - if (details::s_tl_this_worker == this) { - return enqueue_local(tasks); - } - - enqueue_foreign(tasks); -} - -int worker_thread_executor::max_concurrency_level() const noexcept { - return details::consts::k_worker_thread_max_concurrency_level; -} - -bool concurrencpp::worker_thread_executor::shutdown_requested() const noexcept { - return m_atomic_abort.load(std::memory_order_relaxed); -} - -void worker_thread_executor::shutdown() noexcept { - const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); - if (abort) { - return; //shutdown had been called before. - } - - assert(m_private_atomic_abort.load(std::memory_order_relaxed) == false); - m_private_atomic_abort.store(true, std::memory_order_relaxed); - - { - std::unique_lock lock(m_lock); - m_abort = true; - } - - m_condition.notify_one(); - m_thread.join(); - - destroy_tasks(); -} \ No newline at end of file diff --git a/concurrencpp/src/executors/worker_thread_executor.h b/concurrencpp/src/executors/worker_thread_executor.h deleted file mode 100644 index 93d8a938..00000000 --- a/concurrencpp/src/executors/worker_thread_executor.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef CONCURRENCPP_WORKER_THREAD_EXECUTOR_H -#define CONCURRENCPP_WORKER_THREAD_EXECUTOR_H - -#include "derivable_executor.h" - -#include "../threads/thread.h" - -#include - -namespace concurrencpp { - class alignas(64) worker_thread_executor final : public derivable_executor { - - private: - std::deque> m_private_queue; - std::atomic_bool m_private_atomic_abort; - details::thread m_thread; - const char m_padding[64] = {}; - std::mutex m_lock; - std::deque> m_public_queue; - std::condition_variable m_condition; - bool m_abort; - std::atomic_bool m_atomic_abort; - - void destroy_tasks() noexcept; - - bool drain_queue_impl(); - bool drain_queue(); - void work_loop() noexcept; - - void enqueue_local(std::experimental::coroutine_handle<> task); - void enqueue_local(std::span> task); - - void enqueue_foreign(std::experimental::coroutine_handle<> task); - void enqueue_foreign(std::span> task); - - public: - worker_thread_executor(); - ~worker_thread_executor() noexcept; - - void enqueue(std::experimental::coroutine_handle<> task) override; - void enqueue(std::span> tasks) override; - - int max_concurrency_level() const noexcept override; - - bool shutdown_requested() const noexcept override; - void shutdown() noexcept override; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/forward_declerations.h b/concurrencpp/src/forward_declerations.h deleted file mode 100644 index d1a7027c..00000000 --- a/concurrencpp/src/forward_declerations.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef CONCURRENCPP_FORWARD_DECLERATIONS_H -#define CONCURRENCPP_FORWARD_DECLERATIONS_H - -namespace concurrencpp { - struct null_result; - - template class result; - template class result_promise; - - class runtime; - - class timer_queue; - class timer; - - class executor; - class inline_executor; - class thread_pool_executor; - class thread_executor; - class worker_thread_executor; - class manual_executor; -} - -#endif //FORWARD_DECLERATIONS_H diff --git a/concurrencpp/src/results/constants.h b/concurrencpp/src/results/constants.h deleted file mode 100644 index 5c62d078..00000000 --- a/concurrencpp/src/results/constants.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_CONSTS_H -#define CONCURRENCPP_RESULT_CONSTS_H - -namespace concurrencpp::details::consts { - inline const char* k_result_promise_set_result_error_msg = - "result_promise::set_result() - empty result_promise."; - - inline const char* k_result_promise_set_exception_error_msg = - "result_promise::set_exception() - empty result_promise."; - - inline const char* k_result_promise_set_exception_null_exception_error_msg = - "result_promise::set_exception() - exception pointer is null."; - - inline const char* k_result_promise_set_from_function_error_msg = - "result_promise::set_from_function() - empty result_promise."; - - inline const char* k_result_promise_get_result_error_msg = - "result_promise::get_result() - empty result_promise."; - - inline const char* k_result_promise_get_result_already_retrieved_error_msg = - "result_promise::get_result() - result was already retrieved."; - - - inline const char* k_result_status_error_msg = - "result::status() - result is empty."; - - inline const char* k_result_get_error_msg = - "result::get() - result is empty."; - - inline const char* k_result_wait_error_msg = - "result::wait() - result is empty."; - - inline const char* k_result_wait_for_error_msg = - "result::wait_for() - result is empty."; - - inline const char* k_result_wait_until_error_msg = - "result::wait_until() - result is empty."; - - inline const char* k_result_operator_co_await_error_msg = - "result::operator co_await() - result is empty."; - - inline const char* k_result_await_via_error_msg = - "result::await_via() - result is empty."; - - inline const char* k_result_await_via_executor_null_error_msg = - "result::await_via() - given executor is null."; - - inline const char* k_result_resolve_error_msg = - "result::resolve() - result is empty."; - - inline const char* k_result_resolve_via_error_msg = - "result::resolve_via() - result is empty."; - - inline const char* k_result_resolve_via_executor_null_error_msg = - "result::resolve_via() - given executor is null."; - - inline const char* k_result_awaitable_error_msg = - "concurrencpp::awaitable_type::await_suspend() - awaitable is empty."; - - inline const char* k_result_resolver_error_msg = - "result_resolver::await_suspend() - awaitable is empty."; - - inline const char* k_executor_exception_error_msg = - "concurrencpp::result - an exception was thrown while trying to enqueue result continuation."; - - - inline const char* k_make_exceptional_result_exception_null_error_msg = - "make_exception_result() - given exception_ptr is null."; - - - inline const char* k_when_all_empty_result_error_msg = - "concurrencpp::when_all() - one of the result objects is empty."; - - inline const char* k_when_any_empty_result_error_msg = - "concurrencpp::when_any() - one of the result objects is empty."; - - inline const char* k_when_any_empty_range_error_msg = - "concurrencpp::when_any() - given range contains no elements."; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/executor_exception.h b/concurrencpp/src/results/executor_exception.h deleted file mode 100644 index a62c1651..00000000 --- a/concurrencpp/src/results/executor_exception.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTOR_ERROR_H -#define CONCURRENCPP_EXECUTOR_ERROR_H - -#include "constants.h" - -#include "../forward_declerations.h" - -#include -#include - -namespace concurrencpp::errors { - struct executor_exception final : public std::runtime_error { - std::exception_ptr thrown_exception; - std::shared_ptr throwing_executor; - - executor_exception( - std::exception_ptr thrown_exception, - std::shared_ptr throwing_executor) noexcept : - runtime_error(details::consts::k_executor_exception_error_msg), - thrown_exception(thrown_exception), - throwing_executor(throwing_executor) {} - - executor_exception(const executor_exception&) noexcept = default; - executor_exception(executor_exception&&) noexcept = default; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/make_result.h b/concurrencpp/src/results/make_result.h deleted file mode 100644 index e03ddb46..00000000 --- a/concurrencpp/src/results/make_result.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef CONCURRENCPP_MAKE_RESULT_H -#define CONCURRENCPP_MAKE_RESULT_H - -#include "result.h" - -namespace concurrencpp { - template - result make_ready_result(argument_types&& ... arguments) { - static_assert( - std::is_constructible_v || std::is_same_v, - "concurrencpp::make_ready_result - <> is not constructible from <"); - - auto result_core_ptr = std::make_shared>(); - result_core_ptr->set_result(std::forward(arguments)...); - result_core_ptr->publish_result(); - return { std::move(result_core_ptr) }; - } - - inline result make_ready_result() { - auto result_core_ptr = std::make_shared>(); - result_core_ptr->set_result(); - result_core_ptr->publish_result(); - return { std::move(result_core_ptr) }; - } - - template - result make_exceptional_result(std::exception_ptr exception_ptr) { - if (!static_cast(exception_ptr)) { - throw std::invalid_argument(details::consts::k_make_exceptional_result_exception_null_error_msg); - } - - auto result_core_ptr = std::make_shared>(); - result_core_ptr->set_exception(exception_ptr); - result_core_ptr->publish_result(); - return { std::move(result_core_ptr) }; - } - - template - result make_exceptional_result(exception_type exception) { - return make_exceptional_result(std::make_exception_ptr(exception)); - } -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/promises.cpp b/concurrencpp/src/results/promises.cpp deleted file mode 100644 index fde18c47..00000000 --- a/concurrencpp/src/results/promises.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "promises.h" - -using concurrencpp::details::coroutine_per_thread_data; - -thread_local coroutine_per_thread_data coroutine_per_thread_data::s_tl_per_thread_data; - -void concurrencpp::details::initial_accumulating_awaiter::await_suspend( - std::experimental::coroutine_handle<> handle) const noexcept { - auto& per_thread_data = coroutine_per_thread_data::s_tl_per_thread_data; - auto accumulator = std::exchange(per_thread_data.accumulator, nullptr); - - assert(accumulator != nullptr); - accumulator->push_back(handle); -} \ No newline at end of file diff --git a/concurrencpp/src/results/promises.h b/concurrencpp/src/results/promises.h deleted file mode 100644 index d4a65bfb..00000000 --- a/concurrencpp/src/results/promises.h +++ /dev/null @@ -1,280 +0,0 @@ -#ifndef CONCURRENCPP_PROMISES_H -#define CONCURRENCPP_PROMISES_H - -#include "result_core.h" - -#include "../errors.h" - -#include - -namespace concurrencpp::details { - struct coroutine_per_thread_data { - executor* executor = nullptr; - std::vector>* accumulator = nullptr; - - static thread_local coroutine_per_thread_data s_tl_per_thread_data; - }; - - template - struct initial_scheduling_awaiter : public std::experimental::suspend_always { - void await_suspend(std::experimental::coroutine_handle<> handle) const { - auto& per_thread_data = coroutine_per_thread_data::s_tl_per_thread_data; - auto executor_base_ptr = std::exchange(per_thread_data.executor, nullptr); - - assert(executor_base_ptr != nullptr); - assert(dynamic_cast(executor_base_ptr) != nullptr); - - auto executor_ptr = static_cast(executor_base_ptr); - executor_ptr->enqueue(handle); - } - }; - - template<> - struct initial_scheduling_awaiter : - public std::experimental::suspend_never {}; - - struct initial_accumulating_awaiter : public std::experimental::suspend_always { - void await_suspend(std::experimental::coroutine_handle<> handle) const noexcept; - }; - - template - struct initialy_rescheduled_promise { - - static_assert( - std::is_base_of_v, - "concurrencpp::initialy_rescheduled_promise<> - <> isn't driven from concurrencpp::executor."); - - template - static void* operator new (size_t size, argument_types&& ...) { - return ::operator new(size); - } - - template - static void* operator new ( - size_t size, - executor_tag, - executor_type* executor_ptr, - argument_types&& ...) { - assert(executor_ptr != nullptr); - assert(coroutine_per_thread_data::s_tl_per_thread_data.executor == nullptr); - coroutine_per_thread_data::s_tl_per_thread_data.executor = executor_ptr; - - return ::operator new(size); - } - - template - static void* operator new ( - size_t size, - executor_tag, - std::shared_ptr executor, - argument_types&& ... args) { - return operator new(size, executor_tag{}, executor.get(), std::forward(args)...); - } - - template - static void* operator new ( - size_t size, - executor_tag, - executor_type& executor, - argument_types&& ... args) { - - return operator new(size, executor_tag{}, std::addressof(executor), std::forward(args)...); - } - - initial_scheduling_awaiter initial_suspend() const noexcept { - return {}; - } - }; - - struct initialy_resumed_promise { - std::experimental::suspend_never initial_suspend() const noexcept { - return {}; - } - }; - - struct bulk_promise { - template - static void* operator new (size_t size, argument_types&& ...) { - return ::operator new(size); - } - - template - static void* operator new ( - size_t size, - executor_bulk_tag, - std::vector>* accumulator, - argument_types&& ...) { - - assert(accumulator != nullptr); - assert(coroutine_per_thread_data::s_tl_per_thread_data.accumulator == nullptr); - coroutine_per_thread_data::s_tl_per_thread_data.accumulator = accumulator; - - return ::operator new(size); - } - - initial_accumulating_awaiter initial_suspend() const noexcept { - return {}; - } - }; - - struct null_result_promise { - null_result get_return_object() const noexcept { - return {}; - } - - std::experimental::suspend_never final_suspend() const noexcept { - return {}; - } - - void unhandled_exception() const noexcept {} - void return_void() const noexcept {} - }; - - template - struct return_value_struct { - template - void return_value(return_type&& value) { - auto self = static_cast(this); - self->set_result(std::forward(value)); - } - }; - - template - struct return_value_struct { - void return_void() noexcept { - auto self = static_cast(this); - self->set_result(); - } - }; - - struct result_publisher : public std::experimental::suspend_always { - template - bool await_suspend(std::experimental::coroutine_handle handle) const noexcept { - handle.promise().publish_result(); - return false; //don't suspend, resume and destroy this - } - }; - - template - struct result_coro_promise : public return_value_struct, type> { - - private: - std::shared_ptr> m_result_ptr; - - public: - result_coro_promise() : m_result_ptr(std::make_shared>()) {} - - ~result_coro_promise() noexcept { - if (!static_cast(this->m_result_ptr)) { - return; - } - - auto broken_task_error = - std::make_exception_ptr(concurrencpp::errors::broken_task("coroutine was destroyed before finishing execution")); - this->m_result_ptr->set_exception(broken_task_error); - this->m_result_ptr->publish_result(); - } - - template - void set_result(argument_types&& ... args) { - this->m_result_ptr->set_result(std::forward(args)...); - } - - void unhandled_exception() noexcept { - this->m_result_ptr->set_exception(std::current_exception()); - } - - ::concurrencpp::result get_return_object() noexcept { - return { this->m_result_ptr }; - } - - void publish_result() noexcept { - this->m_result_ptr->publish_result(); - this->m_result_ptr.reset(); - } - - result_publisher final_suspend() const noexcept { - return {}; - } - }; - - struct initialy_resumed_null_result_promise : - public initialy_resumed_promise, public null_result_promise {}; - - template - struct initialy_resumed_result_promise : - public initialy_resumed_promise, public result_coro_promise {}; - - template - struct initialy_rescheduled_null_result_promise : - public initialy_rescheduled_promise, public null_result_promise {}; - - template - struct initialy_rescheduled_result_promise : - public initialy_rescheduled_promise, public result_coro_promise{}; - - struct bulk_null_result_promise : public bulk_promise, public null_result_promise {}; - - template - struct bulk_result_promise : public bulk_promise, public result_coro_promise {}; -} - -namespace std::experimental { - //No executor + No result - template - struct coroutine_traits<::concurrencpp::null_result, arguments...> { - using promise_type = concurrencpp::details::initialy_resumed_null_result_promise; - }; - - //No executor + result - template - struct coroutine_traits<::concurrencpp::result, arguments...> { - using promise_type = concurrencpp::details::initialy_resumed_result_promise; - }; - - //Executor + no result - template - struct coroutine_traits, arguments...> { - using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; - }; - - template - struct coroutine_traits { - using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; - }; - - template - struct coroutine_traits { - using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; - }; - - //Executor + result - template - struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, std::shared_ptr, arguments...> { - using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; - }; - - template - struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, executor_type*, arguments...> { - using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; - }; - - template - struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, executor_type&, arguments...> { - using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; - }; - - //Bulk + no result - template - struct coroutine_traits<::concurrencpp::null_result,concurrencpp::details::executor_bulk_tag, std::vector>*, arguments...> { - using promise_type = concurrencpp::details::bulk_null_result_promise; - }; - - //Bulk + result - template - struct coroutine_traits<::concurrencpp::result, concurrencpp::details::executor_bulk_tag, std::vector>*, arguments...> { - using promise_type = concurrencpp::details::bulk_result_promise; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/result.h b/concurrencpp/src/results/result.h deleted file mode 100644 index 5aa27c21..00000000 --- a/concurrencpp/src/results/result.h +++ /dev/null @@ -1,234 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_H -#define CONCURRENCPP_RESULT_H - -#include "constants.h" -#include "promises.h" -#include "result_awaitable.h" - -#include "../errors.h" -#include "../forward_declerations.h" - -#include "../utils/bind.h" - -#include - -namespace concurrencpp { - template - class result { - - static constexpr auto valid_result_type_v = - std::is_same_v || std::is_nothrow_move_constructible_v; - - static_assert(valid_result_type_v, - "concurrencpp::result - <> should be now-throw-move constructable or void."); - - friend class details::when_result_helper; - - private: - std::shared_ptr> m_state; - - void throw_if_empty(const char* message) const { - if (m_state.get() != nullptr) { - return; - } - - throw errors::empty_result(message); - } - - public: - result() noexcept = default; - ~result() noexcept = default; - result(result&& rhs) noexcept = default; - - result(std::shared_ptr> state) noexcept : m_state(std::move(state)) {} - - result& operator = (result&& rhs) noexcept { - if (this != &rhs) { - m_state = std::move(rhs.m_state); - } - - return *this; - } - - result(const result& rhs) = delete; - result& operator = (const result& rhs) = delete; - - operator bool() const noexcept { - return m_state.get() != nullptr; - } - - result_status status() const { - throw_if_empty(details::consts::k_result_status_error_msg); - return m_state->status(); - } - - void wait() { - throw_if_empty(details::consts::k_result_wait_error_msg); - m_state->wait(); - } - - template - result_status wait_for(std::chrono::duration duration) { - throw_if_empty(details::consts::k_result_wait_for_error_msg); - return m_state->wait_for(duration); - } - - template< class clock, class duration > - result_status wait_until(std::chrono::time_point timeout_time) { - throw_if_empty(details::consts::k_result_wait_until_error_msg); - return m_state->wait_until(timeout_time); - } - - type get() { - throw_if_empty(details::consts::k_result_get_error_msg); - auto state = std::move(m_state); - state->wait(); - return state->get(); - } - - auto operator co_await() { - throw_if_empty(details::consts::k_result_operator_co_await_error_msg); - return awaitable{ std::move(m_state) }; - } - - auto await_via( - std::shared_ptr executor, - bool force_rescheduling = true) { - throw_if_empty(details::consts::k_result_await_via_error_msg); - - if (!static_cast(executor)) { - throw std::invalid_argument(details::consts::k_result_await_via_executor_null_error_msg); - } - - return via_awaitable{ std::move(m_state), std::move(executor), force_rescheduling }; - } - - auto resolve() { - throw_if_empty(details::consts::k_result_resolve_error_msg); - return resolve_awaitable{ std::move(m_state) }; - } - - auto resolve_via( - std::shared_ptr executor, - bool force_rescheduling = true) { - throw_if_empty(details::consts::k_result_resolve_via_error_msg); - - if (!static_cast(executor)) { - throw std::invalid_argument(details::consts::k_result_resolve_via_executor_null_error_msg); - } - - return resolve_via_awaitable{ std::move(m_state), std::move(executor), force_rescheduling }; - } - }; -} - -namespace concurrencpp { - template - class result_promise { - - private: - std::shared_ptr> m_state; - bool m_result_retrieved; - - void throw_if_empty(const char* message) const { - if (static_cast(m_state)) { - return; - } - - throw errors::empty_result_promise(message); - } - - void break_task_if_needed() noexcept { - if (!static_cast(m_state)) { - return; - } - - if (!m_result_retrieved) { //no result to break. - return; - } - - auto exception_ptr = - std::make_exception_ptr(errors::broken_task("result_promise - broken task.")); - m_state->set_exception(exception_ptr); - m_state->publish_result(); - } - - public: - result_promise() : - m_state(std::make_shared>()), - m_result_retrieved(false){} - - result_promise(result_promise&& rhs) noexcept : - m_state(std::move(rhs.m_state)), - m_result_retrieved(rhs.m_result_retrieved) {} - - ~result_promise() noexcept { - break_task_if_needed(); - } - - result_promise& operator = (result_promise&& rhs) noexcept { - if (this != &rhs) { - break_task_if_needed(); - m_state = std::move(rhs.m_state); - m_result_retrieved = rhs.m_result_retrieved; - } - - return *this; - } - - explicit operator bool() const noexcept { return static_cast(m_state); } - - template - void set_result(argument_types&& ... arguments) { - constexpr auto is_constructable = - std::is_constructible_v || std::is_same_v; - static_assert(is_constructable, - "result_promise::set_result() - <> is not constructable from <>"); - - throw_if_empty(details::consts::k_result_promise_set_result_error_msg); - - m_state->set_result(std::forward(arguments)...); - m_state->publish_result(); - m_state.reset(); - } - - void set_exception(std::exception_ptr exception_ptr) { - throw_if_empty(details::consts::k_result_promise_set_exception_error_msg); - - if (!static_cast(exception_ptr)) { - throw std::invalid_argument(details::consts::k_result_promise_set_exception_null_exception_error_msg); - } - - m_state->set_exception(exception_ptr); - m_state->publish_result(); - m_state.reset(); - } - - template - void set_from_function(callable_type&& callable, argument_types&& ... args) { - constexpr auto is_invokable = std::is_invocable_r_v; - - static_assert(is_invokable, - "result_promise::set_from_function() - function(args...) is not invokable or its return type can't be used to construct <>"); - - throw_if_empty(details::consts::k_result_promise_set_from_function_error_msg); - m_state->from_callable( - details::bind(std::forward(callable), std::forward(args)...)); - m_state->publish_result(); - m_state.reset(); - } - - result get_result() { - throw_if_empty(details::consts::k_result_get_error_msg); - - if (m_result_retrieved) { - throw errors::result_already_retrieved(details::consts::k_result_promise_get_result_already_retrieved_error_msg); - } - - m_result_retrieved = true; - return result(m_state); - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/result_awaitable.h b/concurrencpp/src/results/result_awaitable.h deleted file mode 100644 index 21cad0ee..00000000 --- a/concurrencpp/src/results/result_awaitable.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_AWAITABLE_H -#define CONCURRENCPP_RESULT_AWAITABLE_H - -#include "constants.h" -#include "result_fwd_declerations.h" - -#include "../errors.h" - -namespace concurrencpp { - template - class awaitable : public std::experimental::suspend_always { - - private: - std::shared_ptr> m_state; - - public: - awaitable(std::shared_ptr> state) noexcept : - m_state(std::move(state)) {} - - awaitable(awaitable&& rhs) noexcept = default; - - bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { - if (!static_cast(m_state)) { - throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); - } - - return m_state->await(caller_handle); - } - - type await_resume() { - auto state = std::move(m_state); - return state->get(); - } - }; - - template - class via_awaitable : public std::experimental::suspend_always { - - private: - std::shared_ptr> m_state; - std::shared_ptr m_executor; - const bool m_force_rescheduling; - - public: - via_awaitable( - std::shared_ptr> state, - std::shared_ptr executor, - bool force_rescheduling) noexcept : - m_state(std::move(state)), - m_executor(std::move(executor)), - m_force_rescheduling(force_rescheduling) {} - - via_awaitable(via_awaitable&& rhs) noexcept = default; - - bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { - if (!static_cast(m_state)) { - throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); - } - - return m_state->await_via(std::move(m_executor), caller_handle, m_force_rescheduling); - } - - type await_resume() { - auto state = std::move(m_state); - return state->get(); - } - }; - - template - class resolve_awaitable final : public std::experimental::suspend_always { - - private: - std::shared_ptr> m_state; - - public: - resolve_awaitable(std::shared_ptr> state) noexcept : - m_state(std::move(state)) {} - - resolve_awaitable(resolve_awaitable&&) noexcept = default; - - bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { - if (!static_cast(m_state)) { - throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); - } - - return m_state->await(caller_handle); - } - - result await_resume() { - return result(std::move(m_state)); - } - }; - - template - class resolve_via_awaitable final : public std::experimental::suspend_always { - - private: - std::shared_ptr> m_state; - std::shared_ptr m_executor; - const bool m_force_rescheduling; - - public: - resolve_via_awaitable( - std::shared_ptr> state, - std::shared_ptr executor, - bool force_rescheduling) noexcept : - m_state(state), - m_executor(std::move(executor)), - m_force_rescheduling(force_rescheduling) {} - - resolve_via_awaitable(resolve_via_awaitable&&) noexcept = default; - - bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { - if (!static_cast(m_state)) { - throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); - } - - return m_state->await_via(std::move(m_executor), caller_handle, m_force_rescheduling); - } - - result await_resume() noexcept { - return result(std::move(m_state)); - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/result_core.cpp b/concurrencpp/src/results/result_core.cpp deleted file mode 100644 index c0f0055d..00000000 --- a/concurrencpp/src/results/result_core.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "result_core.h" -#include "../executors/executor.h" - -using concurrencpp::details::wait_context; -using concurrencpp::details::await_context; -using concurrencpp::details::result_core_base; - -void wait_context::wait() noexcept { - std::unique_lock lock(m_lock); - m_condition.wait(lock, [this] { - return m_ready; - }); -} - -bool wait_context::wait_for(size_t milliseconds) noexcept { - std::unique_lock lock(m_lock); - return m_condition.wait_for(lock, std::chrono::milliseconds(milliseconds), [this] { - return m_ready; - }); -} - -void wait_context::notify() noexcept { - { - std::unique_lock lock(m_lock); - m_ready = true; - } - - m_condition.notify_all(); -} - -void result_core_base::wait() { - const auto state = m_pc_state.load(std::memory_order_acquire); - if (state == pc_state::producer) { - return; - } - - auto wait_ctx = std::make_shared(); - - assert_consumer_idle(); - m_consumer.emplace<3>(wait_ctx); - - auto expected_state = pc_state::idle; - const auto idle = m_pc_state.compare_exchange_strong( - expected_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (!idle) { - assert_done(); - return; - } - - wait_ctx->wait(); - assert_done(); -} - -bool result_core_base::await(std::experimental::coroutine_handle<> caller_handle) noexcept { - const auto state = m_pc_state.load(std::memory_order_acquire); - if (state == pc_state::producer) { - return false; //don't suspend - } - - assert_consumer_idle(); - m_consumer.emplace<1>(caller_handle); - - auto expected_state = pc_state::idle; - const auto idle = m_pc_state.compare_exchange_strong( - expected_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (!idle) { - assert_done(); - } - - return idle; //if idle = true, suspend -} - -bool result_core_base::await_via( - std::shared_ptr executor, - std::experimental::coroutine_handle<> caller_handle, - bool force_rescheduling) { - assert(static_cast(executor)); - auto handle_done_state = [this] (await_context& await_ctx, bool force_rescheduling) -> bool { - assert_done(); - - if (!force_rescheduling) { - return false; //resume caller. - } - - await_ctx.first->enqueue(await_ctx.second); - return true; - }; - - const auto state = m_pc_state.load(std::memory_order_acquire); - if (state == pc_state::producer) { - await_context await_ctx(std::move(executor), caller_handle); - return handle_done_state(await_ctx, force_rescheduling); - } - - assert_consumer_idle(); - m_consumer.emplace<2>(std::move(executor), caller_handle); - - auto expected_state = pc_state::idle; - const auto idle = m_pc_state.compare_exchange_strong( - expected_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (idle) { - return true; - } - - //the result is available - auto await_ctx = std::move(std::get<2>(m_consumer)); - return handle_done_state(await_ctx, force_rescheduling); -} - -void result_core_base::when_all(std::shared_ptr when_all_state) noexcept { - const auto state = m_pc_state.load(std::memory_order_acquire); - if (state == pc_state::producer) { - return when_all_state->on_result_ready(); - } - - assert_consumer_idle(); - m_consumer.emplace<4>(std::move(when_all_state)); - - auto expected_state = pc_state::idle; - const auto idle = m_pc_state.compare_exchange_strong( - expected_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (idle) { - return; - } - - assert_done(); - auto& state_ptr = std::get<4>(m_consumer); - state_ptr->on_result_ready(); -} - -concurrencpp::details::when_any_status result_core_base::when_any( - std::shared_ptr when_any_state, - size_t index) noexcept { - const auto state = m_pc_state.load(std::memory_order_acquire); - if (state == pc_state::producer) { - return when_any_status::result_ready; - } - - assert_consumer_idle(); - m_consumer.emplace<5>(std::move(when_any_state), index); - - auto expected_state = pc_state::idle; - const auto idle = m_pc_state.compare_exchange_strong( - expected_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (idle) { - return when_any_status::set; - } - - //no need to continue the iteration of when_any, we've found a ready task. - //tell all predecessors to rewind their state. - assert_done(); - return when_any_status::result_ready; -} - -void result_core_base::try_rewind_consumer() noexcept { - const auto pc_state = this->m_pc_state.load(std::memory_order_acquire); - if (pc_state != pc_state::consumer) { - return; - } - - auto expected_consumer_state = pc_state::consumer; - const auto consumer = this->m_pc_state.compare_exchange_strong( - expected_consumer_state, - pc_state::idle, - std::memory_order_acq_rel); - - if (!consumer) { - assert_done(); - return; - } - - m_consumer.emplace<0>(); -} - -void result_core_base::schedule_coroutine( - concurrencpp::executor& executor, - std::experimental::coroutine_handle<> coro_handle) { - assert(static_cast(coro_handle)); - assert(!coro_handle.done()); - executor.enqueue(coro_handle); -} - -void result_core_base::schedule_coroutine(await_context& await_ctx) { - auto executor = await_ctx.first.get(); - auto coro_handle = await_ctx.second; - - assert(executor != nullptr); - schedule_coroutine(*executor, coro_handle); -} diff --git a/concurrencpp/src/results/result_core.h b/concurrencpp/src/results/result_core.h deleted file mode 100644 index 7d59e82f..00000000 --- a/concurrencpp/src/results/result_core.h +++ /dev/null @@ -1,417 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_CORE_H -#define CONCURRENCPP_RESULT_CORE_H - -#include "executor_exception.h" -#include "result_fwd_declerations.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "../errors.h" - -namespace concurrencpp::details { - class wait_context { - - private: - std::mutex m_lock; - std::condition_variable m_condition; - bool m_ready = false; - - public: - void wait() noexcept; - bool wait_for(size_t milliseconds) noexcept; - - void notify() noexcept; - }; - - struct when_all_state_base { - std::atomic_size_t m_counter; - - virtual ~when_all_state_base() noexcept = default; - virtual void on_result_ready() noexcept = 0; - }; - - struct when_any_state_base { - std::atomic_bool m_fulfilled = false; - std::recursive_mutex m_lock; - - virtual ~when_any_state_base() noexcept = default; - virtual void on_result_ready(size_t) noexcept = 0; - }; - - using when_any_ctx = std::pair, size_t>; - - template - class async_result { - - public: - using result_context = std::variant; - - protected: - result_context m_result; - - template - void build_impl(std::false_type /*no_throw*/, argument_types&& ... arguments) { - try { - m_result.template emplace<1>(std::forward(arguments)...); - } - catch (...) { - assert(m_result.index() == -1); - m_result.template emplace<0>(); - throw; - } - } - - template - void build_impl(std::true_type /*no_throw*/, argument_types&& ... arguments) noexcept { - m_result.template emplace<1>(std::forward(arguments)...); - } - - public: - template - void build(argument_types&& ... arguments) { - using intc = std::is_nothrow_constructible; - build_impl(intc(), std::forward(arguments)...); - } - - type get() { - const auto index = m_result.index(); - assert(index != 0); - - if (index == 2) { - std::rethrow_exception(std::get<2>(m_result)); - } - - return std::move(std::get<1>(m_result)); - } - }; - - template<> - class async_result { - - public: - using result_context = std::variant; - - protected: - result_context m_result; - - public: - void build() noexcept { - m_result.emplace<1>(); - } - - void get() { - const auto index = m_result.index(); - assert(index != 0); - - if (index == 2) { - std::rethrow_exception(std::get<2>(m_result)); - } - } - }; - - template - class async_result { - - public: - using result_context = std::variant; - - protected: - result_context m_result; - - public: - void build(type& reference) noexcept { - m_result.template emplace<1>(std::addressof(reference)); - } - - type& get() { - const auto index = m_result.index(); - assert(index != 0); - - if (index == 2) { - std::rethrow_exception(std::get<2>(m_result)); - } - - auto pointer = std::get<1>(m_result); - assert(pointer != nullptr); - assert(reinterpret_cast(pointer) % alignof(type) == 0); - - return *pointer; - } - }; - - class result_core_base { - - public: - using consumer_context = std::variant< - std::monostate, - std::experimental::coroutine_handle<>, - await_context, - std::shared_ptr, - std::shared_ptr, - when_any_ctx>; - - enum class pc_state { - idle, - producer, - consumer - }; - - protected: - consumer_context m_consumer; - std::atomic m_pc_state; - - void assert_consumer_idle() const noexcept { - assert(m_consumer.index() == 0); - } - - void assert_done() const noexcept { - assert(m_pc_state.load(std::memory_order_relaxed) == pc_state::producer); - } - - public: - void wait(); - - bool await(std::experimental::coroutine_handle<> caller_handle) noexcept; - - bool await_via( - std::shared_ptr executor, - std::experimental::coroutine_handle<> caller_handle, - bool force_rescheduling); - - void when_all(std::shared_ptr when_all_state) noexcept; - - when_any_status when_any(std::shared_ptr when_any_state, size_t index) noexcept; - - void try_rewind_consumer() noexcept; - - static void schedule_coroutine(executor& executor, std::experimental::coroutine_handle<> handle); - static void schedule_coroutine(await_context& await_ctx); - }; - - template - class result_core : public async_result, public result_core_base { - - using result_context = typename async_result::result_context; - using consumer_context = typename result_core_base::consumer_context; - - private: - void assert_producer_idle() const noexcept { - assert(this->m_result.index() == 0); - } - - void clear_producer() noexcept { - this->m_result.template emplace<0>(); - } - - void schedule_continuation(await_context& await_ctx) noexcept { - try { - this->schedule_coroutine(await_ctx); - } - catch (...) { - auto executor_error = std::make_exception_ptr( - errors::executor_exception( - std::current_exception(), - std::move(await_ctx.first))); - - //consumer can't interfere here - clear_producer(); - this->set_exception(executor_error); - await_ctx.second(); - } - } - - template - void from_callable(std::true_type /*is_void_type*/, callable_type&& callable) { - callable(); - this->set_result(); - } - - template - void from_callable(std::false_type /*is_void_type*/, callable_type&& callable) { - this->set_result(callable()); - } - - public: - template - void set_result(argument_types&& ... arguments) { - this->assert_producer_idle(); - this->build(std::forward(arguments)... ); - } - - void set_exception(std::exception_ptr error) noexcept { - assert(error != nullptr); - this->assert_producer_idle(); - this->m_result.template emplace<2>(std::move(error)); - } - - //Consumer-side functions - result_status status() const noexcept { - const auto state = this->m_pc_state.load(std::memory_order_acquire); - assert(state != pc_state::consumer); - - if (state == pc_state::idle) { - return result_status::idle; - } - - const auto index = this->m_result.index(); - if (index == 1) { - return result_status::value; - } - - assert(index == 2); - return result_status::exception; - } - - template - concurrencpp::result_status wait_for(std::chrono::duration duration) { - auto get_result_status = [this] { - return this->m_result.index() == 1 ? - result_status::value : result_status::exception; - }; - - const auto state_0 = this->m_pc_state.load(std::memory_order_acquire); - if (state_0 == pc_state::producer) { - return get_result_status(); - } - - assert_consumer_idle(); - - auto wait_ctx = std::make_shared(); - m_consumer.emplace<3>(wait_ctx); - - auto expected_idle_state = pc_state::idle; - const auto idle_0 = this->m_pc_state.compare_exchange_strong( - expected_idle_state, - pc_state::consumer, - std::memory_order_acq_rel); - - if (!idle_0) { - assert_done(); - return get_result_status(); - } - - const auto ms = std::chrono::duration_cast(duration).count(); - if (wait_ctx->wait_for(static_cast(ms + 1))) { - assert_done(); - return get_result_status(); - } - - /* - now we need to rewind what we've done: the producer might try to access - the consumer context while we rewind the consumer context back to nothing. - first we'll cas the status back to idle. if we failed - the producer has set its result, - then there's no point in continue rewinding - we just return the status of the result. - if we managed to rewind the status back to idle, - then the consumer is "protected" because the producer will not try - to access the consumer if the flag doesn't say so. - */ - auto expected_consumer_state = pc_state::consumer; - const auto idle_1 = this->m_pc_state.compare_exchange_strong( - expected_consumer_state, - pc_state::idle, - std::memory_order_acq_rel); - - if (!idle_1) { - assert_done(); - return get_result_status(); - } - - m_consumer.emplace<0>(); - return result_status::idle; - } - - template - concurrencpp::result_status wait_until(const std::chrono::time_point& timeout_time) { - const auto now = clock::now(); - if (timeout_time <= now) { - return status(); - } - - const auto diff = timeout_time - now; - return wait_for(diff); - } - - type get() { - assert_done(); - return this->async_result::get(); - } - - void publish_result() noexcept { - const auto state_before = this->m_pc_state.exchange( - pc_state::producer, - std::memory_order_acq_rel); - - assert(state_before != pc_state::producer); - - if (state_before == pc_state::idle) { - return; - } - - assert(state_before == pc_state::consumer); - - switch (this->m_consumer.index()) - { - case 0: { - return; - } - - case 1: { - auto handle = std::get<1>(this->m_consumer); - handle(); - return; - } - - case 2: { - return this->schedule_continuation(std::get<2>(this->m_consumer)); - } - - case 3: { - auto& wait_ctx = std::get<3>(this->m_consumer); - wait_ctx->notify(); - return; - } - - case 4: { - auto& when_all_state = std::get<4>(this->m_consumer); - assert(static_cast(when_all_state)); - when_all_state->on_result_ready(); - return; - } - - case 5: { - auto& when_any_ctx = std::get<5>(this->m_consumer); - auto& when_any_state = when_any_ctx.first; - assert(static_cast(when_any_state)); - when_any_state->on_result_ready(when_any_ctx.second); - return; - } - - default: - break; - } - - assert(false); - } - - template - void from_callable(callable_type&& callable) { - using is_void = std::is_same; - - try { - this->from_callable(is_void{}, std::forward(callable)); - } - catch (...) { - this->set_exception(std::current_exception()); - } - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/result_fwd_declerations.h b/concurrencpp/src/results/result_fwd_declerations.h deleted file mode 100644 index 52e71198..00000000 --- a/concurrencpp/src/results/result_fwd_declerations.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_FWD_DECLERATIONS_H -#define CONCURRENCPP_RESULT_FWD_DECLERATIONS_H - -#include "../forward_declerations.h" - -#include -#include -#include - -namespace concurrencpp { - template class result; - template class result_promise; - - template class awaitable; - template class via_awaitable; - template class resolve_awaitable; - template class resolve_via_awaitable; - - struct executor_tag {}; - - struct null_result {}; - - enum class result_status { - idle, - value, - exception - }; -} - -namespace concurrencpp::details { - template - class result_core; - - using await_context = std::pair, std::experimental::coroutine_handle<>>; - - struct executor_bulk_tag {}; - - class when_result_helper; - - enum class when_any_status { - set, - result_ready - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/results/when_result.h b/concurrencpp/src/results/when_result.h deleted file mode 100644 index c794660c..00000000 --- a/concurrencpp/src/results/when_result.h +++ /dev/null @@ -1,400 +0,0 @@ -#ifndef CONCURRENCPP_WHEN_RESULT_H -#define CONCURRENCPP_WHEN_RESULT_H - -#include -#include -#include -#include -#include - -#include "make_result.h" - -#include "../errors.h" - -namespace concurrencpp::details { - class when_result_helper { - - private: - template - static void throw_if_empty_single(const char* error_message, const result& result) { - if (static_cast(result)) { - return; - } - - throw errors::empty_result(error_message); - } - - static void throw_if_empty_impl(const char* error_message) noexcept { - (void)error_message; - } - - template - static void throw_if_empty_impl( - const char* error_message, - const result& result, - result_types&& ... results) { - throw_if_empty_single(error_message, result); - throw_if_empty_impl(error_message, std::forward(results)...); - } - - public: - template - static result_core* get_core(result& result) noexcept { - return result.m_state.get(); - } - - template - static void throw_if_empty_tuple(const char* error_message, result_types&& ... results) { - throw_if_empty_impl(error_message, std::forward(results)...); - } - - template - static void throw_if_empty_range(const char* error_message, iterator_type begin, iterator_type end) { - for (; begin != end; ++begin) { - throw_if_empty_single(error_message, *begin); - } - } - }; - - template - class when_all_tuple_state final : - public when_all_state_base, - public std::enable_shared_from_this> { - - using tuple_type = std::tuple; - - private: - tuple_type m_tuple; - std::shared_ptr> m_core_ptr; - - template - void set_state(result& result) noexcept { - auto core_ptr = when_result_helper::get_core(result); - core_ptr->when_all(this->shared_from_this()); - } - - public: - when_all_tuple_state(result_types&& ... results) noexcept : - m_tuple(std::forward(results)...), - m_core_ptr(std::make_shared>()) { - m_counter = sizeof ... (result_types); - } - - void set_state() noexcept { - std::apply([this](auto&... result) { (this->set_state(result), ...); }, m_tuple); - } - - void on_result_ready() noexcept override { - if (m_counter.fetch_sub(1, std::memory_order_relaxed) != 1) { - return; - } - - m_core_ptr->set_result(std::move(m_tuple)); - m_core_ptr->publish_result(); - } - - result get_result() noexcept { - return { m_core_ptr }; - } - }; - - template - class when_all_vector_state final : - public when_all_state_base, - public std::enable_shared_from_this> { - - private: - std::vector m_vector; - std::shared_ptr>> m_core_ptr; - - template - void set_state(result& result) noexcept { - auto core_ptr = when_result_helper::get_core(result);; - core_ptr->when_all(this->shared_from_this()); - } - - public: - template - when_all_vector_state(iterator_type begin, iterator_type end) : - m_vector(std::make_move_iterator(begin), std::make_move_iterator(end)), - m_core_ptr(std::make_shared>>()) { - m_counter = m_vector.size(); - } - - void set_state() noexcept { - for (auto& result : m_vector) { - set_state(result); - } - } - - void on_result_ready() noexcept override { - if (m_counter.fetch_sub(1, std::memory_order_relaxed) != 1) { - return; - } - - m_core_ptr->set_result(std::move(m_vector)); - m_core_ptr->publish_result(); - } - - result> get_result() noexcept { - return { m_core_ptr }; - } - }; -} - -namespace concurrencpp { - template - struct when_any_result { - std::size_t index; - sequence_type results; - - when_any_result() noexcept : index(static_cast(-1)) {} - - template - when_any_result(size_t index, result_types&& ... results) noexcept : - index(index), - results(std::forward(results)...) {} - - when_any_result(when_any_result&&) noexcept = default; - when_any_result& operator = (when_any_result&&) noexcept = default; - }; -} - -namespace concurrencpp::details { - template - class when_any_tuple_state final : - public when_any_state_base, - public std::enable_shared_from_this> { - - using tuple_type = std::tuple; - - private: - tuple_type m_results; - std::shared_ptr>> m_core_ptr; - - template - std::pair set_state_impl(std::unique_lock& lock) noexcept { //should be called under a lock. - assert(lock.owns_lock()); - (void)lock; - - auto& result = std::get(m_results); - auto core_ptr = when_result_helper::get_core(result); - const auto status = core_ptr->when_any(this->shared_from_this(), index); - - if (status == when_any_status::result_ready) { - return { status, index }; - } - - const auto res = set_state_impl(lock); - - if (res.first == when_any_status::result_ready) { - core_ptr->try_rewind_consumer(); - } - - return res; - } - - template<> - std::pair set_state_impl(std::unique_lock& lock) noexcept { - (void)lock; - return { when_any_status::set, static_cast(-1) }; - } - - template - void unset_state(std::unique_lock& lock, size_t done_index) noexcept { - assert(lock.owns_lock()); - (void)lock; - if (index != done_index) { - auto core_ptr = when_result_helper::get_core(std::get(m_results)); - core_ptr->try_rewind_consumer(); - } - - unset_state(lock, done_index); - } - - template<> - void unset_state(std::unique_lock& lock, size_t done_index) noexcept { - (void)lock; - (void)done_index; - } - - void complete_promise(std::unique_lock& lock, size_t index) noexcept { - assert(lock.owns_lock()); - (void)lock; - - m_core_ptr->set_result(index, std::move(m_results)); - m_core_ptr->publish_result(); - } - - public: - when_any_tuple_state(result_types&& ... results) : - m_results(std::forward(results)...), - m_core_ptr(std::make_shared>>()) {} - - void on_result_ready(size_t index) noexcept override { - if (m_fulfilled.exchange(true, std::memory_order_relaxed)) { - return; - } - - std::unique_lock lock(m_lock); - unset_state<0>(lock, index); - complete_promise(lock, index); - } - - void set_state() noexcept { - std::unique_lock lock(m_lock); - const auto [status, index] = set_state_impl<0>(lock); - - if (status == when_any_status::result_ready) { - complete_promise(lock, index); - } - } - - result> get_result() noexcept { - return { m_core_ptr }; - } - }; - - template - class when_any_vector_state final : - public when_any_state_base, - public std::enable_shared_from_this> { - - private: - std::vector m_results; - std::shared_ptr>>> m_core_ptr; - - void unset_state(std::unique_lock& lock) noexcept { - assert(lock.owns_lock()); - (void)lock; - - for (auto& result : m_results) { - assert(static_cast(result)); - auto core_ptr = when_result_helper::get_core(result); - core_ptr->try_rewind_consumer(); - } - } - - void complete_promise(std::unique_lock& lock, size_t index) noexcept { - assert(lock.owns_lock()); - (void)lock; - m_core_ptr->set_result(index, std::move(m_results)); - m_core_ptr->publish_result(); - } - - public: - template - when_any_vector_state(iterator_type begin, iterator_type end) : - m_results(std::make_move_iterator(begin), std::make_move_iterator(end)), - m_core_ptr(std::make_shared>>>()) {} - - void on_result_ready(size_t index) noexcept override { - if (m_fulfilled.exchange(true, std::memory_order_relaxed)) { - return; - } - - std::unique_lock lock(m_lock); - unset_state(lock); - complete_promise(lock, index); - } - - void set_state() noexcept { - std::unique_lock lock(m_lock); - for (size_t i = 0; i < m_results.size(); i++) { - if (m_fulfilled.load(std::memory_order_relaxed)) { - return; - } - - auto core_ptr = when_result_helper::get_core(m_results[i]); - const auto res = core_ptr->when_any(this->shared_from_this(), i); - - if (res == when_any_status::result_ready) { - on_result_ready(i); - return; - } - } - } - - result>> get_result() noexcept { - return { m_core_ptr }; - } - }; -} - -namespace concurrencpp { - inline result> when_all() { - return make_ready_result>(); - } - - template - result::type...>> - when_all(result_types&& ... results) { - - details::when_result_helper::throw_if_empty_tuple( - details::consts::k_when_all_empty_result_error_msg, - std::forward(results)...); - - auto when_all_state = - std::make_shared::type...>>( - std::forward(results)...); - - when_all_state->set_state(); - return when_all_state->get_result(); - } - - template - result::value_type>> - when_all(iterator_type begin, iterator_type end) { - - details::when_result_helper::throw_if_empty_range( - details::consts::k_when_all_empty_result_error_msg, - begin, - end); - - using type = typename std::iterator_traits::value_type; - - if (begin == end) { - return make_ready_result>(); - } - - auto when_all_state = std::make_shared>(begin, end); - when_all_state->set_state(); - return when_all_state->get_result(); - } - - template - result>> when_any(result_types&& ... results) { - static_assert( - sizeof ... (result_types) != 0, - "concurrencpp::when_any - the function must accept at least one result"); - - details::when_result_helper::throw_if_empty_tuple( - details::consts::k_when_any_empty_result_error_msg, - std::forward(results)...); - - auto state = std::make_shared>(std::forward(results)...); - state->set_state(); - return state->get_result(); - } - - template - result::value_type>>> - when_any(iterator_type begin, iterator_type end) { - details::when_result_helper::throw_if_empty_range( - details::consts::k_when_any_empty_result_error_msg, - begin, - end); - - if (begin == end) { - throw std::invalid_argument(details::consts::k_when_any_empty_range_error_msg); - } - - using type = typename std::iterator_traits::value_type; - - auto state = std::make_shared>(begin, end); - state->set_state(); - return state->get_result(); - } -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/runtime/constants.h b/concurrencpp/src/runtime/constants.h deleted file mode 100644 index 4eda54e3..00000000 --- a/concurrencpp/src/runtime/constants.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef CONCURRENCPP_CONSTANTS_H -#define CONCURRENCPP_CONSTANTS_H - -#include - -namespace concurrencpp::details::consts { - constexpr static size_t k_cpu_threadpool_worker_count_factor = 1; - constexpr static size_t k_background_threadpool_worker_count_factor = 4; - constexpr static size_t k_max_worker_waiting_time_sec = 2 * 60; - constexpr static size_t k_default_number_of_cores = 8; - - constexpr static unsigned int k_concurrencpp_version_major = 0; - constexpr static unsigned int k_concurrencpp_version_minor = 0; - constexpr static unsigned int k_concurrencpp_version_revision = 6; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/runtime/runtime.cpp b/concurrencpp/src/runtime/runtime.cpp deleted file mode 100644 index 102fd1fe..00000000 --- a/concurrencpp/src/runtime/runtime.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "runtime.h" -#include "constants.h" - -#include "../executors/constants.h" -#include "../executors/inline_executor.h" -#include "../executors/thread_pool_executor.h" -#include "../executors/thread_executor.h" -#include "../executors/worker_thread_executor.h" -#include "../executors/manual_executor.h" - -#include "../timers/timer_queue.h" - -namespace concurrencpp::details { - size_t default_max_cpu_workers() noexcept { - return static_cast(thread::hardware_concurrency() * consts::k_cpu_threadpool_worker_count_factor); - } - - size_t default_max_background_workers() noexcept { - return static_cast(thread::hardware_concurrency() * consts::k_background_threadpool_worker_count_factor); - } - - constexpr static auto k_default_max_worker_wait_time = std::chrono::seconds(consts::k_max_worker_waiting_time_sec); -} - -using concurrencpp::runtime; -using concurrencpp::runtime_options; -using concurrencpp::details::executor_collection; - -/* - executor_collection; -*/ - -void executor_collection::register_executor(std::shared_ptr executor) { - std::unique_lock lock(m_lock); - assert(std::find(m_executors.begin(), m_executors.end(), executor) == m_executors.end()); - m_executors.emplace_back(std::move(executor)); -} - -void executor_collection::shutdown_all() noexcept { - std::unique_lock lock(m_lock); - for (auto& executor : m_executors) { - assert(static_cast(executor)); - executor->shutdown(); - } - - m_executors = {}; -} - -/* - runtime_options -*/ - -runtime_options::runtime_options() noexcept : - max_cpu_threads(details::default_max_cpu_workers()), - max_cpu_thread_waiting_time(details::k_default_max_worker_wait_time), - max_background_threads(details::default_max_background_workers()), - max_background_thread_waiting_time(details::k_default_max_worker_wait_time) {} - -/* - runtime -*/ - -runtime::runtime() : runtime(runtime_options()) {} - -runtime::runtime(const runtime_options& options) { - m_timer_queue = std::make_shared<::concurrencpp::timer_queue>(); - - m_inline_executor = std::make_shared<::concurrencpp::inline_executor>(); - m_registered_executors.register_executor(m_inline_executor); - - m_thread_pool_executor = std::make_shared<::concurrencpp::thread_pool_executor>( - details::consts::k_thread_pool_executor_name, - options.max_cpu_threads, - options.max_cpu_thread_waiting_time); - m_registered_executors.register_executor(m_thread_pool_executor); - - m_background_executor = std::make_shared<::concurrencpp::thread_pool_executor>( - details::consts::k_background_executor_name, - options.max_background_threads, - options.max_background_thread_waiting_time); - m_registered_executors.register_executor(m_background_executor); - - m_thread_executor = std::make_shared<::concurrencpp::thread_executor>(); - m_registered_executors.register_executor(m_thread_executor); -} - -concurrencpp::runtime::~runtime() noexcept { - m_timer_queue->shutdown(); - m_registered_executors.shutdown_all(); -} - -std::shared_ptr runtime::timer_queue() const noexcept { - return m_timer_queue; -} - -std::shared_ptr runtime::inline_executor() const noexcept { - return m_inline_executor; -} - -std::shared_ptr runtime::thread_pool_executor() const noexcept { - return m_thread_pool_executor; -} - -std::shared_ptr runtime::background_executor() const noexcept { - return m_background_executor; -} - -std::shared_ptr runtime::thread_executor() const noexcept { - return m_thread_executor; -} - -std::shared_ptr runtime::make_worker_thread_executor() { - auto executor = std::make_shared(); - m_registered_executors.register_executor(executor); - return executor; -} - -std::shared_ptr runtime::make_manual_executor() { - auto executor = std::make_shared(); - m_registered_executors.register_executor(executor); - return executor; -} - -std::tuple runtime::version() noexcept { - return { - details::consts::k_concurrencpp_version_major, - details::consts::k_concurrencpp_version_minor, - details::consts::k_concurrencpp_version_revision - }; -} \ No newline at end of file diff --git a/concurrencpp/src/runtime/runtime.h b/concurrencpp/src/runtime/runtime.h deleted file mode 100644 index c652e184..00000000 --- a/concurrencpp/src/runtime/runtime.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef CONCURRENCPP_RUNTIME_H -#define CONCURRENCPP_RUNTIME_H - -#include "constants.h" - -#include "../forward_declerations.h" - -#include -#include -#include -#include - -namespace concurrencpp::details { - class executor_collection { - - private: - std::mutex m_lock; - std::vector> m_executors; - - public: - void register_executor(std::shared_ptr executor); - void shutdown_all() noexcept; - }; -} - -namespace concurrencpp { - struct runtime_options { - size_t max_cpu_threads; - std::chrono::seconds max_cpu_thread_waiting_time; - - size_t max_background_threads; - std::chrono::seconds max_background_thread_waiting_time; - - runtime_options() noexcept; - - runtime_options(const runtime_options&) noexcept = default; - runtime_options& operator = (const runtime_options&) noexcept = default; - }; - - class runtime { - - private: - std::shared_ptr m_timer_queue; - - std::shared_ptr m_inline_executor; - std::shared_ptr m_thread_pool_executor; - std::shared_ptr m_background_executor; - std::shared_ptr m_thread_executor; - - details::executor_collection m_registered_executors; - - public: - runtime(); - runtime(const concurrencpp::runtime_options& options); - - ~runtime() noexcept; - - std::shared_ptr timer_queue() const noexcept; - - std::shared_ptr inline_executor() const noexcept; - std::shared_ptr thread_pool_executor() const noexcept; - std::shared_ptr background_executor() const noexcept; - std::shared_ptr thread_executor() const noexcept; - - std::shared_ptr make_worker_thread_executor(); - std::shared_ptr make_manual_executor(); - - static std::tuple version() noexcept; - - template - std::shared_ptr make_executor(argument_types&& ... arguments) { - static_assert(std::is_base_of_v, - "concurrencpp::runtime::make_executor - <> is not a derived class of concurrencpp::executor."); - - static_assert(std::is_constructible_v, - "concurrencpp::runtime::make_executor - can not build <> from <>."); - - static_assert(!std::is_abstract_v, - "concurrencpp::runtime::make_executor - <> is an abstract class."); - - auto executor = std::make_shared(std::forward(arguments)...); - m_registered_executors.register_executor(executor); - return executor; - } - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/threads/thread.cpp b/concurrencpp/src/threads/thread.cpp deleted file mode 100644 index fc6c501d..00000000 --- a/concurrencpp/src/threads/thread.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "thread.h" - -#include "../platform_defs.h" - -#include - -#include "../runtime/constants.h" - -using concurrencpp::details::thread; - -namespace concurrencpp::details { - std::uintptr_t generate_thread_id() noexcept { - static std::atomic_uintptr_t s_id_seed = 1; - return s_id_seed.fetch_add(1, std::memory_order_relaxed); - } - - struct thread_per_thread_data { - const std::uintptr_t id = generate_thread_id(); - }; - - static thread_local thread_per_thread_data s_tl_thread_per_data; -} - -std::thread::id thread::get_id() const noexcept { - return m_thread.get_id(); -} - -std::uintptr_t thread::get_current_virtual_id() noexcept { - return s_tl_thread_per_data.id; -} - -bool thread::joinable() const noexcept { - return m_thread.joinable(); -} - -void thread::join() { - m_thread.join(); -} - -size_t thread::hardware_concurrency() noexcept { - const auto hc = std::thread::hardware_concurrency(); - return (hc != 0) ? hc : consts::k_default_number_of_cores; -} - -#ifdef CRCPP_WIN_OS - -#include - -void thread::set_name(std::string_view name) noexcept { - const std::wstring utf16_name(name.begin(), name.end()); //concurrencpp strings are always ASCII (english only) - ::SetThreadDescription(::GetCurrentThread(), utf16_name.data()); -} - -#elif defined (CRCPP_UNIX_OS) - -#include - -void thread::set_name(std::string_view name) noexcept { - ::pthread_setname_np(::pthread_self(), name.data()); -} - -#elif defined(CRCPP_MACH_OS) - -#include - -void thread::set_name(std::string_view name) noexcept { - ::pthread_setname_np(name.data()); -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/threads/thread.h b/concurrencpp/src/threads/thread.h deleted file mode 100644 index b49f7e28..00000000 --- a/concurrencpp/src/threads/thread.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CONCURRENCPP_THREAD_H -#define CONCURRENCPP_THREAD_H - -#include -#include - -namespace concurrencpp::details { - class thread { - - private: - std::thread m_thread; - - static void set_name(std::string_view name) noexcept; - - public: - thread() noexcept = default; - thread(thread&&) noexcept = default; - - template - thread(std::string name, callable_type&& callable) { - m_thread = std::thread([name = std::move(name), callable = std::forward(callable)]{ - set_name(name); - callable(); - }); - } - - thread& operator = (thread&& rhs) noexcept = default; - - std::thread::id get_id() const noexcept; - - static std::uintptr_t get_current_virtual_id() noexcept; - - bool joinable() const noexcept; - void join(); - - static size_t hardware_concurrency() noexcept; - }; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/timers/constants.h b/concurrencpp/src/timers/constants.h deleted file mode 100644 index e444dd58..00000000 --- a/concurrencpp/src/timers/constants.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CONCURRENCPP_TIMER_CONSTS_H -#define CONCURRENCPP_TIMER_CONSTS_H - - -namespace concurrencpp::details::consts { - inline const char* k_timer_empty_get_due_time_err_msg = "timer::get_due_time() - timer is empty."; - inline const char* k_timer_empty_get_frequency_err_msg = "timer::get_frequency() - timer is empty."; - inline const char* k_timer_empty_get_executor_err_msg = "timer::get_executor() - timer is empty."; - inline const char* k_timer_empty_get_timer_queue_err_msg = "timer::get_timer_queue() - timer is empty."; - inline const char* k_timer_empty_set_frequency_err_msg = "timer::set_frequency() - timer is empty."; - - inline const char* k_timer_queue_make_timer_executor_null_err_msg = "timer_queue::make_timer() - executor is null."; - inline const char* k_timer_queue_make_oneshot_timer_executor_null_err_msg = "timer_queue::make_one_shot_timer() - executor is null."; - inline const char* k_timer_queue_make_delay_object_executor_null_err_msg = "timer_queue::make_delay_object() - executor is null."; - inline const char* k_timer_queue_shutdown_err_msg = "timer_queue has been shut down."; -} - -#endif \ No newline at end of file diff --git a/concurrencpp/src/timers/timer.cpp b/concurrencpp/src/timers/timer.cpp deleted file mode 100644 index e3f1004a..00000000 --- a/concurrencpp/src/timers/timer.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "timer.h" -#include "timer_queue.h" -#include "constants.h" - -#include "../errors.h" -#include "../results/result.h" -#include "../executors/executor.h" - -using concurrencpp::timer; -using concurrencpp::details::timer_state; -using concurrencpp::details::timer_state_base; - -timer_state_base::timer_state_base( - size_t due_time, - size_t frequency, - std::shared_ptr executor, - std::weak_ptr timer_queue, - bool is_oneshot) noexcept : - m_timer_queue(std::move(timer_queue)), - m_executor(std::move(executor)), - m_due_time(due_time), - m_frequency(frequency), - m_deadline(make_deadline(milliseconds(due_time))), - m_is_oneshot(is_oneshot){ - assert(static_cast(m_executor)); -} - -void timer_state_base::fire() { - const auto frequency = m_frequency.load(std::memory_order_relaxed); - m_deadline = make_deadline(milliseconds(frequency)); - - assert(static_cast(m_executor)); - - m_executor->post([self = shared_from_this()]() mutable { - self->execute(); - }); -} - -timer::timer(std::shared_ptr timer_impl) noexcept : - m_state(std::move(timer_impl)) {} - -timer::~timer() noexcept { - cancel(); -} - -void timer::throw_if_empty(const char* error_message) const { - if (static_cast(m_state)) { - return; - } - - throw concurrencpp::errors::empty_timer(error_message); -} - -std::chrono::milliseconds timer::get_due_time() const { - throw_if_empty(details::consts::k_timer_empty_get_due_time_err_msg); - return std::chrono::milliseconds(m_state->get_due_time()); -} - -std::chrono::milliseconds timer::get_frequency() const { - throw_if_empty(details::consts::k_timer_empty_get_frequency_err_msg); - return std::chrono::milliseconds(m_state->get_frequency()); -} - -std::shared_ptr timer::get_executor() const { - throw_if_empty(details::consts::k_timer_empty_get_executor_err_msg); - return m_state->get_executor(); -} - -std::weak_ptr timer::get_timer_queue() const { - throw_if_empty(details::consts::k_timer_empty_get_timer_queue_err_msg); - return m_state->get_timer_queue(); -} - -void timer::cancel() { - if (!static_cast(m_state)) { - return; - } - - auto state = std::move(m_state); - auto timer_queue = state->get_timer_queue().lock(); - - if (!static_cast(timer_queue)) { - return; - } - - timer_queue->remove_timer(std::move(state)); -} - -void timer::set_frequency(std::chrono::milliseconds new_frequency) { - throw_if_empty(details::consts::k_timer_empty_set_frequency_err_msg); - return m_state->set_new_frequency(new_frequency.count()); -} - -timer& timer::operator = (timer&& rhs) noexcept { - if (this == &rhs) { - return *this; - } - - if (static_cast(*this)) { - cancel(); - } - - m_state = std::move(rhs.m_state); - return *this; -} diff --git a/concurrencpp/src/timers/timer.h b/concurrencpp/src/timers/timer.h deleted file mode 100644 index 86c7f98e..00000000 --- a/concurrencpp/src/timers/timer.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef CONCURRENCPP_TIMER_H -#define CONCURRENCPP_TIMER_H - -#include -#include -#include - -#include "../forward_declerations.h" - -namespace concurrencpp::details { - class timer_state_base : public std::enable_shared_from_this { - - public: - using clock_type = std::chrono::high_resolution_clock; - using time_point = std::chrono::time_point; - using milliseconds = std::chrono::milliseconds; - - private: - const std::weak_ptr m_timer_queue; - const std::shared_ptr m_executor; - const size_t m_due_time; - std::atomic_size_t m_frequency; - time_point m_deadline; //set by the c.tor, changed only by the timer_queue thread. - const bool m_is_oneshot; - - static time_point make_deadline(milliseconds diff) noexcept { - return clock_type::now() + diff; - } - - public: - timer_state_base( - size_t due_time, - size_t frequency, - std::shared_ptr executor, - std::weak_ptr timer_queue, - bool is_oneshot) noexcept; - - virtual ~timer_state_base() noexcept = default; - - virtual void execute() = 0; - - void fire(); - - bool expired(const time_point now) const noexcept { - return m_deadline <= now; - } - - time_point get_deadline() const noexcept { - return m_deadline; - } - - size_t get_frequency() const noexcept { - return m_frequency.load(std::memory_order_relaxed); - } - - size_t get_due_time() const noexcept { - return m_due_time; //no need to synchronize, const anyway. - } - - bool is_oneshot() const noexcept { - return m_is_oneshot; - } - - std::shared_ptr get_executor() const noexcept { - return m_executor; - } - - std::weak_ptr get_timer_queue() const noexcept { - return m_timer_queue; - } - - void set_new_frequency(size_t new_frequency) noexcept { - m_frequency.store(new_frequency, std::memory_order_relaxed); - } - }; - - template - class timer_state final : public timer_state_base { - - private: - callable_type m_callable; - - public: - template - timer_state( - size_t due_time, - size_t frequency, - std::shared_ptr executor, - std::weak_ptr timer_queue, - bool is_oneshot, - given_callable_type&& callable) : - timer_state_base( - due_time, - frequency, - std::move(executor), - std::move(timer_queue), is_oneshot), - m_callable(std::forward(callable)) {} - - void execute() override { - m_callable(); - } - }; -} - -namespace concurrencpp { - class timer { - - private: - std::shared_ptr m_state; - - void throw_if_empty(const char* error_message) const; - - public: - timer() noexcept = default; - ~timer() noexcept; - - timer(std::shared_ptr timer_impl) noexcept; - - timer(timer&& rhs) noexcept = default; - timer& operator = (timer&& rhs) noexcept; - - timer(const timer&) = delete; - timer& operator = (const timer&) = delete; - - void cancel(); - - std::chrono::milliseconds get_due_time() const; - std::shared_ptr get_executor() const; - std::weak_ptr get_timer_queue() const; - - std::chrono::milliseconds get_frequency() const; - void set_frequency(std::chrono::milliseconds new_frequency); - - operator bool() const noexcept { - return static_cast(m_state); - } - }; -} - -#endif //CONCURRENCPP_TIMER_H diff --git a/concurrencpp/src/timers/timer_queue.h b/concurrencpp/src/timers/timer_queue.h deleted file mode 100644 index 6fbc4fc2..00000000 --- a/concurrencpp/src/timers/timer_queue.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef CONCURRENCPP_TIMER_QUEUE_H -#define CONCURRENCPP_TIMER_QUEUE_H - -#include "constants.h" -#include "timer.h" - -#include "../errors.h" - -#include "../utils/bind.h" -#include "../threads/thread.h" - -#include -#include - -#include -#include -#include - -#include - -namespace concurrencpp::details { - enum class timer_request { - add, - remove - }; -} - -namespace concurrencpp { - class timer_queue : public std::enable_shared_from_this { - - public: - using timer_ptr = std::shared_ptr; - using clock_type = std::chrono::high_resolution_clock; - using time_point = std::chrono::time_point; - using request_queue = std::vector>; - - friend class concurrencpp::timer; - - private: - std::atomic_bool m_atomic_abort; - std::mutex m_lock; - request_queue m_request_queue; - details::thread m_worker; - std::condition_variable m_condition; - bool m_abort; - - void ensure_worker_thread(std::unique_lock& lock); - - void add_timer(std::unique_lock& lock, timer_ptr new_timer) noexcept; - void remove_timer(timer_ptr existing_timer) noexcept; - - template - timer_ptr make_timer_impl( - size_t due_time, - size_t frequency, - std::shared_ptr executor, - bool is_oneshot, - callable_type&& callable) { - - assert(static_cast(executor)); - - using decayed_type = typename std::decay_t; - - auto timer_core = std::make_shared>( - due_time, - frequency, - std::move(executor), - weak_from_this(), - is_oneshot, - std::forward(callable)); - - std::unique_lock lock(m_lock); - if (m_abort) { - throw errors::timer_queue_shutdown(details::consts::k_timer_queue_shutdown_err_msg); - } - - ensure_worker_thread(lock); - add_timer(lock, timer_core); - return timer_core; - } - - void work_loop() noexcept; - - public: - timer_queue() noexcept; - ~timer_queue() noexcept; - - void shutdown() noexcept; - bool shutdown_requested() const noexcept; - - template - timer make_timer( - std::chrono::milliseconds due_time, - std::chrono::milliseconds frequency, - std::shared_ptr executor, - callable_type&& callable, - argumet_types&& ... arguments) { - - if (!static_cast(executor)) { - throw std::invalid_argument(details::consts::k_timer_queue_make_timer_executor_null_err_msg); - } - - return make_timer_impl( - due_time.count(), - frequency.count(), - std::move(executor), - false, - details::bind( - std::forward(callable), - std::forward(arguments)...)); - } - - template - timer make_one_shot_timer( - std::chrono::milliseconds due_time, - std::shared_ptr executor, - callable_type&& callable, - argumet_types&& ... arguments) { - - if (!static_cast(executor)) { - throw std::invalid_argument(details::consts::k_timer_queue_make_oneshot_timer_executor_null_err_msg); - } - - return make_timer_impl( - due_time.count(), - 0, - std::move(executor), - true, - details::bind( - std::forward(callable), - std::forward(arguments)...)); - } - - result make_delay_object(std::chrono::milliseconds due_time, std::shared_ptr executor); - }; -} - -#endif //CONCURRENCPP_TIMER_QUEUE_H diff --git a/concurrencpp/src/utils/bind.h b/concurrencpp/src/utils/bind.h deleted file mode 100644 index 07723165..00000000 --- a/concurrencpp/src/utils/bind.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CONCURRENCPP_BIND_H -#define CONCURRENCPP_BIND_H - -#include -#include - -namespace concurrencpp::details { - template - auto bind(callable_type&& callable) { - return std::forward(callable); //no arguments to bind - } - - template - auto bind(callable_type&& callable, argument_types&& ... arguments) { - constexpr static auto inti = std::is_nothrow_invocable_v; - return[ - callable = std::forward(callable), - tuple = std::make_tuple(std::forward(arguments)...) - ]() mutable noexcept(inti) -> decltype(auto) { - return std::apply(callable, tuple); - }; - } -} - -#endif \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..40d5fa1f --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) + +project(concurrencppExamples LANGUAGES CXX) + +foreach(example IN ITEMS + async_file_processing + async_sql + process_monitor + synchronous_web_socket) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/${example}" + "${CMAKE_CURRENT_BINARY_DIR}/${example}") +endforeach() diff --git a/example/async_file_processing/CMakeLists.txt b/example/async_file_processing/CMakeLists.txt new file mode 100644 index 00000000..dbecc93a --- /dev/null +++ b/example/async_file_processing/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +project(async_file_processing LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") +FetchContent_MakeAvailable(concurrencpp) + +include(../../cmake/coroutineOptions.cmake) + +add_executable(async_file_processing source/main.cpp) + +target_compile_features(async_file_processing PRIVATE cxx_std_20) + +target_link_libraries(async_file_processing PRIVATE concurrencpp::concurrencpp) + +target_coroutine_options(async_file_processing) diff --git a/example/async_file_processing/source/main.cpp b/example/async_file_processing/source/main.cpp new file mode 100644 index 00000000..8ea8dc73 --- /dev/null +++ b/example/async_file_processing/source/main.cpp @@ -0,0 +1,93 @@ +/* + In this example, we'll use concurrencpp executors to process a file + asynchronously, the application iterate over the file characters (we assume + ASCII) and replaces a character with another. The application gets three + parameters through the command line arguments: argv[0] - the path to the + binary that created this process (not used in this example) argv[1] - a file + path argv[2] - the character to replace argv[3] - the character to replace + with. + + Since standard file streams are blocking, we would like to execute + file-io operations using the background_executor, who's job is to execute + relatively short-blocking tasks (like file-io). + + Processing the file content is a cpu-bound task (iterating over a binary + buffer and potentially changing characters), so after reading the file we + will resume execution in the thread_pool_executor, + + After content has been modified, it is ready to be re-written back to + the file. we will again schedule a blocking write operation to the + background_executor. +*/ + +#include +#include +#include + +#include "concurrencpp/concurrencpp.h" + +concurrencpp::result replace_chars_in_file(concurrencpp::runtime& runtime, const std::string file_path, char from, char to) { + + auto file_content_result = runtime.background_executor()->submit([file_path] { + std::ifstream input; + input.exceptions(std::ifstream::badbit); + input.open(file_path, std::ios::binary); + std::vector buffer(std::istreambuf_iterator(input), {}); + input.close(); + return buffer; + }); + + // if we just await on the result returned by the background_executor, the + // execution resumes inside the blocking-threadpool. it is important to resume + // execution in the cpu-threadpool by calling result::await_via or + // result::resolve_via. + auto file_content = co_await file_content_result.await_via(runtime.thread_pool_executor()); + + for (auto& c : file_content) { + if (c == from) { + c = to; + } + } + + // schedule the write operation on the background executor and await on it to + // finish. + co_await runtime.background_executor()->submit([file_path, file_content = std::move(file_content)] { + std::ofstream output; + output.exceptions(std::ofstream::badbit); + output.open(file_path, std::ios::binary); + output.write(file_content.data(), file_content.size()); + }); + + std::cout << "file has been modified successfully" << std::endl; +} + +int main(int argc, const char* argv[]) { + if (argc < 4) { + const auto help_msg = "please pass all necessary arguments\n\ +argv[1] - the file to process\n\ +argv[2] - the character to replace\n\ +argv[3] - the character to replace with"; + + std::cerr << help_msg << std::endl; + return -1; + } + + if (std::strlen(argv[2]) != 1 || std::strlen(argv[3]) != 1) { + std::cerr << "argv[2] and argv[3] must be one character only" << std::endl; + return -1; + } + + const auto file_path = std::string(argv[1]); + const auto from_char = argv[2][0]; + const auto to_char = argv[3][0]; + + concurrencpp::runtime runtime; + + try { + replace_chars_in_file(runtime, file_path, from_char, to_char).get(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 0; +} diff --git a/example/async_sql/CMakeLists.txt b/example/async_sql/CMakeLists.txt new file mode 100644 index 00000000..195f0061 --- /dev/null +++ b/example/async_sql/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +project(async_sql LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") +FetchContent_MakeAvailable(concurrencpp) + +include(../../cmake/coroutineOptions.cmake) + +add_executable(async_sql + include/mock_async_sql.h + source/main.cpp + source/mock_async_sql.cpp) + +target_compile_features(async_sql PRIVATE cxx_std_20) + +target_link_libraries(async_sql PRIVATE concurrencpp::concurrencpp) + +target_include_directories(async_sql PUBLIC "${PROJECT_SOURCE_DIR}/include") + +target_coroutine_options(async_sql) diff --git a/example/async_sql/include/mock_async_sql.h b/example/async_sql/include/mock_async_sql.h new file mode 100644 index 00000000..0a490f96 --- /dev/null +++ b/example/async_sql/include/mock_async_sql.h @@ -0,0 +1,39 @@ +#ifndef MOCK_ASYNC_SQL_H +#define MOCK_ASYNC_SQL_H + +#include +#include +#include +#include +#include + +namespace mock_async_sql { + class db_connection; + + struct connection_callback_base { + virtual ~connection_callback_base() noexcept = default; + + virtual void on_connection(std::exception_ptr, std::shared_ptr) = 0; + }; + + struct query_callback_base { + virtual ~query_callback_base() noexcept = default; + + virtual void on_query(std::exception_ptr, std::shared_ptr, std::vector>) = 0; + }; + + class db_connection : public std::enable_shared_from_this { + + public: + db_connection(std::string db_url, std::string username, std::string password) { + (void)db_url; + (void)username; + (void)password; + } + + void connect(std::unique_ptr connection_callback); + void query(std::string query_string, std::unique_ptr query_callback); + }; +} // namespace mock_async_sql + +#endif diff --git a/example/async_sql/source/main.cpp b/example/async_sql/source/main.cpp new file mode 100644 index 00000000..3981dad6 --- /dev/null +++ b/example/async_sql/source/main.cpp @@ -0,0 +1,144 @@ +/* + It's the end of February and a company would like to congratulate all of + its workers who were born in March. To do so, the company uses an + asynchronous sql library, that uses callbacks to marshal asynchronous results + and events. + + When using the library vanilla style, the application creates a + db_connection object, calls db_connection::connect() and provides a callback + that receives the asynchronous error or an open connection to the database. + + The application then proceeds calling db_connection::query() and + provides a callback that receives the asynchronous error or the results in + the form of std::vector< std::vector < std::any > > (a dynamic table). + + This example shows how to marshal asynchronous results and errors using + concurrencpp::result_promise and concurrencpp::result pair. this allows + applications to avoid callback-style code flow and allows using coroutines + instead. + + Do note that this example uses a mock SQL library and no real + connection/query is going to be executed. The constants are made up and + results are hard-coded. This example is only good for showing how to use + third-party libraries with concurrencpp. + + Library's API: + + mock_async_sql::db_connection{ + db_connection(db_url, username, password); + void connect(connection_cb => (error, connection){ ... }); + void query(query_string, query_cb => (error, results){ ... }); + }; + + connection_cb must inherit from mock_async_sql::connection_callback_base + and override its on_connect method. query_cb must inherit from + mock_async_sql::query_callback_base and override its on_query method. +*/ + +#include "mock_async_sql.h" +#include "concurrencpp/concurrencpp.h" + +#include + +using concurrencpp::result; +using concurrencpp::result_promise; + +using mock_async_sql::db_connection; +using mock_async_sql::connection_callback_base; +using mock_async_sql::query_callback_base; + +result> connect_async() { + class connection_callback final : public connection_callback_base { + + private: + result_promise> m_result_promise; + + public: + connection_callback(result_promise> result_promise) noexcept : m_result_promise(std::move(result_promise)) {} + + void on_connection(std::exception_ptr error, std::shared_ptr connection) override { + if (error) { + return m_result_promise.set_exception(error); + } + + m_result_promise.set_result(std::move(connection)); + } + }; + + auto connection = std::make_shared("http://123.45.67.89:8080/db", "admin", "password"); + result_promise> result_promise; + auto result = result_promise.get_result(); + std::unique_ptr callback(new connection_callback(std::move(result_promise))); + + connection->connect(std::move(callback)); + + return result; +} + +result>> query_async(std::shared_ptr open_connection) { + using query_result_type = std::vector>; + + class query_callback final : public query_callback_base { + + private: + result_promise m_result_promise; + + public: + query_callback(result_promise result_promise) noexcept : m_result_promise(std::move(result_promise)) {} + + void on_query(std::exception_ptr error, std::shared_ptr connection, query_result_type results) override { + (void)connection; + if (error) { + return m_result_promise.set_exception(error); + } + + m_result_promise.set_result(std::move(results)); + } + }; + + result_promise result_promise; + auto result = result_promise.get_result(); + std::unique_ptr callback(new query_callback(std::move(result_promise))); + + open_connection->query("select first_name, last_name, age from db.users where birth_month=3", std::move(callback)); + return result; +} + +void print_result(const std::vector>& results) { + for (const auto line : results) { + assert(line.size() == 3); + + assert(line[0].has_value()); + assert(line[0].type() == typeid(const char*)); + + const auto first_name = std::any_cast(line[0]); + + assert(line[1].has_value()); + assert(line[1].type() == typeid(const char*)); + + const auto last_name = std::any_cast(line[1]); + + assert(line[2].has_value()); + assert(line[2].type() == typeid(int)); + + const auto age = std::any_cast(line[2]); + + std::cout << first_name << " " << last_name << " " << age << std::endl; + } +} + +concurrencpp::result print_workers_born_in_march() { + auto connection_ptr = co_await connect_async(); + auto results = co_await query_async(connection_ptr); + print_result(results); +} + +int main() { + try { + print_workers_born_in_march().get(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + } + + return 0; +} diff --git a/example/async_sql/source/mock_async_sql.cpp b/example/async_sql/source/mock_async_sql.cpp new file mode 100644 index 00000000..86837729 --- /dev/null +++ b/example/async_sql/source/mock_async_sql.cpp @@ -0,0 +1,95 @@ +#include "mock_async_sql.h" + +#include +#include +#include +#include + +#include + +namespace mock_async_sql { + class mock_sql_runner { + + private: + std::mutex m_lock; + std::vector> m_tasks; + + /* + Mock connection/query failure: randomize a failure in 5% of the cases + */ + static bool failed() noexcept { + static const int dummy = [] { + std::srand(static_cast(std::time(nullptr))); + return 0; + }(); + + (void)dummy; + + const auto randomized_num = std::rand() % 100; + return randomized_num <= 5; + } + + public: + mock_sql_runner() noexcept = default; + + ~mock_sql_runner() noexcept { + std::unique_lock lock(m_lock); + for (auto& task : m_tasks) { + try { + task.get(); + } catch (...) { + // do nothing. + } + } + } + + void mock_connection(std::shared_ptr connection_ptr, std::unique_ptr cb) { + auto future = std::async(std::launch::async, [connection_ptr, cb = std::move(cb)]() mutable { + std::this_thread::sleep_for(std::chrono::seconds(2)); + + if (failed()) { + auto error_ptr = std::make_exception_ptr(std::runtime_error("db_connection::connect - can't connect.")); + return cb->on_connection(error_ptr, nullptr); + } + + cb->on_connection(nullptr, connection_ptr); + }); + + std::unique_lock lock(m_lock); + m_tasks.emplace_back(std::move(future)); + } + + void mock_query_result(std::shared_ptr connection_ptr, std::unique_ptr cb) { + auto future = std::async(std::launch::async, [connection_ptr, cb = std::move(cb)]() mutable { + std::this_thread::sleep_for(std::chrono::seconds(2)); + + if (failed()) { + auto error_ptr = std::make_exception_ptr(std::runtime_error("db_connection::query - can't execute query.")); + return cb->on_query(error_ptr, nullptr, {}); + } + + std::vector> results; + results.emplace_back(std::vector {"Daniel", "Williams", 34}); + results.emplace_back(std::vector {"Sarah", "Jones", 31}); + results.emplace_back(std::vector {"Mark", "Williams", 40}); + results.emplace_back(std::vector {"Jack", "Davis", 37}); + + cb->on_query(nullptr, connection_ptr, std::move(results)); + }); + + std::unique_lock lock(m_lock); + m_tasks.emplace_back(std::move(future)); + } + }; + + mock_sql_runner global_runner; +} // namespace mock_async_sql + +void mock_async_sql::db_connection::connect(std::unique_ptr connection_callback) { + global_runner.mock_connection(shared_from_this(), std::move(connection_callback)); +} + +void mock_async_sql::db_connection::query(std::string query_string, std::unique_ptr query_callback) { + (void)query_string; + global_runner.mock_query_result(shared_from_this(), std::move(query_callback)); +} diff --git a/example/process_monitor/CMakeLists.txt b/example/process_monitor/CMakeLists.txt new file mode 100644 index 00000000..d33962e0 --- /dev/null +++ b/example/process_monitor/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +project(process_monitor LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") +FetchContent_MakeAvailable(concurrencpp) + +include(../../cmake/coroutineOptions.cmake) + +add_executable(process_monitor + include/mock_process_monitor.h + source/main.cpp + source/mock_process_monitor.cpp) + +target_compile_features(process_monitor PRIVATE cxx_std_20) + +target_link_libraries(process_monitor PRIVATE concurrencpp::concurrencpp) + +target_include_directories(process_monitor PUBLIC "${PROJECT_SOURCE_DIR}/include") + +target_coroutine_options(process_monitor) diff --git a/example/process_monitor/include/mock_process_monitor.h b/example/process_monitor/include/mock_process_monitor.h new file mode 100644 index 00000000..f269026e --- /dev/null +++ b/example/process_monitor/include/mock_process_monitor.h @@ -0,0 +1,25 @@ +#ifndef MOCK_PROCESS_MONITOR_H +#define MOCK_PROCESS_MONITOR_H + +#include + +namespace mock_process_monitor { + class monitor { + + private: + size_t m_last_cpu_usage; + size_t m_last_memory_usage; + size_t m_last_thread_count; + size_t m_last_kernel_object_count; + + public: + monitor() noexcept; + + size_t cpu_usage() noexcept; + size_t memory_usage() noexcept; + size_t thread_count() noexcept; + size_t kernel_object_count() noexcept; + }; +} // namespace mock_process_monitor + +#endif diff --git a/example/process_monitor/source/main.cpp b/example/process_monitor/source/main.cpp new file mode 100644 index 00000000..b1cf0ae7 --- /dev/null +++ b/example/process_monitor/source/main.cpp @@ -0,0 +1,48 @@ +/* + In this example, we will use concurrencpp::timer_queue and + concurrencpp::timer to periodically monitor the state of the process, by + logging the cpu and memory usage along with the thread count and the + kernel-object count. + + Do note that this example uses a mock process monitor. the stats are + made up and don't reflect the real state of this process. this example is + good to show how concurrencpp:timer and concurrencpp::timer_queue can be used + to schedule an asynchronous action periodically. +*/ + +#include "mock_process_monitor.h" +#include "concurrencpp/concurrencpp.h" + +#include + +using namespace std::chrono_literals; + +class process_stat_printer { + + private: + mock_process_monitor::monitor m_process_monitor; + + public: + void operator()() noexcept { + const auto cpu_usage = m_process_monitor.cpu_usage(); + const auto memory_usage = m_process_monitor.memory_usage(); + const auto thread_count = m_process_monitor.thread_count(); + const auto kernel_object_count = m_process_monitor.kernel_object_count(); + + std::cout << "cpu(%): " << cpu_usage << ", " + << "memory(%): " << memory_usage << ", " + << "thread count: " << thread_count << ", " + << "kernel-object count: " << kernel_object_count << std::endl; + } +}; + +int main(int, const char**) { + concurrencpp::runtime runtime; + auto timer_queue = runtime.timer_queue(); + auto thread_pool_executor = runtime.thread_pool_executor(); + auto timer = timer_queue->make_timer(2500ms, 2500ms, thread_pool_executor, process_stat_printer()); + + std::cout << "press enter to quit" << std::endl; + std::getchar(); + return 0; +} diff --git a/example/process_monitor/source/mock_process_monitor.cpp b/example/process_monitor/source/mock_process_monitor.cpp new file mode 100644 index 00000000..7e6582a0 --- /dev/null +++ b/example/process_monitor/source/mock_process_monitor.cpp @@ -0,0 +1,81 @@ +#include "mock_process_monitor.h" + +#include +#include + +using mock_process_monitor::monitor; + +namespace mock_process_monitor { + size_t random_in_range(size_t min, size_t max) { + static const int dummy = [] { + std::srand(static_cast(std::time(nullptr))); + return 0; + }(); + (void)dummy; + + const auto range = max - min + 1; + return std::rand() % range + min; + } +} // namespace mock_process_monitor + +monitor::monitor() noexcept : m_last_cpu_usage(5), m_last_memory_usage(15), m_last_thread_count(4), m_last_kernel_object_count(21) {} + +size_t monitor::cpu_usage() noexcept { + size_t max_range, min_range; + + if (m_last_cpu_usage >= 95) { + max_range = 95; + } else { + max_range = m_last_cpu_usage + 5; + } + + if (m_last_cpu_usage <= 5) { + min_range = 1; + } else { + min_range = m_last_cpu_usage - 5; + } + + m_last_cpu_usage = random_in_range(min_range, max_range); + return m_last_cpu_usage; +} + +size_t monitor::memory_usage() noexcept { + size_t max_range, min_range; + + if (m_last_memory_usage >= 95) { + max_range = 95; + } else { + max_range = m_last_memory_usage + 5; + } + + if (m_last_memory_usage <= 5) { + min_range = 1; + } else { + min_range = m_last_memory_usage - 5; + } + + m_last_memory_usage = random_in_range(min_range, max_range); + return m_last_memory_usage; +} + +size_t monitor::thread_count() noexcept { + const auto max_range = m_last_thread_count + 4; + size_t min_range; + + if (m_last_thread_count <= 5) { + min_range = 5; + } else { + min_range = m_last_thread_count - 2; + } + + m_last_thread_count = random_in_range(min_range, max_range); + return m_last_thread_count; +} + +size_t monitor::kernel_object_count() noexcept { + const auto max_range = m_last_thread_count + 20; + const auto min_range = m_last_thread_count + 3; + + m_last_kernel_object_count = random_in_range(min_range, max_range); + return m_last_kernel_object_count; +} diff --git a/example/synchronous_web_socket/CMakeLists.txt b/example/synchronous_web_socket/CMakeLists.txt new file mode 100644 index 00000000..0084f6d2 --- /dev/null +++ b/example/synchronous_web_socket/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +project(synchronous_web_socket LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") +FetchContent_MakeAvailable(concurrencpp) + +include(../../cmake/coroutineOptions.cmake) + +add_executable(synchronous_web_socket + include/mock_web_socket.h + source/main.cpp + source/mock_web_socket.cpp) + +target_compile_features(synchronous_web_socket PRIVATE cxx_std_20) + +target_link_libraries(synchronous_web_socket PRIVATE concurrencpp::concurrencpp) + +target_include_directories(synchronous_web_socket PUBLIC "${PROJECT_SOURCE_DIR}/include") + +target_coroutine_options(synchronous_web_socket) diff --git a/example/synchronous_web_socket/include/mock_web_socket.h b/example/synchronous_web_socket/include/mock_web_socket.h new file mode 100644 index 00000000..4c8ff115 --- /dev/null +++ b/example/synchronous_web_socket/include/mock_web_socket.h @@ -0,0 +1,13 @@ +#ifndef MOCK_WEB_SOCKET_H +#define MOCK_WEB_SOCKET_H + +#include + +namespace mock_web_socket { + struct web_socket { + void open(std::string_view url); + std::string receive_msg(); + }; +} // namespace mock_web_socket + +#endif diff --git a/example/synchronous_web_socket/source/main.cpp b/example/synchronous_web_socket/source/main.cpp new file mode 100644 index 00000000..92dee38e --- /dev/null +++ b/example/synchronous_web_socket/source/main.cpp @@ -0,0 +1,98 @@ +/* + A company develops a desktop application for weather updates and + reports. The software uses a synchronous web-socket library to communicate + with the weather server. + + Since connecting and reading from a websocket may block for a long time + (up to hours and days, if no update is available), and in an un-predictable + manner, neither the background_executor or a concurrencpp::timer is suitable + for the task. + + We will use the concurrencpp::thread_executor to create a dedicated + thread that runs a work-loop of receiving weather reports from the server and + showing them to the end-user. this way, this long-running, blocking task can + be executed asynchronously without blocking or interfering other tasks + or threads. + + There are merits in using concurrencpp::thread_executor over a raw + std:thread : 1) std::thread is a hard-coded executor. since all concurrencpp + executors are polymorphic, executing tasks is decoupled away. if we want to + change std::thread to let's say, a threadpool, we will have to rewrite these + lines of code dealing with thread creation, task scheduling etc. with + concurrencpp::thread_executor, we just have to change the underlying type of + the executor, and let the dynamic dispatching do the work for us. 2) + std::thread has to be manually joined or detached. every std::thread_executor + is stored and monitored inside the concurrencpp runtime, and is joined + automatically when the runtime object is destroyed. + + Do note that this example uses a mock websocket library and no real + connection/reading is going to be executed. The constants are made up and + results are hard-coded. This example is only good for showing how to use a + potentially long running/blocking tasks with a dedicated thread. + + Library's API: + + mock_web_socket::web_socket { + void open(std::string_view server_url); //blocking + std::string receive_msg(); //blocking + } + +*/ + +#include "concurrencpp/concurrencpp.h" +#include "mock_web_socket.h" + +#include + +class weather_reporter { + + private: + std::atomic_bool m_cancelled; + const std::string m_endpoint_url; + mock_web_socket::web_socket m_web_socket; + + void work_loop() { + std::cout << "weather_reporter::work_loop started running on thread " << std::this_thread::get_id() << std::endl; + + try { + m_web_socket.open(m_endpoint_url); + } catch (const std::exception& e) { + std::cerr << "can't connect to the server " << e.what() << std::endl; + return; + } + + std::cout << "websocket connected successfully to the weather server, waiting for updates." << std::endl; + + while (!m_cancelled.load()) { + const auto msg = m_web_socket.receive_msg(); + std::cout << msg << std::endl; + } + } + + public: + weather_reporter(std::string_view endpoint_url) : m_cancelled(false), m_endpoint_url(endpoint_url) {} + + ~weather_reporter() noexcept { + m_cancelled.store(true); + } + + void launch() { + work_loop(); + } +}; + +int main() { + concurrencpp::runtime runtime; + auto reporter = std::make_shared("wss://www.example.com/weather-server"); + runtime.thread_executor()->post([reporter]() mutable { + reporter->launch(); + }); + + std::cout << "weather reporter is on and running asynchronously. press any key to exit." << std::endl; + + // do other things in the main thread or in other threads without having the + // websocket blocking on reads. + + std::getchar(); + return 0; +} diff --git a/example/synchronous_web_socket/source/mock_web_socket.cpp b/example/synchronous_web_socket/source/mock_web_socket.cpp new file mode 100644 index 00000000..c9604c03 --- /dev/null +++ b/example/synchronous_web_socket/source/mock_web_socket.cpp @@ -0,0 +1,46 @@ +#include "mock_web_socket.h" + +#include +#include +#include + +#include +#include + +namespace mock_web_socket { + bool failed() noexcept { + static const int dummy = [] { + std::srand(static_cast(std::time(nullptr))); + return 0; + }(); + (void)dummy; + + const auto randomized_num = std::rand() % 100; + return randomized_num <= 5; + } + + size_t random_in_range(size_t min, size_t max) { + const auto range = max - min + 1; + return std::rand() % range + min; + } + + const std::string cities[] = {"London", "New York City", "Tokyo", "Paris", "Singapore", "Amsterdam", "Seoul", "Berlin", "Hong Kong", "Sydney"}; +} // namespace mock_web_socket + +void mock_web_socket::web_socket::open(std::string_view) { + if (failed()) { + throw std::runtime_error("web_socket::open - can't connect."); + } + + std::this_thread::sleep_for(std::chrono::seconds(2)); +} + +std::string mock_web_socket::web_socket::receive_msg() { + const auto time_to_sleep = random_in_range(0, 4); + std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep)); + + const auto random_city = cities[random_in_range(0, std::size(cities) - 1)]; + const auto random_temperature = random_in_range(-5, 37); + + return std::string("Current temperature in ") + random_city + " is " + std::to_string(random_temperature) + " deg. C"; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index dced12bf..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(examples) - -add_subdirectory(async_file_processing) -add_subdirectory(async_sql) -add_subdirectory(process_monitor) -add_subdirectory(synchronous_web_socket) diff --git a/examples/async_file_processing/CMakeLists.txt b/examples/async_file_processing/CMakeLists.txt deleted file mode 100644 index d3f69af9..00000000 --- a/examples/async_file_processing/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(async_file_processing) - -add_executable( - async_file_processing - src/main.cpp -) - -target_link_libraries(async_file_processing concurrencpp) diff --git a/examples/async_file_processing/src/main.cpp b/examples/async_file_processing/src/main.cpp deleted file mode 100644 index e8df6f01..00000000 --- a/examples/async_file_processing/src/main.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - In this example, we'll use concurrencpp executors to process a file asynchronously, - the application iterate over the file characters (we assume ASCII) and replaces a character with another. - The application gets three parameters through the command line arguments: - argv[0] - the path to the binary that created this process (not used in this example) - argv[1] - a file path - argv[2] - the character to replace - argv[3] - the character to replace with. - - Since standard file streams are blocking, we would like to execute file-io operations using - the background_executor, who's job is to execute relatively short-blocking tasks (like file-io). - - Processing the file content is a cpu-bound task (iterating over a binary buffer and potentially changing characters), - so after reading the file we will resume execution in the thread_pool_executor, - - After content has been modified, it is ready to be re-written back to the file. we will again schedule a blocking - write operation to the background_executor. -*/ - -#include -#include -#include - -#include "concurrencpp.h" - -concurrencpp::result replace_chars_in_file( - concurrencpp::runtime& runtime, - const std::string file_path, - char from, - char to) { - - auto file_content_result = runtime.background_executor()->submit([file_path] { - std::ifstream input; - input.exceptions(std::ifstream::badbit); - input.open(file_path, std::ios::binary); - std::vector buffer(std::istreambuf_iterator(input), {}); - input.close(); - return buffer; - }); - - //if we just await on the result returned by the background_executor, the execution resumes - //inside the blocking-threadpool. it is important to resume execution in the cpu-threadpool - //by calling result::await_via or result::resolve_via. - auto file_content = co_await file_content_result.await_via(runtime.thread_pool_executor()); - - for (auto& c : file_content) { - if (c == from) { - c = to; - } - } - - //schedule the write operation on the background executor and await on it to finish. - co_await runtime.background_executor()->submit([file_path, file_content = std::move(file_content)]{ - std::ofstream output; - output.exceptions(std::ofstream::badbit); - output.open(file_path, std::ios::binary); - output.write(file_content.data(), file_content.size()); - }); - - std::cout << "file has been modified successfully" << std::endl; -} - -int main(int argc, const char* argv[]) { - if (argc < 4) { - const auto help_msg = "please pass all necessary arguments\n\ -argv[1] - the file to process\n\ -argv[2] - the character to replace\n\ -argv[3] - the character to replace with"; - - std::cerr << help_msg << std::endl; - return -1; - } - - if (std::strlen(argv[2]) != 1 || std::strlen(argv[3]) != 1) { - std::cerr << "argv[2] and argv[3] must be one character only" << std::endl; - return -1; - } - - const auto file_path = std::string(argv[1]); - const auto from_char = argv[2][0]; - const auto to_char = argv[3][0]; - - concurrencpp::runtime runtime; - - try { - replace_chars_in_file(runtime, file_path, from_char, to_char).get(); - } - catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - } - - return 0; -} \ No newline at end of file diff --git a/examples/async_sql/CMakeLists.txt b/examples/async_sql/CMakeLists.txt deleted file mode 100644 index ac136f72..00000000 --- a/examples/async_sql/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(async_sql) - -add_executable( - async_sql - src/main.cpp - src/mock_async_sql.cpp -) - -target_link_libraries(async_sql concurrencpp) diff --git a/examples/async_sql/src/main.cpp b/examples/async_sql/src/main.cpp deleted file mode 100644 index 1f3b417f..00000000 --- a/examples/async_sql/src/main.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - It's the end of February and a company would like to congratulate all of its workers - who were born in March. To do so, the company uses an asynchronous sql library, - that uses callbacks to marshal asynchronous results and events. - - When using the library vanilla style, the application creates a db_connection object, calls - db_connection::connect() and provides a callback that receives the asynchronous error or an open connection to - the database. - - The application then proceeds calling db_connection::query() and provides a callback that receives the asynchronous error or - the results in the form of std::vector< std::vector < std::any > > (a dynamic table). - - This example shows how to marshal asynchronous results and errors using concurrencpp::result_promise and - concurrencpp::result pair. this allows applications to avoid callback-style code flow and allows using - coroutines instead. - - Do note that this example uses a mock SQL library and no real connection/query is going to be executed. - The constants are made up and results are hard-coded. - This example is only good for showing how to use third-party libraries with concurrencpp. - - Library's API: - - mock_async_sql::db_connection{ - db_connection(db_url, username, password); - void connect(connection_cb => (error, connection){ ... }); - void query(query_string, query_cb => (error, results){ ... }); - }; - - connection_cb must inherit from mock_async_sql::connection_callback_base and override its on_connect method. - query_cb must inherit from mock_async_sql::query_callback_base and override its on_query method. -*/ - -#include "mock_async_sql.h" -#include "concurrencpp.h" - -#include - -using concurrencpp::result; -using concurrencpp::result_promise; - -using mock_async_sql::db_connection; -using mock_async_sql::connection_callback_base; -using mock_async_sql::query_callback_base; - -result> connect_async() { - class connection_callback final : public connection_callback_base { - - private: - result_promise> m_result_promise; - - public: - connection_callback(result_promise> result_promise) noexcept : - m_result_promise(std::move(result_promise)) {} - - void on_connection(std::exception_ptr error, std::shared_ptr connection) override { - if (error) { - return m_result_promise.set_exception(error); - } - - m_result_promise.set_result(std::move(connection)); - } - }; - - auto connection = std::make_shared("http://123.45.67.89:8080/db", "admin", "password"); - result_promise> result_promise; - auto result = result_promise.get_result(); - std::unique_ptr callback(new connection_callback(std::move(result_promise))); - - connection->connect(std::move(callback)); - - return result; -} - -result>> query_async(std::shared_ptr open_connection) { - using query_result_type = std::vector>; - - class query_callback final : public query_callback_base { - - private: - result_promise m_result_promise; - - public: - query_callback(result_promise result_promise) noexcept : - m_result_promise(std::move(result_promise)) {} - - void on_query( - std::exception_ptr error, - std::shared_ptr connection, - query_result_type results) override { - (void)connection; - if (error) { - return m_result_promise.set_exception(error); - } - - m_result_promise.set_result(std::move(results)); - } - }; - - result_promise result_promise; - auto result = result_promise.get_result(); - std::unique_ptr callback(new query_callback(std::move(result_promise))); - - open_connection->query("select first_name, last_name, age from db.users where birth_month=3", std::move(callback)); - return result; -} - -void print_result(const std::vector>& results) { - for (const auto line : results) { - assert(line.size() == 3); - - assert(line[0].has_value()); - assert(line[0].type() == typeid(const char*)); - - const auto first_name = std::any_cast(line[0]); - - assert(line[1].has_value()); - assert(line[1].type() == typeid(const char*)); - - const auto last_name = std::any_cast(line[1]); - - assert(line[2].has_value()); - assert(line[2].type() == typeid(int)); - - const auto age = std::any_cast(line[2]); - - std::cout << first_name << " " << last_name << " " << age << std::endl; - } -} - -concurrencpp::result print_workers_born_in_march() { - auto connection_ptr = co_await connect_async(); - auto results = co_await query_async(connection_ptr); - print_result(results); -} - -int main() { - try { - print_workers_born_in_march().get(); - } - catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - } - - return 0; -} \ No newline at end of file diff --git a/examples/async_sql/src/mock_async_sql.cpp b/examples/async_sql/src/mock_async_sql.cpp deleted file mode 100644 index d9e778ec..00000000 --- a/examples/async_sql/src/mock_async_sql.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "mock_async_sql.h" - -#include -#include -#include -#include - -#include - -namespace mock_async_sql { - class mock_sql_runner { - - private: - std::mutex m_lock; - std::vector> m_tasks; - - /* - Mock connection/query failure: randomize a failure in 5% of the cases - */ - static bool failed() noexcept { - static const int dummy = [] { - std::srand(static_cast(std::time(nullptr))); - return 0; - }(); - - (void)dummy; - - const auto randomized_num = std::rand() % 100; - return randomized_num <= 5; - } - - public: - mock_sql_runner() noexcept = default; - - ~mock_sql_runner() noexcept { - std::unique_lock lock(m_lock); - for (auto& task : m_tasks) { - try { - task.get(); - } - catch (...) { - //do nothing. - } - } - } - - void mock_connection( - std::shared_ptr connection_ptr, - std::unique_ptr cb) { - auto future = std::async(std::launch::async, [connection_ptr, cb = std::move(cb)]() mutable { - std::this_thread::sleep_for(std::chrono::seconds(2)); - - if (failed()) { - auto error_ptr = std::make_exception_ptr(std::runtime_error("db_connection::connect - can't connect.")); - return cb->on_connection(error_ptr, nullptr); - } - - cb->on_connection(nullptr, connection_ptr); - }); - - std::unique_lock lock(m_lock); - m_tasks.emplace_back(std::move(future)); - } - - void mock_query_result( - std::shared_ptr connection_ptr, - std::unique_ptr cb) { - auto future = std::async(std::launch::async, [connection_ptr, cb = std::move(cb)]() mutable { - std::this_thread::sleep_for(std::chrono::seconds(2)); - - if (failed()) { - auto error_ptr = std::make_exception_ptr(std::runtime_error("db_connection::query - can't execute query.")); - return cb->on_query(error_ptr, nullptr, {}); - } - - std::vector> results; - results.emplace_back(std::vector{ "Daniel", "Williams", 34 }); - results.emplace_back(std::vector{ "Sarah", "Jones", 31 }); - results.emplace_back(std::vector{ "Mark", "Williams", 40 }); - results.emplace_back(std::vector{ "Jack", "Davis", 37 }); - - cb->on_query(nullptr, connection_ptr, std::move(results)); - }); - - std::unique_lock lock(m_lock); - m_tasks.emplace_back(std::move(future)); - } - }; - - mock_sql_runner global_runner; -} - -void mock_async_sql::db_connection::connect(std::unique_ptr connection_callback) { - global_runner.mock_connection(shared_from_this(), std::move(connection_callback)); -} - -void mock_async_sql::db_connection::query(std::string query_string, std::unique_ptr query_callback) { - (void)query_string; - global_runner.mock_query_result(shared_from_this(), std::move(query_callback)); -} diff --git a/examples/async_sql/src/mock_async_sql.h b/examples/async_sql/src/mock_async_sql.h deleted file mode 100644 index 01223b7d..00000000 --- a/examples/async_sql/src/mock_async_sql.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef MOCK_ASYNC_SQL_H -#define MOCK_ASYNC_SQL_H - -#include -#include -#include -#include -#include - -namespace mock_async_sql { - class db_connection; - - struct connection_callback_base { - virtual ~connection_callback_base() noexcept = default; - - virtual void on_connection(std::exception_ptr, std::shared_ptr) = 0; - }; - - struct query_callback_base { - virtual ~query_callback_base() noexcept = default; - - virtual void on_query( - std::exception_ptr, - std::shared_ptr, - std::vector>) = 0; - }; - - class db_connection : public std::enable_shared_from_this { - - public: - db_connection(std::string db_url, std::string username, std::string password) { - (void)db_url; - (void)username; - (void)password; - } - - void connect(std::unique_ptr connection_callback); - void query(std::string query_string, std::unique_ptr query_callback); - }; -} - -#endif \ No newline at end of file diff --git a/examples/process_monitor/CMakeLists.txt b/examples/process_monitor/CMakeLists.txt deleted file mode 100644 index d34cce61..00000000 --- a/examples/process_monitor/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(process_monitor) - -add_executable( - process_monitor - src/main.cpp - src/mock_process_monitor.cpp -) - -target_link_libraries(process_monitor concurrencpp) diff --git a/examples/process_monitor/src/main.cpp b/examples/process_monitor/src/main.cpp deleted file mode 100644 index cb34745f..00000000 --- a/examples/process_monitor/src/main.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - In this example, we will use concurrencpp::timer_queue and concurrencpp::timer to periodically monitor - the state of the process, by logging the cpu and memory usage along with the thread count and the kernel-object count. - - Do note that this example uses a mock process monitor. the stats are made up and don't reflect the real state of - this process. this example is good to show how concurrencpp:timer and concurrencpp::timer_queue can be used - to schedule an asynchronous action periodically. -*/ - -#include "mock_process_monitor.h" -#include "concurrencpp.h" - -#include - -using namespace std::chrono_literals; - -class process_stat_printer { - -private: - mock_process_monitor::monitor m_process_monitor; - -public: - void operator()() noexcept { - const auto cpu_usage = m_process_monitor.cpu_usage(); - const auto memory_usage = m_process_monitor.memory_usage(); - const auto thread_count = m_process_monitor.thread_count(); - const auto kernel_object_count = m_process_monitor.kernel_object_count(); - - std::cout << "cpu(%): " << cpu_usage << ", " - << "memory(%): " << memory_usage << ", " - << "thread count: " << thread_count << ", " - << "kernel-object count: " << kernel_object_count - << std::endl; - } -}; - -int main(int, const char**) { - concurrencpp::runtime runtime; - auto timer_queue = runtime.timer_queue(); - auto thread_pool_executor = runtime.thread_pool_executor(); - auto timer = timer_queue->make_timer( - 2500ms, - 2500ms, - thread_pool_executor, - process_stat_printer()); - - std::cout << "press enter to quit" << std::endl; - std::getchar(); - return 0; -} \ No newline at end of file diff --git a/examples/process_monitor/src/mock_process_monitor.cpp b/examples/process_monitor/src/mock_process_monitor.cpp deleted file mode 100644 index b61ba2cc..00000000 --- a/examples/process_monitor/src/mock_process_monitor.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "mock_process_monitor.h" - -#include -#include - -using mock_process_monitor::monitor; - -namespace mock_process_monitor { - size_t random_in_range(size_t min, size_t max) { - static const int dummy = [] { - std::srand(static_cast(std::time(nullptr))); - return 0; - }(); - (void)dummy; - - const auto range = max - min + 1; - return std::rand() % range + min; - } -} - -monitor::monitor() noexcept : - m_last_cpu_usage(5), - m_last_memory_usage(15), - m_last_thread_count(4), - m_last_kernel_object_count(21) {} - -size_t monitor::cpu_usage() noexcept { - size_t max_range, min_range; - - if (m_last_cpu_usage >= 95) { - max_range = 95; - } - else { - max_range = m_last_cpu_usage + 5; - } - - if (m_last_cpu_usage <= 5) { - min_range = 1; - } - else { - min_range = m_last_cpu_usage - 5; - } - - m_last_cpu_usage = random_in_range(min_range, max_range); - return m_last_cpu_usage; -} - -size_t monitor::memory_usage() noexcept { - size_t max_range, min_range; - - if (m_last_memory_usage >= 95) { - max_range = 95; - } - else { - max_range = m_last_memory_usage + 5; - } - - if (m_last_memory_usage <= 5) { - min_range = 1; - } - else { - min_range = m_last_memory_usage - 5; - } - - m_last_memory_usage = random_in_range(min_range, max_range); - return m_last_memory_usage; -} - -size_t monitor::thread_count() noexcept { - const auto max_range = m_last_thread_count + 4; - size_t min_range; - - if (m_last_thread_count <= 5) { - min_range = 5; - } - else { - min_range = m_last_thread_count - 2; - } - - m_last_thread_count = random_in_range(min_range, max_range); - return m_last_thread_count; -} - -size_t monitor::kernel_object_count() noexcept { - const auto max_range = m_last_thread_count + 20; - const auto min_range = m_last_thread_count + 3; - - m_last_kernel_object_count = random_in_range(min_range, max_range); - return m_last_kernel_object_count; -} \ No newline at end of file diff --git a/examples/process_monitor/src/mock_process_monitor.h b/examples/process_monitor/src/mock_process_monitor.h deleted file mode 100644 index 3f9af888..00000000 --- a/examples/process_monitor/src/mock_process_monitor.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MOCK_PROCESS_MONITOR_H -#define MOCK_PROCESS_MONITOR_H - -#include - -namespace mock_process_monitor { - class monitor { - - private: - size_t m_last_cpu_usage; - size_t m_last_memory_usage; - size_t m_last_thread_count; - size_t m_last_kernel_object_count; - - public: - monitor() noexcept; - - size_t cpu_usage() noexcept; - size_t memory_usage() noexcept; - size_t thread_count() noexcept; - size_t kernel_object_count() noexcept; - }; -} - -#endif \ No newline at end of file diff --git a/examples/synchronous_web_socket/CMakeLists.txt b/examples/synchronous_web_socket/CMakeLists.txt deleted file mode 100644 index 39e3934a..00000000 --- a/examples/synchronous_web_socket/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(synchronous_web_socket) - -add_executable( - synchronous_web_socket - src/main.cpp - src/mock_web_socket.cpp -) - -target_link_libraries(synchronous_web_socket concurrencpp) diff --git a/examples/synchronous_web_socket/src/main.cpp b/examples/synchronous_web_socket/src/main.cpp deleted file mode 100644 index 7d1d1a10..00000000 --- a/examples/synchronous_web_socket/src/main.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - A company develops a desktop application for weather updates and reports. - The software uses a synchronous web-socket library to communicate with the weather server. - - Since connecting and reading from a websocket may block for a long time (up to hours and days, if no update is available), - and in an un-predictable manner, neither the background_executor or a concurrencpp::timer is suitable for - the task. - - We will use the concurrencpp::thread_executor to create a dedicated thread that runs a work-loop of receiving - weather reports from the server and showing them to the end-user. this way, this long-running, blocking task can - be executed asynchronously without blocking or interfering other tasks or threads. - - There are merits in using concurrencpp::thread_executor over a raw std:thread : - 1) std::thread is a hard-coded executor. since all concurrencpp executors are polymorphic, - executing tasks is decoupled away. if we want to change std::thread to let's say, a threadpool, - we will have to rewrite these lines of code dealing with thread creation, task scheduling etc. - with concurrencpp::thread_executor, we just have to change the underlying type of the executor, - and let the dynamic dispatching do the work for us. - 2) std::thread has to be manually joined or detached. every std::thread_executor is stored and monitored - inside the concurrencpp runtime, and is joined automatically when the runtime object is destroyed. - - Do note that this example uses a mock websocket library and no real connection/reading is going to be executed. - The constants are made up and results are hard-coded. - This example is only good for showing how to use a potentially long running/blocking tasks with a dedicated thread. - - Library's API: - - mock_web_socket::web_socket { - void open(std::string_view server_url); //blocking - std::string receive_msg(); //blocking - } - -*/ - -#include "concurrencpp.h" -#include "mock_web_socket.h" - -#include - -class weather_reporter { - -private: - std::atomic_bool m_cancelled; - const std::string m_endpoint_url; - mock_web_socket::web_socket m_web_socket; - - void work_loop() { - std::cout << "weather_reporter::work_loop started running on thread " << std::this_thread::get_id() << std::endl; - - try - { - m_web_socket.open(m_endpoint_url); - } - catch (const std::exception& e) - { - std::cerr << "can't connect to the server " << e.what() << std::endl; - return; - } - - std::cout << "websocket connected successfully to the weather server, waiting for updates." << std::endl; - - while (!m_cancelled.load()) { - const auto msg = m_web_socket.receive_msg(); - std::cout << msg << std::endl; - } - } - -public: - weather_reporter(std::string_view endpoint_url) : - m_cancelled(false), - m_endpoint_url(endpoint_url) {} - - ~weather_reporter() noexcept { - m_cancelled.store(true); - } - - void launch() { work_loop(); } -}; - -int main() { - concurrencpp::runtime runtime; - auto reporter = std::make_shared("wss://www.example.com/weather-server"); - runtime.thread_executor()->post([reporter]() mutable { - reporter->launch(); - }); - - std::cout << "weather reporter is on and running asynchronously. press any key to exit." << std::endl; - - //do other things in the main thread or in other threads without having the websocket blocking on reads. - - std::getchar(); - return 0; -} \ No newline at end of file diff --git a/examples/synchronous_web_socket/src/mock_web_socket.cpp b/examples/synchronous_web_socket/src/mock_web_socket.cpp deleted file mode 100644 index eebc2c3e..00000000 --- a/examples/synchronous_web_socket/src/mock_web_socket.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "mock_web_socket.h" - -#include -#include -#include - -#include -#include - -namespace mock_web_socket { - bool failed() noexcept { - static const int dummy = [] { - std::srand(static_cast(std::time(nullptr))); - return 0; - }(); - (void)dummy; - - const auto randomized_num = std::rand() % 100; - return randomized_num <= 5; - } - - size_t random_in_range(size_t min, size_t max) { - const auto range = max - min + 1; - return std::rand() % range + min; - } - - const std::string cities[] = { - "London", - "New York City", - "Tokyo", - "Paris", - "Singapore", - "Amsterdam", - "Seoul", - "Berlin", - "Hong Kong", - "Sydney" - }; -} - -void mock_web_socket::web_socket::open(std::string_view) { - if (failed()) { - throw std::runtime_error("web_socket::open - can't connect."); - } - - std::this_thread::sleep_for(std::chrono::seconds(2)); -} - -std::string mock_web_socket::web_socket::receive_msg() { - const auto time_to_sleep = random_in_range(0, 4); - std::this_thread::sleep_for(std::chrono::seconds(time_to_sleep)); - - const auto random_city = cities[random_in_range(0, std::size(cities) - 1)]; - const auto random_temperature = random_in_range(-5, 37); - - return std::string("Current temperature in ") + random_city + " is " + std::to_string(random_temperature) + " deg. C"; -} \ No newline at end of file diff --git a/examples/synchronous_web_socket/src/mock_web_socket.h b/examples/synchronous_web_socket/src/mock_web_socket.h deleted file mode 100644 index c2fd4192..00000000 --- a/examples/synchronous_web_socket/src/mock_web_socket.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef MOCK_WEB_SOCKET_H -#define MOCK_WEB_SOCKET_H - -#include - -namespace mock_web_socket { - struct web_socket { - void open(std::string_view url); - std::string receive_msg(); - }; -} - - -#endif \ No newline at end of file diff --git a/include/concurrencpp/concurrencpp.h b/include/concurrencpp/concurrencpp.h new file mode 100644 index 00000000..8fee8d01 --- /dev/null +++ b/include/concurrencpp/concurrencpp.h @@ -0,0 +1,15 @@ +#ifndef CONCURRENCPP_H +#define CONCURRENCPP_H + +#include "concurrencpp/forward_declerations.h" +#include "concurrencpp/platform_defs.h" + +#include "concurrencpp/timers/timer.h" +#include "concurrencpp/timers/timer_queue.h" +#include "concurrencpp/runtime/runtime.h" +#include "concurrencpp/results/result.h" +#include "concurrencpp/results/make_result.h" +#include "concurrencpp/results/when_result.h" +#include "concurrencpp/executors/executor_all.h" + +#endif diff --git a/include/concurrencpp/errors.h b/include/concurrencpp/errors.h new file mode 100644 index 00000000..3d64b145 --- /dev/null +++ b/include/concurrencpp/errors.h @@ -0,0 +1,45 @@ +#ifndef CONCURRENCPP_ERRORS_H +#define CONCURRENCPP_ERRORS_H + +#include +#include + +namespace concurrencpp::errors { + struct empty_object : public std::runtime_error { + empty_object(const std::string& message) : runtime_error(message) {} + }; + + struct empty_result : public empty_object { + empty_result(const std::string& message) : empty_object(message) {} + }; + + struct empty_result_promise : public empty_object { + empty_result_promise(const std::string& message) : empty_object(message) {} + }; + + struct empty_awaitable : public empty_object { + empty_awaitable(const std::string& message) : empty_object(message) {} + }; + + struct empty_timer : public empty_object { + empty_timer(const std::string& error_messgae) : empty_object(error_messgae) {} + }; + + struct broken_task : public std::runtime_error { + broken_task(const std::string& message) : runtime_error(message) {} + }; + + struct result_already_retrieved : public std::runtime_error { + result_already_retrieved(const std::string& message) : runtime_error(message) {} + }; + + struct executor_shutdown : public std::runtime_error { + executor_shutdown(const std::string& message) : runtime_error(message) {} + }; + + struct timer_queue_shutdown : public std::runtime_error { + timer_queue_shutdown(const std::string& message) : runtime_error(message) {} + }; +} // namespace concurrencpp::errors + +#endif // ERRORS_H diff --git a/include/concurrencpp/executors/constants.h b/include/concurrencpp/executors/constants.h new file mode 100644 index 00000000..74053cfa --- /dev/null +++ b/include/concurrencpp/executors/constants.h @@ -0,0 +1,23 @@ +#ifndef CONCURRENCPP_EXECUTORS_CONSTS_H +#define CONCURRENCPP_EXECUTORS_CONSTS_H + +namespace concurrencpp::details::consts { + inline const char* k_inline_executor_name = "concurrencpp::inline_executor"; + constexpr int k_inline_executor_max_concurrency_level = 0; + + inline const char* k_thread_executor_name = "concurrencpp::thread_executor"; + constexpr int k_thread_executor_max_concurrency_level = std::numeric_limits::max(); + + inline const char* k_thread_pool_executor_name = "concurrencpp::thread_pool_executor"; + inline const char* k_background_executor_name = "concurrencpp::background_executor"; + + constexpr int k_worker_thread_max_concurrency_level = 1; + inline const char* k_worker_thread_executor_name = "concurrencpp::worker_thread_executor"; + + inline const char* k_manual_executor_name = "concurrencpp::manual_executor"; + constexpr int k_manual_executor_max_concurrency_level = std::numeric_limits::max(); + + inline const char* k_executor_shutdown_err_msg = " - shutdown has been called on this executor."; +} // namespace concurrencpp::details::consts + +#endif diff --git a/include/concurrencpp/executors/derivable_executor.h b/include/concurrencpp/executors/derivable_executor.h new file mode 100644 index 00000000..85b9ebbd --- /dev/null +++ b/include/concurrencpp/executors/derivable_executor.h @@ -0,0 +1,40 @@ +#ifndef CONCURRENCPP_DERIVABLE_EXECUTOR_H +#define CONCURRENCPP_DERIVABLE_EXECUTOR_H + +#include "concurrencpp/executors/executor.h" + +namespace concurrencpp { + template + class derivable_executor : public executor { + + private: + concrete_executor_type* self() noexcept { + return static_cast(this); + } + + public: + derivable_executor(std::string_view name) : executor(name) {} + + template + void post(callable_type&& callable, argument_types&&... arguments) { + return do_post(self(), std::forward(callable), std::forward(arguments)...); + } + + template + auto submit(callable_type&& callable, argument_types&&... arguments) { + return do_submit(self(), std::forward(callable), std::forward(arguments)...); + } + + template + void bulk_post(std::span callable_list) { + return do_bulk_post(self(), callable_list); + } + + template> + std::vector> bulk_submit(std::span callable_list) { + return do_bulk_submit(self(), callable_list); + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/executor.h b/include/concurrencpp/executors/executor.h new file mode 100644 index 00000000..a07c76d2 --- /dev/null +++ b/include/concurrencpp/executors/executor.h @@ -0,0 +1,129 @@ +#ifndef CONCURRENCPP_EXECUTOR_H +#define CONCURRENCPP_EXECUTOR_H + +#include "concurrencpp/results/result.h" + +#include +#include +#include + +namespace concurrencpp::details { + [[noreturn]] void throw_executor_shutdown_exception(std::string_view executor_name); + std::string make_executor_worker_name(std::string_view executor_name); +} // namespace concurrencpp::details + +namespace concurrencpp { + class executor { + + private: + template + static null_result post_bridge(executor_tag, executor_type*, callable_type callable, argument_types... arguments) { + callable(arguments...); + co_return; + } + + template + static null_result bulk_post_bridge(details::executor_bulk_tag, std::vector>* accumulator, callable_type callable) { + callable(); + co_return; + } + + template + static result submit_bridge(executor_tag, executor_type*, callable_type callable, argument_types... arguments) { + co_return callable(arguments...); + } + + template> + static result bulk_submit_bridge(details::executor_bulk_tag, + std::vector>* accumulator, + callable_type callable) { + co_return callable(); + } + + protected: + template + static void do_post(executor_type* executor_ptr, callable_type&& callable, argument_types&&... arguments) { + static_assert(std::is_invocable_v, + "concurrencpp::executor::post - <> is not invokable with <>"); + + post_bridge({}, executor_ptr, std::forward(callable), std::forward(arguments)...); + } + + template + static auto do_submit(executor_type* executor_ptr, callable_type&& callable, argument_types&&... arguments) { + static_assert(std::is_invocable_v, + "concurrencpp::executor::submit - <> is not invokable with <>"); + + using return_type = typename std::invoke_result_t; + + return submit_bridge({}, executor_ptr, std::forward(callable), std::forward(arguments)...); + } + + template + static void do_bulk_post(executor_type* executor_ptr, std::span callable_list) { + std::vector> accumulator; + accumulator.reserve(callable_list.size()); + + for (auto& callable : callable_list) { + bulk_post_bridge({}, &accumulator, std::move(callable)); + } + + assert(!accumulator.empty()); + executor_ptr->enqueue(accumulator); + } + + template> + static std::vector> do_bulk_submit(executor_type* executor_ptr, std::span callable_list) { + std::vector> accumulator; + accumulator.reserve(callable_list.size()); + + std::vector> results; + results.reserve(callable_list.size()); + + for (auto& callable : callable_list) { + results.emplace_back(bulk_submit_bridge({}, &accumulator, std::move(callable))); + } + + assert(!accumulator.empty()); + executor_ptr->enqueue(accumulator); + return results; + } + + public: + executor(std::string_view name) : name(name) {} + + virtual ~executor() noexcept = default; + + const std::string name; + + virtual void enqueue(std::experimental::coroutine_handle<> task) = 0; + virtual void enqueue(std::span> tasks) = 0; + + virtual int max_concurrency_level() const noexcept = 0; + + virtual bool shutdown_requested() const noexcept = 0; + virtual void shutdown() noexcept = 0; + + template + void post(callable_type&& callable, argument_types&&... arguments) { + return do_post(this, std::forward(callable), std::forward(arguments)...); + } + + template + auto submit(callable_type&& callable, argument_types&&... arguments) { + return do_submit(this, std::forward(callable), std::forward(arguments)...); + } + + template + void bulk_post(std::span callable_list) { + return do_bulk_post(this, callable_list); + } + + template> + std::vector> bulk_submit(std::span callable_list) { + return do_bulk_submit(this, callable_list); + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/executor_all.h b/include/concurrencpp/executors/executor_all.h new file mode 100644 index 00000000..30dc5d1b --- /dev/null +++ b/include/concurrencpp/executors/executor_all.h @@ -0,0 +1,11 @@ +#ifndef CONCURRENCPP_EXECUTORS_ALL_H +#define CONCURRENCPP_EXECUTORS_ALL_H + +#include "concurrencpp/executors/derivable_executor.h" +#include "concurrencpp/executors/inline_executor.h" +#include "concurrencpp/executors/thread_pool_executor.h" +#include "concurrencpp/executors/thread_executor.h" +#include "concurrencpp/executors/worker_thread_executor.h" +#include "concurrencpp/executors/manual_executor.h" + +#endif diff --git a/include/concurrencpp/executors/inline_executor.h b/include/concurrencpp/executors/inline_executor.h new file mode 100644 index 00000000..4a70b2a3 --- /dev/null +++ b/include/concurrencpp/executors/inline_executor.h @@ -0,0 +1,51 @@ +#ifndef CONCURRENCPP_INLINE_EXECUTOR_H +#define CONCURRENCPP_INLINE_EXECUTOR_H + +#include "concurrencpp/executors/executor.h" +#include "concurrencpp/executors/constants.h" + +namespace concurrencpp { + class inline_executor final : public executor { + + private: + std::atomic_bool m_abort; + + void throw_if_aborted() const { + if (!m_abort.load(std::memory_order_relaxed)) { + return; + } + + details::throw_executor_shutdown_exception(name); + } + + public: + inline_executor() noexcept : executor(details::consts::k_inline_executor_name), m_abort(false) {} + + void enqueue(std::experimental::coroutine_handle<> task) override { + throw_if_aborted(); + task(); + } + + void enqueue(std::span> tasks) override { + throw_if_aborted(); + + for (auto& task : tasks) { + task(); + } + } + + int max_concurrency_level() const noexcept override { + return details::consts::k_inline_executor_max_concurrency_level; + } + + void shutdown() noexcept override { + m_abort.store(true, std::memory_order_relaxed); + } + + bool shutdown_requested() const noexcept override { + return m_abort.load(std::memory_order_relaxed); + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/manual_executor.h b/include/concurrencpp/executors/manual_executor.h new file mode 100644 index 00000000..894876b6 --- /dev/null +++ b/include/concurrencpp/executors/manual_executor.h @@ -0,0 +1,47 @@ +#ifndef CONCURRENCPP_MANUAL_EXECUTOR_H +#define CONCURRENCPP_MANUAL_EXECUTOR_H + +#include "concurrencpp/executors/derivable_executor.h" +#include "concurrencpp/executors/constants.h" + +#include + +namespace concurrencpp { + class alignas(64) manual_executor final : public derivable_executor { + + private: + mutable std::mutex m_lock; + std::deque> m_tasks; + std::condition_variable m_condition; + bool m_abort; + std::atomic_bool m_atomic_abort; + + void destroy_tasks(std::unique_lock& lock) noexcept; + + public: + manual_executor(); + + void enqueue(std::experimental::coroutine_handle<> task) override; + void enqueue(std::span> tasks) override; + + int max_concurrency_level() const noexcept override; + + void shutdown() noexcept override; + bool shutdown_requested() const noexcept override; + + size_t size() const noexcept; + bool empty() const noexcept; + + bool loop_once(); + bool loop_once(std::chrono::milliseconds max_waiting_time); + + size_t loop(size_t max_count); + + size_t clear() noexcept; + + void wait_for_task(); + bool wait_for_task(std::chrono::milliseconds max_waiting_time); + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/thread_executor.h b/include/concurrencpp/executors/thread_executor.h new file mode 100644 index 00000000..25185afa --- /dev/null +++ b/include/concurrencpp/executors/thread_executor.h @@ -0,0 +1,63 @@ +#ifndef CONCURRENCPP_THREAD_EXECUTOR_H +#define CONCURRENCPP_THREAD_EXECUTOR_H + +#include "concurrencpp/executors/derivable_executor.h" +#include "concurrencpp/executors/constants.h" + +#include "concurrencpp/threads/thread.h" + +#include +#include +#include +#include +#include + +namespace concurrencpp::details { + class thread_worker { + + private: + thread m_thread; + thread_executor& m_parent_pool; + + void execute_and_retire(std::experimental::coroutine_handle<> task, typename std::list::iterator self_it); + + public: + thread_worker(thread_executor& parent_pool) noexcept; + ~thread_worker() noexcept; + + void start(const std::string worker_name, std::experimental::coroutine_handle<> task, std::list::iterator self_it); + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + class alignas(64) thread_executor final : public derivable_executor { + + friend class ::concurrencpp::details::thread_worker; + + private: + std::mutex m_lock; + std::list m_workers; + std::condition_variable m_condition; + std::list m_last_retired; + bool m_abort; + std::atomic_bool m_atomic_abort; + + void enqueue_impl(std::experimental::coroutine_handle<> task); + + void retire_worker(std::list::iterator it); + + public: + thread_executor(); + ~thread_executor() noexcept; + + void enqueue(std::experimental::coroutine_handle<> task) override; + void enqueue(std::span> tasks) override; + + int max_concurrency_level() const noexcept override; + + bool shutdown_requested() const noexcept override; + void shutdown() noexcept override; + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/thread_pool_executor.h b/include/concurrencpp/executors/thread_pool_executor.h new file mode 100644 index 00000000..ce0bd8d4 --- /dev/null +++ b/include/concurrencpp/executors/thread_pool_executor.h @@ -0,0 +1,135 @@ +#ifndef CONCURRENCPP_THREAD_POOL_EXECUTOR_H +#define CONCURRENCPP_THREAD_POOL_EXECUTOR_H + +#include "concurrencpp/executors/derivable_executor.h" + +#include +#include +#include +#include +#include +#include + +#include "../threads/thread.h" + +namespace concurrencpp::details { + class idle_worker_set; + class thread_pool_worker; +} // namespace concurrencpp::details + +namespace concurrencpp::details { + class idle_worker_set { + + enum class status { active, idle }; + + struct alignas(64) padded_flag { + std::atomic flag; + const char padding[64 - sizeof(flag)] = {}; + }; + + private: + std::atomic_intptr_t m_approx_size; + const std::unique_ptr m_idle_flags; + const size_t m_size; + + bool try_acquire_flag(size_t index) noexcept; + + public: + idle_worker_set(size_t size); + + void set_idle(size_t idle_thread) noexcept; + void set_active(size_t idle_thread) noexcept; + + size_t find_idle_worker(size_t caller_index) noexcept; + void find_idle_workers(size_t caller_index, std::vector& result_buffer, size_t max_count) noexcept; + }; +} // namespace concurrencpp::details + +namespace concurrencpp::details { + class alignas(64) thread_pool_worker { + + enum class status { working, waiting, idle }; + + private: + std::deque> m_private_queue; + std::vector m_idle_worker_list; + std::atomic_bool m_atomic_abort; + thread_pool_executor& m_parent_pool; + const size_t m_index; + const size_t m_pool_size; + const std::chrono::seconds m_max_idle_time; + const std::string m_worker_name; + const char m_padding[64] = {}; + std::mutex m_lock; + status m_status; + std::deque> m_public_queue; + thread m_thread; + std::condition_variable m_condition; + bool m_abort; + + void balance_work(); + + bool wait_for_task(std::unique_lock& lock) noexcept; + bool drain_queue_impl(); + bool drain_queue(); + + void work_loop() noexcept; + + void destroy_tasks() noexcept; + void ensure_worker_active(std::unique_lock& lock); + + public: + thread_pool_worker(thread_pool_executor& parent_pool, size_t index, size_t pool_size, std::chrono::seconds max_idle_time); + + thread_pool_worker(thread_pool_worker&& rhs) noexcept; + ~thread_pool_worker() noexcept; + + void enqueue_foreign(std::experimental::coroutine_handle<> task); + void enqueue_foreign(std::span> tasks); + + void enqueue_local(std::experimental::coroutine_handle<> task); + void enqueue_local(std::span> tasks); + + void abort() noexcept; + void join() noexcept; + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + class alignas(64) thread_pool_executor final : public derivable_executor { + + friend class details::thread_pool_worker; + + private: + std::vector m_workers; + const char m_padding_0[64 - sizeof(m_workers)] = {}; + std::atomic_size_t m_round_robin_cursor; + const char m_padding_1[64 - sizeof(m_round_robin_cursor)] = {}; + details::idle_worker_set m_idle_workers; + const char m_padding_2[64 - sizeof(m_idle_workers)] = {}; + std::atomic_bool m_abort; + + void mark_worker_idle(size_t index) noexcept; + void mark_worker_active(size_t index) noexcept; + + void find_idle_workers(size_t caller_index, std::vector& buffer, size_t max_count) noexcept; + + details::thread_pool_worker& worker_at(size_t index) noexcept { + return m_workers[index]; + } + + public: + thread_pool_executor(std::string_view name, size_t size, std::chrono::seconds max_idle_time); + ~thread_pool_executor() noexcept; + + void enqueue(std::experimental::coroutine_handle<> task) override; + void enqueue(std::span> tasks) override; + + int max_concurrency_level() const noexcept override; + + bool shutdown_requested() const noexcept override; + void shutdown() noexcept override; + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/executors/worker_thread_executor.h b/include/concurrencpp/executors/worker_thread_executor.h new file mode 100644 index 00000000..c96b4512 --- /dev/null +++ b/include/concurrencpp/executors/worker_thread_executor.h @@ -0,0 +1,50 @@ +#ifndef CONCURRENCPP_WORKER_THREAD_EXECUTOR_H +#define CONCURRENCPP_WORKER_THREAD_EXECUTOR_H + +#include "concurrencpp/executors/derivable_executor.h" + +#include "concurrencpp/threads/thread.h" + +#include + +namespace concurrencpp { + class alignas(64) worker_thread_executor final : public derivable_executor { + + private: + std::deque> m_private_queue; + std::atomic_bool m_private_atomic_abort; + details::thread m_thread; + const char m_padding[64] = {}; + std::mutex m_lock; + std::deque> m_public_queue; + std::condition_variable m_condition; + bool m_abort; + std::atomic_bool m_atomic_abort; + + void destroy_tasks() noexcept; + + bool drain_queue_impl(); + bool drain_queue(); + void work_loop() noexcept; + + void enqueue_local(std::experimental::coroutine_handle<> task); + void enqueue_local(std::span> task); + + void enqueue_foreign(std::experimental::coroutine_handle<> task); + void enqueue_foreign(std::span> task); + + public: + worker_thread_executor(); + ~worker_thread_executor() noexcept; + + void enqueue(std::experimental::coroutine_handle<> task) override; + void enqueue(std::span> tasks) override; + + int max_concurrency_level() const noexcept override; + + bool shutdown_requested() const noexcept override; + void shutdown() noexcept override; + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/forward_declerations.h b/include/concurrencpp/forward_declerations.h new file mode 100644 index 00000000..b4a5c453 --- /dev/null +++ b/include/concurrencpp/forward_declerations.h @@ -0,0 +1,25 @@ +#ifndef CONCURRENCPP_FORWARD_DECLERATIONS_H +#define CONCURRENCPP_FORWARD_DECLERATIONS_H + +namespace concurrencpp { + struct null_result; + + template + class result; + template + class result_promise; + + class runtime; + + class timer_queue; + class timer; + + class executor; + class inline_executor; + class thread_pool_executor; + class thread_executor; + class worker_thread_executor; + class manual_executor; +} // namespace concurrencpp + +#endif // FORWARD_DECLERATIONS_H diff --git a/concurrencpp/src/platform_defs.h b/include/concurrencpp/platform_defs.h similarity index 58% rename from concurrencpp/src/platform_defs.h rename to include/concurrencpp/platform_defs.h index c4eae12b..102cee51 100644 --- a/concurrencpp/src/platform_defs.h +++ b/include/concurrencpp/platform_defs.h @@ -2,28 +2,27 @@ #define CONCURRENCPP_PLATFORM_DEFS_H #if defined(_WIN32) -#define CRCPP_WIN_OS +# define CRCPP_WIN_OS #elif defined(unix) || defined(__unix__) || defined(__unix) -#define CRCPP_UNIX_OS +# define CRCPP_UNIX_OS #elif defined(__APPLE__) || defined(__MACH__) -#define CRCPP_MACH_OS +# define CRCPP_MACH_OS #elif defined(__FreeBSD__) -#define CRCPP_FREE_BSD_OS +# define CRCPP_FREE_BSD_OS #elif defined(__ANDROID__) -#define CRCPP_ANDROID_OS +# define CRCPP_ANDROID_OS #endif #if defined(__clang__) -#define CRCPP_CLANG_COMPILER +# define CRCPP_CLANG_COMPILER #elif defined(__GNUC__) || defined(__GNUG__) -#define CRCPP_GCC_COMPILER +# define CRCPP_GCC_COMPILER #elif defined(_MSC_VER) -#define CRCPP_MSVC_COMPILER +# define CRCPP_MSVC_COMPILER #endif - #if !defined(NDEBUG) || defined(_DEBUG) -#define CRCPP_DEBUG_MODE +# define CRCPP_DEBUG_MODE #endif -#endif //PLATFORM_DEFS_H +#endif // PLATFORM_DEFS_H diff --git a/include/concurrencpp/results/constants.h b/include/concurrencpp/results/constants.h new file mode 100644 index 00000000..0bc77c41 --- /dev/null +++ b/include/concurrencpp/results/constants.h @@ -0,0 +1,54 @@ +#ifndef CONCURRENCPP_RESULT_CONSTS_H +#define CONCURRENCPP_RESULT_CONSTS_H + +namespace concurrencpp::details::consts { + inline const char* k_result_promise_set_result_error_msg = "result_promise::set_result() - empty result_promise."; + + inline const char* k_result_promise_set_exception_error_msg = "result_promise::set_exception() - empty result_promise."; + + inline const char* k_result_promise_set_exception_null_exception_error_msg = "result_promise::set_exception() - exception pointer is null."; + + inline const char* k_result_promise_set_from_function_error_msg = "result_promise::set_from_function() - empty result_promise."; + + inline const char* k_result_promise_get_result_error_msg = "result_promise::get_result() - empty result_promise."; + + inline const char* k_result_promise_get_result_already_retrieved_error_msg = "result_promise::get_result() - result was already retrieved."; + + inline const char* k_result_status_error_msg = "result::status() - result is empty."; + + inline const char* k_result_get_error_msg = "result::get() - result is empty."; + + inline const char* k_result_wait_error_msg = "result::wait() - result is empty."; + + inline const char* k_result_wait_for_error_msg = "result::wait_for() - result is empty."; + + inline const char* k_result_wait_until_error_msg = "result::wait_until() - result is empty."; + + inline const char* k_result_operator_co_await_error_msg = "result::operator co_await() - result is empty."; + + inline const char* k_result_await_via_error_msg = "result::await_via() - result is empty."; + + inline const char* k_result_await_via_executor_null_error_msg = "result::await_via() - given executor is null."; + + inline const char* k_result_resolve_error_msg = "result::resolve() - result is empty."; + + inline const char* k_result_resolve_via_error_msg = "result::resolve_via() - result is empty."; + + inline const char* k_result_resolve_via_executor_null_error_msg = "result::resolve_via() - given executor is null."; + + inline const char* k_result_awaitable_error_msg = "concurrencpp::awaitable_type::await_suspend() - awaitable is empty."; + + inline const char* k_result_resolver_error_msg = "result_resolver::await_suspend() - awaitable is empty."; + + inline const char* k_executor_exception_error_msg = "concurrencpp::result - an exception was thrown while trying to enqueue result continuation."; + + inline const char* k_make_exceptional_result_exception_null_error_msg = "make_exception_result() - given exception_ptr is null."; + + inline const char* k_when_all_empty_result_error_msg = "concurrencpp::when_all() - one of the result objects is empty."; + + inline const char* k_when_any_empty_result_error_msg = "concurrencpp::when_any() - one of the result objects is empty."; + + inline const char* k_when_any_empty_range_error_msg = "concurrencpp::when_any() - given range contains no elements."; +} // namespace concurrencpp::details::consts + +#endif diff --git a/include/concurrencpp/results/executor_exception.h b/include/concurrencpp/results/executor_exception.h new file mode 100644 index 00000000..9898e579 --- /dev/null +++ b/include/concurrencpp/results/executor_exception.h @@ -0,0 +1,24 @@ +#ifndef CONCURRENCPP_EXECUTOR_ERROR_H +#define CONCURRENCPP_EXECUTOR_ERROR_H + +#include "constants.h" + +#include "concurrencpp/forward_declerations.h" + +#include +#include + +namespace concurrencpp::errors { + struct executor_exception final : public std::runtime_error { + std::exception_ptr thrown_exception; + std::shared_ptr throwing_executor; + + executor_exception(std::exception_ptr thrown_exception, std::shared_ptr throwing_executor) noexcept : + runtime_error(details::consts::k_executor_exception_error_msg), thrown_exception(thrown_exception), throwing_executor(throwing_executor) {} + + executor_exception(const executor_exception&) noexcept = default; + executor_exception(executor_exception&&) noexcept = default; + }; +} // namespace concurrencpp::errors + +#endif diff --git a/include/concurrencpp/results/make_result.h b/include/concurrencpp/results/make_result.h new file mode 100644 index 00000000..6e983e1f --- /dev/null +++ b/include/concurrencpp/results/make_result.h @@ -0,0 +1,43 @@ +#ifndef CONCURRENCPP_MAKE_RESULT_H +#define CONCURRENCPP_MAKE_RESULT_H + +#include "concurrencpp/results/result.h" + +namespace concurrencpp { + template + result make_ready_result(argument_types&&... arguments) { + static_assert(std::is_constructible_v || std::is_same_v, + "concurrencpp::make_ready_result - <> is not constructible from <"); + + auto result_core_ptr = std::make_shared>(); + result_core_ptr->set_result(std::forward(arguments)...); + result_core_ptr->publish_result(); + return {std::move(result_core_ptr)}; + } + + inline result make_ready_result() { + auto result_core_ptr = std::make_shared>(); + result_core_ptr->set_result(); + result_core_ptr->publish_result(); + return {std::move(result_core_ptr)}; + } + + template + result make_exceptional_result(std::exception_ptr exception_ptr) { + if (!static_cast(exception_ptr)) { + throw std::invalid_argument(details::consts::k_make_exceptional_result_exception_null_error_msg); + } + + auto result_core_ptr = std::make_shared>(); + result_core_ptr->set_exception(exception_ptr); + result_core_ptr->publish_result(); + return {std::move(result_core_ptr)}; + } + + template + result make_exceptional_result(exception_type exception) { + return make_exceptional_result(std::make_exception_ptr(exception)); + } +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/results/promises.h b/include/concurrencpp/results/promises.h new file mode 100644 index 00000000..24c0f272 --- /dev/null +++ b/include/concurrencpp/results/promises.h @@ -0,0 +1,263 @@ +#ifndef CONCURRENCPP_PROMISES_H +#define CONCURRENCPP_PROMISES_H + +#include "concurrencpp/results/result_core.h" + +#include "concurrencpp/errors.h" + +#include + +namespace concurrencpp::details { + struct coroutine_per_thread_data { + executor* executor = nullptr; + std::vector>* accumulator = nullptr; + + static thread_local coroutine_per_thread_data s_tl_per_thread_data; + }; + + template + struct initial_scheduling_awaiter : public std::experimental::suspend_always { + void await_suspend(std::experimental::coroutine_handle<> handle) const { + auto& per_thread_data = coroutine_per_thread_data::s_tl_per_thread_data; + auto executor_base_ptr = std::exchange(per_thread_data.executor, nullptr); + + assert(executor_base_ptr != nullptr); + assert(dynamic_cast(executor_base_ptr) != nullptr); + + auto executor_ptr = static_cast(executor_base_ptr); + executor_ptr->enqueue(handle); + } + }; + + template<> + struct initial_scheduling_awaiter : public std::experimental::suspend_never {}; + + struct initial_accumulating_awaiter : public std::experimental::suspend_always { + void await_suspend(std::experimental::coroutine_handle<> handle) const noexcept; + }; + + template + struct initialy_rescheduled_promise { + + static_assert(std::is_base_of_v, + "concurrencpp::initialy_rescheduled_promise<> - <> isn't driven from concurrencpp::executor."); + + template + static void* operator new(size_t size, argument_types&&...) { + return ::operator new(size); + } + + template + static void* operator new(size_t size, executor_tag, executor_type* executor_ptr, argument_types&&...) { + assert(executor_ptr != nullptr); + assert(coroutine_per_thread_data::s_tl_per_thread_data.executor == nullptr); + coroutine_per_thread_data::s_tl_per_thread_data.executor = executor_ptr; + + return ::operator new(size); + } + + template + static void* operator new(size_t size, executor_tag, std::shared_ptr executor, argument_types&&... args) { + return operator new (size, executor_tag {}, executor.get(), std::forward(args)...); + } + + template + static void* operator new(size_t size, executor_tag, executor_type& executor, argument_types&&... args) { + + return operator new (size, executor_tag {}, std::addressof(executor), std::forward(args)...); + } + + initial_scheduling_awaiter initial_suspend() const noexcept { + return {}; + } + }; + + struct initialy_resumed_promise { + std::experimental::suspend_never initial_suspend() const noexcept { + return {}; + } + }; + + struct bulk_promise { + template + static void* operator new(size_t size, argument_types&&...) { + return ::operator new(size); + } + + template + static void* operator new(size_t size, executor_bulk_tag, std::vector>* accumulator, argument_types&&...) { + + assert(accumulator != nullptr); + assert(coroutine_per_thread_data::s_tl_per_thread_data.accumulator == nullptr); + coroutine_per_thread_data::s_tl_per_thread_data.accumulator = accumulator; + + return ::operator new(size); + } + + initial_accumulating_awaiter initial_suspend() const noexcept { + return {}; + } + }; + + struct null_result_promise { + null_result get_return_object() const noexcept { + return {}; + } + + std::experimental::suspend_never final_suspend() const noexcept { + return {}; + } + + void unhandled_exception() const noexcept {} + void return_void() const noexcept {} + }; + + template + struct return_value_struct { + template + void return_value(return_type&& value) { + auto self = static_cast(this); + self->set_result(std::forward(value)); + } + }; + + template + struct return_value_struct { + void return_void() noexcept { + auto self = static_cast(this); + self->set_result(); + } + }; + + struct result_publisher : public std::experimental::suspend_always { + template + bool await_suspend(std::experimental::coroutine_handle handle) const noexcept { + handle.promise().publish_result(); + return false; // don't suspend, resume and destroy this + } + }; + + template + struct result_coro_promise : public return_value_struct, type> { + + private: + std::shared_ptr> m_result_ptr; + + public: + result_coro_promise() : m_result_ptr(std::make_shared>()) {} + + ~result_coro_promise() noexcept { + if (!static_cast(this->m_result_ptr)) { + return; + } + + auto broken_task_error = std::make_exception_ptr(concurrencpp::errors::broken_task("coroutine was destroyed before finishing execution")); + this->m_result_ptr->set_exception(broken_task_error); + this->m_result_ptr->publish_result(); + } + + template + void set_result(argument_types&&... args) { + this->m_result_ptr->set_result(std::forward(args)...); + } + + void unhandled_exception() noexcept { + this->m_result_ptr->set_exception(std::current_exception()); + } + + ::concurrencpp::result get_return_object() noexcept { + return {this->m_result_ptr}; + } + + void publish_result() noexcept { + this->m_result_ptr->publish_result(); + this->m_result_ptr.reset(); + } + + result_publisher final_suspend() const noexcept { + return {}; + } + }; + + struct initialy_resumed_null_result_promise : public initialy_resumed_promise, public null_result_promise {}; + + template + struct initialy_resumed_result_promise : public initialy_resumed_promise, public result_coro_promise {}; + + template + struct initialy_rescheduled_null_result_promise : public initialy_rescheduled_promise, public null_result_promise {}; + + template + struct initialy_rescheduled_result_promise : public initialy_rescheduled_promise, public result_coro_promise {}; + + struct bulk_null_result_promise : public bulk_promise, public null_result_promise {}; + + template + struct bulk_result_promise : public bulk_promise, public result_coro_promise {}; +} // namespace concurrencpp::details + +namespace std::experimental { + // No executor + No result + template + struct coroutine_traits<::concurrencpp::null_result, arguments...> { + using promise_type = concurrencpp::details::initialy_resumed_null_result_promise; + }; + + // No executor + result + template + struct coroutine_traits<::concurrencpp::result, arguments...> { + using promise_type = concurrencpp::details::initialy_resumed_result_promise; + }; + + // Executor + no result + template + struct coroutine_traits, arguments...> { + using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; + }; + + template + struct coroutine_traits { + using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; + }; + + template + struct coroutine_traits { + using promise_type = concurrencpp::details::initialy_rescheduled_null_result_promise; + }; + + // Executor + result + template + struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, std::shared_ptr, arguments...> { + using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; + }; + + template + struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, executor_type*, arguments...> { + using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; + }; + + template + struct coroutine_traits<::concurrencpp::result, concurrencpp::executor_tag, executor_type&, arguments...> { + using promise_type = concurrencpp::details::initialy_rescheduled_result_promise; + }; + + // Bulk + no result + template + struct coroutine_traits<::concurrencpp::null_result, + concurrencpp::details::executor_bulk_tag, + std::vector>*, + arguments...> { + using promise_type = concurrencpp::details::bulk_null_result_promise; + }; + + // Bulk + result + template + struct coroutine_traits<::concurrencpp::result, + concurrencpp::details::executor_bulk_tag, + std::vector>*, + arguments...> { + using promise_type = concurrencpp::details::bulk_result_promise; + }; +} // namespace std::experimental + +#endif diff --git a/include/concurrencpp/results/result.h b/include/concurrencpp/results/result.h new file mode 100644 index 00000000..205a19b8 --- /dev/null +++ b/include/concurrencpp/results/result.h @@ -0,0 +1,222 @@ +#ifndef CONCURRENCPP_RESULT_H +#define CONCURRENCPP_RESULT_H + +#include "concurrencpp/results/constants.h" +#include "concurrencpp/results/promises.h" +#include "concurrencpp/results/result_awaitable.h" + +#include "concurrencpp/errors.h" +#include "concurrencpp/forward_declerations.h" + +#include "concurrencpp/utils/bind.h" + +#include + +namespace concurrencpp { + template + class result { + + static constexpr auto valid_result_type_v = std::is_same_v || std::is_nothrow_move_constructible_v; + + static_assert(valid_result_type_v, "concurrencpp::result - <> should be now-throw-move constructable or void."); + + friend class details::when_result_helper; + + private: + std::shared_ptr> m_state; + + void throw_if_empty(const char* message) const { + if (m_state.get() != nullptr) { + return; + } + + throw errors::empty_result(message); + } + + public: + result() noexcept = default; + ~result() noexcept = default; + result(result&& rhs) noexcept = default; + + result(std::shared_ptr> state) noexcept : m_state(std::move(state)) {} + + result& operator=(result&& rhs) noexcept { + if (this != &rhs) { + m_state = std::move(rhs.m_state); + } + + return *this; + } + + result(const result& rhs) = delete; + result& operator=(const result& rhs) = delete; + + operator bool() const noexcept { + return m_state.get() != nullptr; + } + + result_status status() const { + throw_if_empty(details::consts::k_result_status_error_msg); + return m_state->status(); + } + + void wait() { + throw_if_empty(details::consts::k_result_wait_error_msg); + m_state->wait(); + } + + template + result_status wait_for(std::chrono::duration duration) { + throw_if_empty(details::consts::k_result_wait_for_error_msg); + return m_state->wait_for(duration); + } + + template + result_status wait_until(std::chrono::time_point timeout_time) { + throw_if_empty(details::consts::k_result_wait_until_error_msg); + return m_state->wait_until(timeout_time); + } + + type get() { + throw_if_empty(details::consts::k_result_get_error_msg); + auto state = std::move(m_state); + state->wait(); + return state->get(); + } + + auto operator co_await() { + throw_if_empty(details::consts::k_result_operator_co_await_error_msg); + return awaitable {std::move(m_state)}; + } + + auto await_via(std::shared_ptr executor, bool force_rescheduling = true) { + throw_if_empty(details::consts::k_result_await_via_error_msg); + + if (!static_cast(executor)) { + throw std::invalid_argument(details::consts::k_result_await_via_executor_null_error_msg); + } + + return via_awaitable {std::move(m_state), std::move(executor), force_rescheduling}; + } + + auto resolve() { + throw_if_empty(details::consts::k_result_resolve_error_msg); + return resolve_awaitable {std::move(m_state)}; + } + + auto resolve_via(std::shared_ptr executor, bool force_rescheduling = true) { + throw_if_empty(details::consts::k_result_resolve_via_error_msg); + + if (!static_cast(executor)) { + throw std::invalid_argument(details::consts::k_result_resolve_via_executor_null_error_msg); + } + + return resolve_via_awaitable {std::move(m_state), std::move(executor), force_rescheduling}; + } + }; +} // namespace concurrencpp + +namespace concurrencpp { + template + class result_promise { + + private: + std::shared_ptr> m_state; + bool m_result_retrieved; + + void throw_if_empty(const char* message) const { + if (static_cast(m_state)) { + return; + } + + throw errors::empty_result_promise(message); + } + + void break_task_if_needed() noexcept { + if (!static_cast(m_state)) { + return; + } + + if (!m_result_retrieved) { // no result to break. + return; + } + + auto exception_ptr = std::make_exception_ptr(errors::broken_task("result_promise - broken task.")); + m_state->set_exception(exception_ptr); + m_state->publish_result(); + } + + public: + result_promise() : m_state(std::make_shared>()), m_result_retrieved(false) {} + + result_promise(result_promise&& rhs) noexcept : m_state(std::move(rhs.m_state)), m_result_retrieved(rhs.m_result_retrieved) {} + + ~result_promise() noexcept { + break_task_if_needed(); + } + + result_promise& operator=(result_promise&& rhs) noexcept { + if (this != &rhs) { + break_task_if_needed(); + m_state = std::move(rhs.m_state); + m_result_retrieved = rhs.m_result_retrieved; + } + + return *this; + } + + explicit operator bool() const noexcept { + return static_cast(m_state); + } + + template + void set_result(argument_types&&... arguments) { + constexpr auto is_constructable = std::is_constructible_v || std::is_same_v; + static_assert(is_constructable, "result_promise::set_result() - <> is not constructable from <>"); + + throw_if_empty(details::consts::k_result_promise_set_result_error_msg); + + m_state->set_result(std::forward(arguments)...); + m_state->publish_result(); + m_state.reset(); + } + + void set_exception(std::exception_ptr exception_ptr) { + throw_if_empty(details::consts::k_result_promise_set_exception_error_msg); + + if (!static_cast(exception_ptr)) { + throw std::invalid_argument(details::consts::k_result_promise_set_exception_null_exception_error_msg); + } + + m_state->set_exception(exception_ptr); + m_state->publish_result(); + m_state.reset(); + } + + template + void set_from_function(callable_type&& callable, argument_types&&... args) { + constexpr auto is_invokable = std::is_invocable_r_v; + + static_assert(is_invokable, + "result_promise::set_from_function() - function(args...) is not invokable or its return type can't be used to construct <>"); + + throw_if_empty(details::consts::k_result_promise_set_from_function_error_msg); + m_state->from_callable(details::bind(std::forward(callable), std::forward(args)...)); + m_state->publish_result(); + m_state.reset(); + } + + result get_result() { + throw_if_empty(details::consts::k_result_get_error_msg); + + if (m_result_retrieved) { + throw errors::result_already_retrieved(details::consts::k_result_promise_get_result_already_retrieved_error_msg); + } + + m_result_retrieved = true; + return result(m_state); + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/results/result_awaitable.h b/include/concurrencpp/results/result_awaitable.h new file mode 100644 index 00000000..8ac03ccc --- /dev/null +++ b/include/concurrencpp/results/result_awaitable.h @@ -0,0 +1,117 @@ +#ifndef CONCURRENCPP_RESULT_AWAITABLE_H +#define CONCURRENCPP_RESULT_AWAITABLE_H + +#include "concurrencpp/results/constants.h" +#include "concurrencpp/results/result_fwd_declerations.h" + +#include "concurrencpp/errors.h" + +namespace concurrencpp { + template + class awaitable : public std::experimental::suspend_always { + + private: + std::shared_ptr> m_state; + + public: + awaitable(std::shared_ptr> state) noexcept : m_state(std::move(state)) {} + + awaitable(awaitable&& rhs) noexcept = default; + + bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { + if (!static_cast(m_state)) { + throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); + } + + return m_state->await(caller_handle); + } + + type await_resume() { + auto state = std::move(m_state); + return state->get(); + } + }; + + template + class via_awaitable : public std::experimental::suspend_always { + + private: + std::shared_ptr> m_state; + std::shared_ptr m_executor; + const bool m_force_rescheduling; + + public: + via_awaitable(std::shared_ptr> state, std::shared_ptr executor, bool force_rescheduling) noexcept : + m_state(std::move(state)), m_executor(std::move(executor)), m_force_rescheduling(force_rescheduling) {} + + via_awaitable(via_awaitable&& rhs) noexcept = default; + + bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { + if (!static_cast(m_state)) { + throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); + } + + return m_state->await_via(std::move(m_executor), caller_handle, m_force_rescheduling); + } + + type await_resume() { + auto state = std::move(m_state); + return state->get(); + } + }; + + template + class resolve_awaitable final : public std::experimental::suspend_always { + + private: + std::shared_ptr> m_state; + + public: + resolve_awaitable(std::shared_ptr> state) noexcept : m_state(std::move(state)) {} + + resolve_awaitable(resolve_awaitable&&) noexcept = default; + + bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { + if (!static_cast(m_state)) { + throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); + } + + return m_state->await(caller_handle); + } + + result await_resume() { + return result(std::move(m_state)); + } + }; + + template + class resolve_via_awaitable final : public std::experimental::suspend_always { + + private: + std::shared_ptr> m_state; + std::shared_ptr m_executor; + const bool m_force_rescheduling; + + public: + resolve_via_awaitable(std::shared_ptr> state, std::shared_ptr executor, bool force_rescheduling) noexcept + : + m_state(state), + m_executor(std::move(executor)), m_force_rescheduling(force_rescheduling) {} + + resolve_via_awaitable(resolve_via_awaitable&&) noexcept = default; + + bool await_suspend(std::experimental::coroutine_handle<> caller_handle) { + if (!static_cast(m_state)) { + throw concurrencpp::errors::empty_awaitable(details::consts::k_result_awaitable_error_msg); + } + + return m_state->await_via(std::move(m_executor), caller_handle, m_force_rescheduling); + } + + result await_resume() noexcept { + return result(std::move(m_state)); + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/results/result_core.h b/include/concurrencpp/results/result_core.h new file mode 100644 index 00000000..7ef5229b --- /dev/null +++ b/include/concurrencpp/results/result_core.h @@ -0,0 +1,393 @@ +#ifndef CONCURRENCPP_RESULT_CORE_H +#define CONCURRENCPP_RESULT_CORE_H + +#include "concurrencpp/results/executor_exception.h" +#include "concurrencpp/results/result_fwd_declerations.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "concurrencpp/errors.h" + +namespace concurrencpp::details { + class wait_context { + + private: + std::mutex m_lock; + std::condition_variable m_condition; + bool m_ready = false; + + public: + void wait() noexcept; + bool wait_for(size_t milliseconds) noexcept; + + void notify() noexcept; + }; + + struct when_all_state_base { + std::atomic_size_t m_counter; + + virtual ~when_all_state_base() noexcept = default; + virtual void on_result_ready() noexcept = 0; + }; + + struct when_any_state_base { + std::atomic_bool m_fulfilled = false; + std::recursive_mutex m_lock; + + virtual ~when_any_state_base() noexcept = default; + virtual void on_result_ready(size_t) noexcept = 0; + }; + + using when_any_ctx = std::pair, size_t>; + + template + class async_result { + + public: + using result_context = std::variant; + + protected: + result_context m_result; + + template + void build_impl(std::false_type /*no_throw*/, argument_types&&... arguments) { + try { + m_result.template emplace<1>(std::forward(arguments)...); + } catch (...) { + assert(m_result.index() == -1); + m_result.template emplace<0>(); + throw; + } + } + + template + void build_impl(std::true_type /*no_throw*/, argument_types&&... arguments) noexcept { + m_result.template emplace<1>(std::forward(arguments)...); + } + + public: + template + void build(argument_types&&... arguments) { + using intc = std::is_nothrow_constructible; + build_impl(intc(), std::forward(arguments)...); + } + + type get() { + const auto index = m_result.index(); + assert(index != 0); + + if (index == 2) { + std::rethrow_exception(std::get<2>(m_result)); + } + + return std::move(std::get<1>(m_result)); + } + }; + + template<> + class async_result { + + public: + using result_context = std::variant; + + protected: + result_context m_result; + + public: + void build() noexcept { + m_result.emplace<1>(); + } + + void get() { + const auto index = m_result.index(); + assert(index != 0); + + if (index == 2) { + std::rethrow_exception(std::get<2>(m_result)); + } + } + }; + + template + class async_result { + + public: + using result_context = std::variant; + + protected: + result_context m_result; + + public: + void build(type& reference) noexcept { + m_result.template emplace<1>(std::addressof(reference)); + } + + type& get() { + const auto index = m_result.index(); + assert(index != 0); + + if (index == 2) { + std::rethrow_exception(std::get<2>(m_result)); + } + + auto pointer = std::get<1>(m_result); + assert(pointer != nullptr); + assert(reinterpret_cast(pointer) % alignof(type) == 0); + + return *pointer; + } + }; + + class result_core_base { + + public: + using consumer_context = std::variant, + await_context, + std::shared_ptr, + std::shared_ptr, + when_any_ctx>; + + enum class pc_state { idle, producer, consumer }; + + protected: + consumer_context m_consumer; + std::atomic m_pc_state; + + void assert_consumer_idle() const noexcept { + assert(m_consumer.index() == 0); + } + + void assert_done() const noexcept { + assert(m_pc_state.load(std::memory_order_relaxed) == pc_state::producer); + } + + public: + void wait(); + + bool await(std::experimental::coroutine_handle<> caller_handle) noexcept; + + bool await_via(std::shared_ptr executor, std::experimental::coroutine_handle<> caller_handle, bool force_rescheduling); + + void when_all(std::shared_ptr when_all_state) noexcept; + + when_any_status when_any(std::shared_ptr when_any_state, size_t index) noexcept; + + void try_rewind_consumer() noexcept; + + static void schedule_coroutine(executor& executor, std::experimental::coroutine_handle<> handle); + static void schedule_coroutine(await_context& await_ctx); + }; + + template + class result_core : public async_result, public result_core_base { + + using result_context = typename async_result::result_context; + using consumer_context = typename result_core_base::consumer_context; + + private: + void assert_producer_idle() const noexcept { + assert(this->m_result.index() == 0); + } + + void clear_producer() noexcept { + this->m_result.template emplace<0>(); + } + + void schedule_continuation(await_context& await_ctx) noexcept { + try { + this->schedule_coroutine(await_ctx); + } catch (...) { + auto executor_error = std::make_exception_ptr(errors::executor_exception(std::current_exception(), std::move(await_ctx.first))); + + // consumer can't interfere here + clear_producer(); + this->set_exception(executor_error); + await_ctx.second(); + } + } + + template + void from_callable(std::true_type /*is_void_type*/, callable_type&& callable) { + callable(); + this->set_result(); + } + + template + void from_callable(std::false_type /*is_void_type*/, callable_type&& callable) { + this->set_result(callable()); + } + + public: + template + void set_result(argument_types&&... arguments) { + this->assert_producer_idle(); + this->build(std::forward(arguments)...); + } + + void set_exception(std::exception_ptr error) noexcept { + assert(error != nullptr); + this->assert_producer_idle(); + this->m_result.template emplace<2>(std::move(error)); + } + + // Consumer-side functions + result_status status() const noexcept { + const auto state = this->m_pc_state.load(std::memory_order_acquire); + assert(state != pc_state::consumer); + + if (state == pc_state::idle) { + return result_status::idle; + } + + const auto index = this->m_result.index(); + if (index == 1) { + return result_status::value; + } + + assert(index == 2); + return result_status::exception; + } + + template + concurrencpp::result_status wait_for(std::chrono::duration duration) { + auto get_result_status = [this] { + return this->m_result.index() == 1 ? result_status::value : result_status::exception; + }; + + const auto state_0 = this->m_pc_state.load(std::memory_order_acquire); + if (state_0 == pc_state::producer) { + return get_result_status(); + } + + assert_consumer_idle(); + + auto wait_ctx = std::make_shared(); + m_consumer.emplace<3>(wait_ctx); + + auto expected_idle_state = pc_state::idle; + const auto idle_0 = this->m_pc_state.compare_exchange_strong(expected_idle_state, pc_state::consumer, std::memory_order_acq_rel); + + if (!idle_0) { + assert_done(); + return get_result_status(); + } + + const auto ms = std::chrono::duration_cast(duration).count(); + if (wait_ctx->wait_for(static_cast(ms + 1))) { + assert_done(); + return get_result_status(); + } + + /* + now we need to rewind what we've done: the producer might try to + access the consumer context while we rewind the consumer context back to + nothing. first we'll cas the status back to idle. if we failed - the + producer has set its result, then there's no point in continue rewinding + - we just return the status of the result. if we managed to rewind the + status back to idle, then the consumer is "protected" because the + producer will not try to access the consumer if the flag doesn't say so. + */ + auto expected_consumer_state = pc_state::consumer; + const auto idle_1 = this->m_pc_state.compare_exchange_strong(expected_consumer_state, pc_state::idle, std::memory_order_acq_rel); + + if (!idle_1) { + assert_done(); + return get_result_status(); + } + + m_consumer.emplace<0>(); + return result_status::idle; + } + + template + concurrencpp::result_status wait_until(const std::chrono::time_point& timeout_time) { + const auto now = clock::now(); + if (timeout_time <= now) { + return status(); + } + + const auto diff = timeout_time - now; + return wait_for(diff); + } + + type get() { + assert_done(); + return this->async_result::get(); + } + + void publish_result() noexcept { + const auto state_before = this->m_pc_state.exchange(pc_state::producer, std::memory_order_acq_rel); + + assert(state_before != pc_state::producer); + + if (state_before == pc_state::idle) { + return; + } + + assert(state_before == pc_state::consumer); + + switch (this->m_consumer.index()) { + case 0: { + return; + } + + case 1: { + auto handle = std::get<1>(this->m_consumer); + handle(); + return; + } + + case 2: { + return this->schedule_continuation(std::get<2>(this->m_consumer)); + } + + case 3: { + auto& wait_ctx = std::get<3>(this->m_consumer); + wait_ctx->notify(); + return; + } + + case 4: { + auto& when_all_state = std::get<4>(this->m_consumer); + assert(static_cast(when_all_state)); + when_all_state->on_result_ready(); + return; + } + + case 5: { + auto& when_any_ctx = std::get<5>(this->m_consumer); + auto& when_any_state = when_any_ctx.first; + assert(static_cast(when_any_state)); + when_any_state->on_result_ready(when_any_ctx.second); + return; + } + + default: + break; + } + + assert(false); + } + + template + void from_callable(callable_type&& callable) { + using is_void = std::is_same; + + try { + this->from_callable(is_void {}, std::forward(callable)); + } catch (...) { + this->set_exception(std::current_exception()); + } + } + }; +} // namespace concurrencpp::details + +#endif diff --git a/include/concurrencpp/results/result_fwd_declerations.h b/include/concurrencpp/results/result_fwd_declerations.h new file mode 100644 index 00000000..0ce8e4ba --- /dev/null +++ b/include/concurrencpp/results/result_fwd_declerations.h @@ -0,0 +1,45 @@ +#ifndef CONCURRENCPP_RESULT_FWD_DECLERATIONS_H +#define CONCURRENCPP_RESULT_FWD_DECLERATIONS_H + +#include "concurrencpp/forward_declerations.h" + +#include +#include +#include + +namespace concurrencpp { + template + class result; + template + class result_promise; + + template + class awaitable; + template + class via_awaitable; + template + class resolve_awaitable; + template + class resolve_via_awaitable; + + struct executor_tag {}; + + struct null_result {}; + + enum class result_status { idle, value, exception }; +} // namespace concurrencpp + +namespace concurrencpp::details { + template + class result_core; + + using await_context = std::pair, std::experimental::coroutine_handle<>>; + + struct executor_bulk_tag {}; + + class when_result_helper; + + enum class when_any_status { set, result_ready }; +} // namespace concurrencpp::details + +#endif diff --git a/include/concurrencpp/results/when_result.h b/include/concurrencpp/results/when_result.h new file mode 100644 index 00000000..79c8a91c --- /dev/null +++ b/include/concurrencpp/results/when_result.h @@ -0,0 +1,372 @@ +#ifndef CONCURRENCPP_WHEN_RESULT_H +#define CONCURRENCPP_WHEN_RESULT_H + +#include +#include +#include +#include +#include + +#include "concurrencpp/results/make_result.h" + +#include "concurrencpp/errors.h" + +namespace concurrencpp::details { + class when_result_helper { + + private: + template + static void throw_if_empty_single(const char* error_message, const result& result) { + if (static_cast(result)) { + return; + } + + throw errors::empty_result(error_message); + } + + static void throw_if_empty_impl(const char* error_message) noexcept { + (void)error_message; + } + + template + static void throw_if_empty_impl(const char* error_message, const result& result, result_types&&... results) { + throw_if_empty_single(error_message, result); + throw_if_empty_impl(error_message, std::forward(results)...); + } + + public: + template + static result_core* get_core(result& result) noexcept { + return result.m_state.get(); + } + + template + static void throw_if_empty_tuple(const char* error_message, result_types&&... results) { + throw_if_empty_impl(error_message, std::forward(results)...); + } + + template + static void throw_if_empty_range(const char* error_message, iterator_type begin, iterator_type end) { + for (; begin != end; ++begin) { + throw_if_empty_single(error_message, *begin); + } + } + }; + + template + class when_all_tuple_state final : public when_all_state_base, public std::enable_shared_from_this> { + + using tuple_type = std::tuple; + + private: + tuple_type m_tuple; + std::shared_ptr> m_core_ptr; + + template + void set_state(result& result) noexcept { + auto core_ptr = when_result_helper::get_core(result); + core_ptr->when_all(this->shared_from_this()); + } + + public: + when_all_tuple_state(result_types&&... results) noexcept : + m_tuple(std::forward(results)...), m_core_ptr(std::make_shared>()) { + m_counter = sizeof...(result_types); + } + + void set_state() noexcept { + std::apply( + [this](auto&... result) { + (this->set_state(result), ...); + }, + m_tuple); + } + + void on_result_ready() noexcept override { + if (m_counter.fetch_sub(1, std::memory_order_relaxed) != 1) { + return; + } + + m_core_ptr->set_result(std::move(m_tuple)); + m_core_ptr->publish_result(); + } + + result get_result() noexcept { + return {m_core_ptr}; + } + }; + + template + class when_all_vector_state final : public when_all_state_base, public std::enable_shared_from_this> { + + private: + std::vector m_vector; + std::shared_ptr>> m_core_ptr; + + template + void set_state(result& result) noexcept { + auto core_ptr = when_result_helper::get_core(result); + ; + core_ptr->when_all(this->shared_from_this()); + } + + public: + template + when_all_vector_state(iterator_type begin, iterator_type end) : + m_vector(std::make_move_iterator(begin), std::make_move_iterator(end)), m_core_ptr(std::make_shared>>()) { + m_counter = m_vector.size(); + } + + void set_state() noexcept { + for (auto& result : m_vector) { + set_state(result); + } + } + + void on_result_ready() noexcept override { + if (m_counter.fetch_sub(1, std::memory_order_relaxed) != 1) { + return; + } + + m_core_ptr->set_result(std::move(m_vector)); + m_core_ptr->publish_result(); + } + + result> get_result() noexcept { + return {m_core_ptr}; + } + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + template + struct when_any_result { + std::size_t index; + sequence_type results; + + when_any_result() noexcept : index(static_cast(-1)) {} + + template + when_any_result(size_t index, result_types&&... results) noexcept : index(index), results(std::forward(results)...) {} + + when_any_result(when_any_result&&) noexcept = default; + when_any_result& operator=(when_any_result&&) noexcept = default; + }; +} // namespace concurrencpp + +namespace concurrencpp::details { + template + class when_any_tuple_state final : public when_any_state_base, public std::enable_shared_from_this> { + + using tuple_type = std::tuple; + + private: + tuple_type m_results; + std::shared_ptr>> m_core_ptr; + + template + std::pair set_state_impl(std::unique_lock& lock) noexcept { // should be called under a lock. + assert(lock.owns_lock()); + (void)lock; + + auto& result = std::get(m_results); + auto core_ptr = when_result_helper::get_core(result); + const auto status = core_ptr->when_any(this->shared_from_this(), index); + + if (status == when_any_status::result_ready) { + return {status, index}; + } + + const auto res = set_state_impl(lock); + + if (res.first == when_any_status::result_ready) { + core_ptr->try_rewind_consumer(); + } + + return res; + } + + template<> + std::pair set_state_impl(std::unique_lock& lock) noexcept { + (void)lock; + return {when_any_status::set, static_cast(-1)}; + } + + template + void unset_state(std::unique_lock& lock, size_t done_index) noexcept { + assert(lock.owns_lock()); + (void)lock; + if (index != done_index) { + auto core_ptr = when_result_helper::get_core(std::get(m_results)); + core_ptr->try_rewind_consumer(); + } + + unset_state(lock, done_index); + } + + template<> + void unset_state(std::unique_lock& lock, size_t done_index) noexcept { + (void)lock; + (void)done_index; + } + + void complete_promise(std::unique_lock& lock, size_t index) noexcept { + assert(lock.owns_lock()); + (void)lock; + + m_core_ptr->set_result(index, std::move(m_results)); + m_core_ptr->publish_result(); + } + + public: + when_any_tuple_state(result_types&&... results) : + m_results(std::forward(results)...), m_core_ptr(std::make_shared>>()) {} + + void on_result_ready(size_t index) noexcept override { + if (m_fulfilled.exchange(true, std::memory_order_relaxed)) { + return; + } + + std::unique_lock lock(m_lock); + unset_state<0>(lock, index); + complete_promise(lock, index); + } + + void set_state() noexcept { + std::unique_lock lock(m_lock); + const auto [status, index] = set_state_impl<0>(lock); + + if (status == when_any_status::result_ready) { + complete_promise(lock, index); + } + } + + result> get_result() noexcept { + return {m_core_ptr}; + } + }; + + template + class when_any_vector_state final : public when_any_state_base, public std::enable_shared_from_this> { + + private: + std::vector m_results; + std::shared_ptr>>> m_core_ptr; + + void unset_state(std::unique_lock& lock) noexcept { + assert(lock.owns_lock()); + (void)lock; + + for (auto& result : m_results) { + assert(static_cast(result)); + auto core_ptr = when_result_helper::get_core(result); + core_ptr->try_rewind_consumer(); + } + } + + void complete_promise(std::unique_lock& lock, size_t index) noexcept { + assert(lock.owns_lock()); + (void)lock; + m_core_ptr->set_result(index, std::move(m_results)); + m_core_ptr->publish_result(); + } + + public: + template + when_any_vector_state(iterator_type begin, iterator_type end) : + m_results(std::make_move_iterator(begin), std::make_move_iterator(end)), + m_core_ptr(std::make_shared>>>()) {} + + void on_result_ready(size_t index) noexcept override { + if (m_fulfilled.exchange(true, std::memory_order_relaxed)) { + return; + } + + std::unique_lock lock(m_lock); + unset_state(lock); + complete_promise(lock, index); + } + + void set_state() noexcept { + std::unique_lock lock(m_lock); + for (size_t i = 0; i < m_results.size(); i++) { + if (m_fulfilled.load(std::memory_order_relaxed)) { + return; + } + + auto core_ptr = when_result_helper::get_core(m_results[i]); + const auto res = core_ptr->when_any(this->shared_from_this(), i); + + if (res == when_any_status::result_ready) { + on_result_ready(i); + return; + } + } + } + + result>> get_result() noexcept { + return {m_core_ptr}; + } + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + inline result> when_all() { + return make_ready_result>(); + } + + template + result::type...>> when_all(result_types&&... results) { + + details::when_result_helper::throw_if_empty_tuple(details::consts::k_when_all_empty_result_error_msg, std::forward(results)...); + + auto when_all_state = std::make_shared::type...>>(std::forward(results)...); + + when_all_state->set_state(); + return when_all_state->get_result(); + } + + template + result::value_type>> when_all(iterator_type begin, iterator_type end) { + + details::when_result_helper::throw_if_empty_range(details::consts::k_when_all_empty_result_error_msg, begin, end); + + using type = typename std::iterator_traits::value_type; + + if (begin == end) { + return make_ready_result>(); + } + + auto when_all_state = std::make_shared>(begin, end); + when_all_state->set_state(); + return when_all_state->get_result(); + } + + template + result>> when_any(result_types&&... results) { + static_assert(sizeof...(result_types) != 0, "concurrencpp::when_any - the function must accept at least one result"); + + details::when_result_helper::throw_if_empty_tuple(details::consts::k_when_any_empty_result_error_msg, std::forward(results)...); + + auto state = std::make_shared>(std::forward(results)...); + state->set_state(); + return state->get_result(); + } + + template + result::value_type>>> when_any(iterator_type begin, iterator_type end) { + details::when_result_helper::throw_if_empty_range(details::consts::k_when_any_empty_result_error_msg, begin, end); + + if (begin == end) { + throw std::invalid_argument(details::consts::k_when_any_empty_range_error_msg); + } + + using type = typename std::iterator_traits::value_type; + + auto state = std::make_shared>(begin, end); + state->set_state(); + return state->get_result(); + } +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/runtime/constants.h b/include/concurrencpp/runtime/constants.h new file mode 100644 index 00000000..dc3402c8 --- /dev/null +++ b/include/concurrencpp/runtime/constants.h @@ -0,0 +1,17 @@ +#ifndef CONCURRENCPP_CONSTANTS_H +#define CONCURRENCPP_CONSTANTS_H + +#include + +namespace concurrencpp::details::consts { + constexpr static size_t k_cpu_threadpool_worker_count_factor = 1; + constexpr static size_t k_background_threadpool_worker_count_factor = 4; + constexpr static size_t k_max_worker_waiting_time_sec = 2 * 60; + constexpr static size_t k_default_number_of_cores = 8; + + constexpr static unsigned int k_concurrencpp_version_major = 0; + constexpr static unsigned int k_concurrencpp_version_minor = 0; + constexpr static unsigned int k_concurrencpp_version_revision = 8; +} // namespace concurrencpp::details::consts + +#endif diff --git a/include/concurrencpp/runtime/runtime.h b/include/concurrencpp/runtime/runtime.h new file mode 100644 index 00000000..1b15c4e6 --- /dev/null +++ b/include/concurrencpp/runtime/runtime.h @@ -0,0 +1,87 @@ +#ifndef CONCURRENCPP_RUNTIME_H +#define CONCURRENCPP_RUNTIME_H + +#include "constants.h" + +#include "concurrencpp/forward_declerations.h" + +#include +#include +#include +#include + +namespace concurrencpp::details { + class executor_collection { + + private: + std::mutex m_lock; + std::vector> m_executors; + + public: + void register_executor(std::shared_ptr executor); + void shutdown_all() noexcept; + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + struct runtime_options { + size_t max_cpu_threads; + std::chrono::seconds max_cpu_thread_waiting_time; + + size_t max_background_threads; + std::chrono::seconds max_background_thread_waiting_time; + + runtime_options() noexcept; + + runtime_options(const runtime_options&) noexcept = default; + runtime_options& operator=(const runtime_options&) noexcept = default; + }; + + class runtime { + + private: + std::shared_ptr m_timer_queue; + + std::shared_ptr m_inline_executor; + std::shared_ptr m_thread_pool_executor; + std::shared_ptr m_background_executor; + std::shared_ptr m_thread_executor; + + details::executor_collection m_registered_executors; + + public: + runtime(); + runtime(const concurrencpp::runtime_options& options); + + ~runtime() noexcept; + + std::shared_ptr timer_queue() const noexcept; + + std::shared_ptr inline_executor() const noexcept; + std::shared_ptr thread_pool_executor() const noexcept; + std::shared_ptr background_executor() const noexcept; + std::shared_ptr thread_executor() const noexcept; + + std::shared_ptr make_worker_thread_executor(); + std::shared_ptr make_manual_executor(); + + static std::tuple version() noexcept; + + template + std::shared_ptr make_executor(argument_types&&... arguments) { + static_assert(std::is_base_of_v, + "concurrencpp::runtime::make_executor - <> is not a derived class of concurrencpp::executor."); + + static_assert(std::is_constructible_v, + "concurrencpp::runtime::make_executor - can not build <> from <>."); + + static_assert(!std::is_abstract_v, "concurrencpp::runtime::make_executor - <> is an abstract class."); + + auto executor = std::make_shared(std::forward(arguments)...); + m_registered_executors.register_executor(executor); + return executor; + } + }; +} // namespace concurrencpp + +#endif diff --git a/include/concurrencpp/threads/thread.h b/include/concurrencpp/threads/thread.h new file mode 100644 index 00000000..2cc91c73 --- /dev/null +++ b/include/concurrencpp/threads/thread.h @@ -0,0 +1,40 @@ +#ifndef CONCURRENCPP_THREAD_H +#define CONCURRENCPP_THREAD_H + +#include +#include + +namespace concurrencpp::details { + class thread { + + private: + std::thread m_thread; + + static void set_name(std::string_view name) noexcept; + + public: + thread() noexcept = default; + thread(thread&&) noexcept = default; + + template + thread(std::string name, callable_type&& callable) { + m_thread = std::thread([name = std::move(name), callable = std::forward(callable)] { + set_name(name); + callable(); + }); + } + + thread& operator=(thread&& rhs) noexcept = default; + + std::thread::id get_id() const noexcept; + + static std::uintptr_t get_current_virtual_id() noexcept; + + bool joinable() const noexcept; + void join(); + + static size_t hardware_concurrency() noexcept; + }; +} // namespace concurrencpp::details + +#endif diff --git a/include/concurrencpp/timers/constants.h b/include/concurrencpp/timers/constants.h new file mode 100644 index 00000000..2408c73b --- /dev/null +++ b/include/concurrencpp/timers/constants.h @@ -0,0 +1,17 @@ +#ifndef CONCURRENCPP_TIMER_CONSTS_H +#define CONCURRENCPP_TIMER_CONSTS_H + +namespace concurrencpp::details::consts { + inline const char* k_timer_empty_get_due_time_err_msg = "timer::get_due_time() - timer is empty."; + inline const char* k_timer_empty_get_frequency_err_msg = "timer::get_frequency() - timer is empty."; + inline const char* k_timer_empty_get_executor_err_msg = "timer::get_executor() - timer is empty."; + inline const char* k_timer_empty_get_timer_queue_err_msg = "timer::get_timer_queue() - timer is empty."; + inline const char* k_timer_empty_set_frequency_err_msg = "timer::set_frequency() - timer is empty."; + + inline const char* k_timer_queue_make_timer_executor_null_err_msg = "timer_queue::make_timer() - executor is null."; + inline const char* k_timer_queue_make_oneshot_timer_executor_null_err_msg = "timer_queue::make_one_shot_timer() - executor is null."; + inline const char* k_timer_queue_make_delay_object_executor_null_err_msg = "timer_queue::make_delay_object() - executor is null."; + inline const char* k_timer_queue_shutdown_err_msg = "timer_queue has been shut down."; +} // namespace concurrencpp::details::consts + +#endif diff --git a/include/concurrencpp/timers/timer.h b/include/concurrencpp/timers/timer.h new file mode 100644 index 00000000..fd14ff83 --- /dev/null +++ b/include/concurrencpp/timers/timer.h @@ -0,0 +1,134 @@ +#ifndef CONCURRENCPP_TIMER_H +#define CONCURRENCPP_TIMER_H + +#include +#include +#include + +#include "concurrencpp/forward_declerations.h" + +namespace concurrencpp::details { + class timer_state_base : public std::enable_shared_from_this { + + public: + using clock_type = std::chrono::high_resolution_clock; + using time_point = std::chrono::time_point; + using milliseconds = std::chrono::milliseconds; + + private: + const std::weak_ptr m_timer_queue; + const std::shared_ptr m_executor; + const size_t m_due_time; + std::atomic_size_t m_frequency; + time_point m_deadline; // set by the c.tor, changed only by the timer_queue thread. + const bool m_is_oneshot; + + static time_point make_deadline(milliseconds diff) noexcept { + return clock_type::now() + diff; + } + + public: + timer_state_base(size_t due_time, + size_t frequency, + std::shared_ptr executor, + std::weak_ptr timer_queue, + bool is_oneshot) noexcept; + + virtual ~timer_state_base() noexcept = default; + + virtual void execute() = 0; + + void fire(); + + bool expired(const time_point now) const noexcept { + return m_deadline <= now; + } + + time_point get_deadline() const noexcept { + return m_deadline; + } + + size_t get_frequency() const noexcept { + return m_frequency.load(std::memory_order_relaxed); + } + + size_t get_due_time() const noexcept { + return m_due_time; // no need to synchronize, const anyway. + } + + bool is_oneshot() const noexcept { + return m_is_oneshot; + } + + std::shared_ptr get_executor() const noexcept { + return m_executor; + } + + std::weak_ptr get_timer_queue() const noexcept { + return m_timer_queue; + } + + void set_new_frequency(size_t new_frequency) noexcept { + m_frequency.store(new_frequency, std::memory_order_relaxed); + } + }; + + template + class timer_state final : public timer_state_base { + + private: + callable_type m_callable; + + public: + template + timer_state(size_t due_time, + size_t frequency, + std::shared_ptr executor, + std::weak_ptr timer_queue, + bool is_oneshot, + given_callable_type&& callable) : + timer_state_base(due_time, frequency, std::move(executor), std::move(timer_queue), is_oneshot), + m_callable(std::forward(callable)) {} + + void execute() override { + m_callable(); + } + }; +} // namespace concurrencpp::details + +namespace concurrencpp { + class timer { + + private: + std::shared_ptr m_state; + + void throw_if_empty(const char* error_message) const; + + public: + timer() noexcept = default; + ~timer() noexcept; + + timer(std::shared_ptr timer_impl) noexcept; + + timer(timer&& rhs) noexcept = default; + timer& operator=(timer&& rhs) noexcept; + + timer(const timer&) = delete; + timer& operator=(const timer&) = delete; + + void cancel(); + + std::chrono::milliseconds get_due_time() const; + std::shared_ptr get_executor() const; + std::weak_ptr get_timer_queue() const; + + std::chrono::milliseconds get_frequency() const; + void set_frequency(std::chrono::milliseconds new_frequency); + + operator bool() const noexcept { + return static_cast(m_state); + } + }; +} // namespace concurrencpp + +#endif // CONCURRENCPP_TIMER_H diff --git a/include/concurrencpp/timers/timer_queue.h b/include/concurrencpp/timers/timer_queue.h new file mode 100644 index 00000000..1ca2f9e4 --- /dev/null +++ b/include/concurrencpp/timers/timer_queue.h @@ -0,0 +1,121 @@ +#ifndef CONCURRENCPP_TIMER_QUEUE_H +#define CONCURRENCPP_TIMER_QUEUE_H + +#include "constants.h" +#include "timer.h" + +#include "concurrencpp/errors.h" + +#include "concurrencpp/utils/bind.h" +#include "concurrencpp/threads/thread.h" + +#include +#include + +#include +#include +#include + +#include + +namespace concurrencpp::details { + enum class timer_request { add, remove }; +} + +namespace concurrencpp { + class timer_queue : public std::enable_shared_from_this { + + public: + using timer_ptr = std::shared_ptr; + using clock_type = std::chrono::high_resolution_clock; + using time_point = std::chrono::time_point; + using request_queue = std::vector>; + + friend class concurrencpp::timer; + + private: + std::atomic_bool m_atomic_abort; + std::mutex m_lock; + request_queue m_request_queue; + details::thread m_worker; + std::condition_variable m_condition; + bool m_abort; + + void ensure_worker_thread(std::unique_lock& lock); + + void add_timer(std::unique_lock& lock, timer_ptr new_timer) noexcept; + void remove_timer(timer_ptr existing_timer) noexcept; + + template + timer_ptr make_timer_impl(size_t due_time, size_t frequency, std::shared_ptr executor, bool is_oneshot, callable_type&& callable) { + + assert(static_cast(executor)); + + using decayed_type = typename std::decay_t; + + auto timer_core = std::make_shared>(due_time, + frequency, + std::move(executor), + weak_from_this(), + is_oneshot, + std::forward(callable)); + + std::unique_lock lock(m_lock); + if (m_abort) { + throw errors::timer_queue_shutdown(details::consts::k_timer_queue_shutdown_err_msg); + } + + ensure_worker_thread(lock); + add_timer(lock, timer_core); + return timer_core; + } + + void work_loop() noexcept; + + public: + timer_queue() noexcept; + ~timer_queue() noexcept; + + void shutdown() noexcept; + bool shutdown_requested() const noexcept; + + template + timer make_timer(std::chrono::milliseconds due_time, + std::chrono::milliseconds frequency, + std::shared_ptr executor, + callable_type&& callable, + argumet_types&&... arguments) { + + if (!static_cast(executor)) { + throw std::invalid_argument(details::consts::k_timer_queue_make_timer_executor_null_err_msg); + } + + return make_timer_impl(due_time.count(), + frequency.count(), + std::move(executor), + false, + details::bind(std::forward(callable), std::forward(arguments)...)); + } + + template + timer make_one_shot_timer(std::chrono::milliseconds due_time, + std::shared_ptr executor, + callable_type&& callable, + argumet_types&&... arguments) { + + if (!static_cast(executor)) { + throw std::invalid_argument(details::consts::k_timer_queue_make_oneshot_timer_executor_null_err_msg); + } + + return make_timer_impl(due_time.count(), + 0, + std::move(executor), + true, + details::bind(std::forward(callable), std::forward(arguments)...)); + } + + result make_delay_object(std::chrono::milliseconds due_time, std::shared_ptr executor); + }; +} // namespace concurrencpp + +#endif // CONCURRENCPP_TIMER_QUEUE_H diff --git a/include/concurrencpp/utils/bind.h b/include/concurrencpp/utils/bind.h new file mode 100644 index 00000000..90dcece2 --- /dev/null +++ b/include/concurrencpp/utils/bind.h @@ -0,0 +1,23 @@ +#ifndef CONCURRENCPP_BIND_H +#define CONCURRENCPP_BIND_H + +#include +#include + +namespace concurrencpp::details { + template + auto bind(callable_type&& callable) { + return std::forward(callable); // no arguments to bind + } + + template + auto bind(callable_type&& callable, argument_types&&... arguments) { + constexpr static auto inti = std::is_nothrow_invocable_v; + return [callable = std::forward(callable), + tuple = std::make_tuple(std::forward(arguments)...)]() mutable noexcept(inti) -> decltype(auto) { + return std::apply(callable, tuple); + }; + } +} // namespace concurrencpp::details + +#endif diff --git a/sandbox/CMakeLists.txt b/sandbox/CMakeLists.txt index ccb646c9..2a0d828d 100644 --- a/sandbox/CMakeLists.txt +++ b/sandbox/CMakeLists.txt @@ -1,10 +1,17 @@ cmake_minimum_required(VERSION 3.16) -project(sandbox) -add_executable( - sandbox - main.cpp -) +project(sandbox LANGUAGES CXX) -target_link_libraries(sandbox concurrencpp) +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +FetchContent_MakeAvailable(concurrencpp) +include(../cmake/coroutineOptions.cmake) + +add_executable(sandbox main.cpp) + +target_compile_features(sandbox PRIVATE cxx_std_20) + +target_link_libraries(sandbox PRIVATE concurrencpp::concurrencpp) + +target_coroutine_options(sandbox) diff --git a/sandbox/main.cpp b/sandbox/main.cpp index 21dff4ad..c585522c 100644 --- a/sandbox/main.cpp +++ b/sandbox/main.cpp @@ -1,13 +1,13 @@ -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include int main() { - concurrencpp::runtime runtime; - auto result = runtime.thread_pool_executor()->submit([] { - std::cout << "hello world" << std::endl; - }); + concurrencpp::runtime runtime; + auto result = runtime.thread_pool_executor()->submit([] { + std::cout << "hello world" << std::endl; + }); - result.get(); - return 0; + result.get(); + return 0; } diff --git a/source/executors/executor.cpp b/source/executors/executor.cpp new file mode 100644 index 00000000..deab5d15 --- /dev/null +++ b/source/executors/executor.cpp @@ -0,0 +1,14 @@ +#include "concurrencpp/executors/executor.h" +#include "concurrencpp/executors/constants.h" + +#include "concurrencpp/errors.h" +#include "concurrencpp/threads/thread.h" + +void concurrencpp::details::throw_executor_shutdown_exception(std::string_view executor_name) { + const auto error_msg = std::string(executor_name) + consts::k_executor_shutdown_err_msg; + throw errors::executor_shutdown(error_msg); +} + +std::string concurrencpp::details::make_executor_worker_name(std::string_view executor_name) { + return std::string(executor_name) + " worker"; +} diff --git a/source/executors/manual_executor.cpp b/source/executors/manual_executor.cpp new file mode 100644 index 00000000..7b295651 --- /dev/null +++ b/source/executors/manual_executor.cpp @@ -0,0 +1,167 @@ +#include "concurrencpp/executors/manual_executor.h" +#include "concurrencpp/executors/constants.h" + +using concurrencpp::manual_executor; + +void manual_executor::destroy_tasks(std::unique_lock& lock) noexcept { + assert(lock.owns_lock()); + (void)lock; + + for (auto task : m_tasks) { + task.destroy(); + } + + m_tasks.clear(); +} + +manual_executor::manual_executor() : + derivable_executor(details::consts::k_manual_executor_name), m_abort(false), m_atomic_abort(false) {} + +void manual_executor::enqueue(std::experimental::coroutine_handle<> task) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + m_tasks.emplace_back(task); + lock.unlock(); + m_condition.notify_all(); +} + +void manual_executor::enqueue(std::span> tasks) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + m_tasks.insert(m_tasks.end(), tasks.begin(), tasks.end()); + lock.unlock(); + + m_condition.notify_all(); +} + +int manual_executor::max_concurrency_level() const noexcept { + return details::consts::k_manual_executor_max_concurrency_level; +} + +size_t manual_executor::size() const noexcept { + std::unique_lock lock(m_lock); + return m_tasks.size(); +} + +bool manual_executor::empty() const noexcept { + return size() == 0; +} + +bool manual_executor::loop_once() { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + if (m_tasks.empty()) { + return false; + } + + auto task = m_tasks.front(); + m_tasks.pop_front(); + lock.unlock(); + + task(); + return true; +} + +bool manual_executor::loop_once(std::chrono::milliseconds max_waiting_time) { + std::unique_lock lock(m_lock); + m_condition.wait_for(lock, max_waiting_time, [this] { + return !m_tasks.empty() || m_abort; + }); + + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + if (m_tasks.empty()) { + return false; + } + + auto task = m_tasks.front(); + m_tasks.pop_front(); + lock.unlock(); + + task(); + return true; +} + +size_t manual_executor::loop(size_t max_count) { + size_t executed = 0; + for (; executed < max_count; ++executed) { + if (!loop_once()) { + break; + } + } + + return executed; +} + +size_t manual_executor::clear() noexcept { + std::unique_lock lock(m_lock); + auto tasks = std::move(m_tasks); + lock.unlock(); + + for (auto task : tasks) { + task.destroy(); + } + + return tasks.size(); +} + +void manual_executor::wait_for_task() { + std::unique_lock lock(m_lock); + m_condition.wait(lock, [this] { + return !m_tasks.empty() || m_abort; + }); + + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } +} + +bool manual_executor::wait_for_task(std::chrono::milliseconds max_waiting_time) { + std::unique_lock lock(m_lock); + const auto res = m_condition.wait_for(lock, max_waiting_time, [this] { + return !m_tasks.empty() || m_abort; + }); + + if (!res) { + assert(!m_abort && m_tasks.empty()); + return false; + } + + if (!m_abort) { + assert(!m_tasks.empty()); + return true; + } + + details::throw_executor_shutdown_exception(name); +} + +void manual_executor::shutdown() noexcept { + const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); + if (abort) { + return; // shutdown had been called before. + } + + { + std::unique_lock lock(m_lock); + m_abort = true; + + destroy_tasks(lock); + } + + m_condition.notify_all(); +} + +bool manual_executor::shutdown_requested() const noexcept { + return m_atomic_abort.load(std::memory_order_relaxed); +} diff --git a/source/executors/thread_executor.cpp b/source/executors/thread_executor.cpp new file mode 100644 index 00000000..590af740 --- /dev/null +++ b/source/executors/thread_executor.cpp @@ -0,0 +1,88 @@ +#include "concurrencpp/executors/thread_executor.h" +#include "concurrencpp/executors/constants.h" + +using concurrencpp::thread_executor; +using concurrencpp::details::thread_worker; + +thread_worker::thread_worker(thread_executor& parent_pool) noexcept : m_parent_pool(parent_pool) {} + +thread_worker::~thread_worker() noexcept { + m_thread.join(); +} + +void thread_worker::execute_and_retire(std::experimental::coroutine_handle<> task, typename std::list::iterator self_it) { + task(); + m_parent_pool.retire_worker(self_it); +} + +void thread_worker::start(std::string worker_name, std::experimental::coroutine_handle<> task, std::list::iterator self_it) { + m_thread = thread(std::move(worker_name), [this, task, self_it] { + execute_and_retire(task, self_it); + }); +} + +thread_executor::thread_executor() : + derivable_executor(details::consts::k_thread_executor_name), m_abort(false), m_atomic_abort(false) {} + +thread_executor::~thread_executor() noexcept { + assert(m_workers.empty()); + assert(m_last_retired.empty()); +} + +void thread_executor::enqueue_impl(std::experimental::coroutine_handle<> task) { + m_workers.emplace_front(*this); + m_workers.front().start(details::make_executor_worker_name(name), task, m_workers.begin()); +} + +void thread_executor::enqueue(std::experimental::coroutine_handle<> task) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + enqueue_impl(task); +} + +void thread_executor::enqueue(std::span> tasks) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + for (auto task : tasks) { + enqueue_impl(task); + } +} + +int thread_executor::max_concurrency_level() const noexcept { + return details::consts::k_thread_executor_max_concurrency_level; +} + +bool thread_executor::shutdown_requested() const noexcept { + return m_atomic_abort.load(std::memory_order_relaxed); +} + +void thread_executor::shutdown() noexcept { + const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); + if (abort) { + return; // shutdown had been called before. + } + + std::unique_lock lock(m_lock); + m_abort = true; + m_condition.wait(lock, [this] { + return m_workers.empty(); + }); + m_last_retired.clear(); +} + +void thread_executor::retire_worker(std::list::iterator it) { + std::unique_lock lock(m_lock); + auto last_retired = std::move(m_last_retired); + m_last_retired.splice(m_last_retired.begin(), m_workers, it); + + lock.unlock(); + m_condition.notify_one(); + + last_retired.clear(); +} diff --git a/source/executors/thread_pool_executor.cpp b/source/executors/thread_pool_executor.cpp new file mode 100644 index 00000000..ac2c7119 --- /dev/null +++ b/source/executors/thread_pool_executor.cpp @@ -0,0 +1,436 @@ +#include "concurrencpp/executors/thread_pool_executor.h" +#include "concurrencpp/executors/constants.h" + +#include + +#include +#include + +using concurrencpp::thread_pool_executor; +using concurrencpp::details::idle_worker_set; +using concurrencpp::details::thread_pool_worker; + +namespace concurrencpp::details { + class randomizer { + + private: + uint32_t m_seed; + + public: + randomizer(uint32_t seed) noexcept : m_seed(seed) {} + + uint32_t operator()() noexcept { + m_seed = (214013 * m_seed + 2531011); + return (m_seed >> 16) & 0x7FFF; + } + }; + + struct thread_pool_per_thread_data { + thread_pool_worker* this_worker; + randomizer randomizer; + size_t this_thread_index; + + thread_pool_per_thread_data() noexcept : this_worker(nullptr), randomizer(static_cast(std::rand())), this_thread_index(static_cast(-1)) {} + }; + + static thread_local thread_pool_per_thread_data s_tl_thread_pool_data; +} // namespace concurrencpp::details + +idle_worker_set::idle_worker_set(size_t size) : m_approx_size(0), m_idle_flags(std::make_unique(size)), m_size(size) { + for (size_t i = 0; i < size; i++) { + m_idle_flags[i].flag = status::active; + } +} + +void idle_worker_set::set_idle(size_t idle_thread) noexcept { + m_idle_flags[idle_thread].flag.store(status::idle, std::memory_order_relaxed); + m_approx_size.fetch_add(1, + std::memory_order_release); // the mo is in order for the addition to + // happen AFTER flagging. +} + +void idle_worker_set::set_active(size_t idle_thread) noexcept { + auto expected = status::idle; + const auto swapped = m_idle_flags[idle_thread].flag.compare_exchange_strong(expected, status::active, std::memory_order_relaxed); + + if (!swapped) { + return; + } + + m_approx_size.fetch_sub(1, std::memory_order_release); +} + +bool idle_worker_set::try_acquire_flag(size_t index) noexcept { + const auto worker_status = m_idle_flags[index].flag.load(std::memory_order_relaxed); + if (worker_status == status::active) { + return false; + } + + auto expected = status::idle; + const auto swapped = m_idle_flags[index].flag.compare_exchange_strong(expected, status::active, std::memory_order_relaxed); + + if (swapped) { + m_approx_size.fetch_sub(1, std::memory_order_relaxed); + } + + return swapped; +} + +size_t idle_worker_set::find_idle_worker(size_t caller_index) noexcept { + if (m_approx_size.load(std::memory_order_relaxed) <= 0) { + return static_cast(-1); + } + + const auto starting_pos = s_tl_thread_pool_data.randomizer() % m_size; + for (size_t i = 0; i < m_size; i++) { + const auto index = (starting_pos + i) % m_size; + if (index == caller_index) { + continue; + } + + if (try_acquire_flag(index)) { + return index; + } + } + + return static_cast(-1); +} + +void idle_worker_set::find_idle_workers(size_t caller_index, std::vector& result_buffer, size_t max_count) noexcept { + assert(result_buffer.capacity() >= max_count); + + if (m_approx_size.load(std::memory_order_relaxed) <= 0) { + return; + } + + const auto starting_pos = s_tl_thread_pool_data.randomizer() % m_size; + size_t count = 0; + + for (size_t i = 0; (i < m_size) && (count < max_count); i++) { + const auto index = (starting_pos + i) % m_size; + if (index == caller_index) { + continue; + } + + if (try_acquire_flag(index)) { + result_buffer.emplace_back(index); + ++count; + } + } +} + +thread_pool_worker::thread_pool_worker(thread_pool_executor& parent_pool, size_t index, size_t pool_size, std::chrono::seconds max_idle_time) : + m_atomic_abort(false), m_parent_pool(parent_pool), m_index(index), m_pool_size(pool_size), m_max_idle_time(max_idle_time), + m_worker_name(details::make_executor_worker_name(parent_pool.name)), m_status(status::idle), m_abort(false) { + m_idle_worker_list.reserve(pool_size); +} + +thread_pool_worker::thread_pool_worker(thread_pool_worker&& rhs) noexcept : + m_parent_pool(rhs.m_parent_pool), m_index(rhs.m_index), m_pool_size(rhs.m_pool_size), m_max_idle_time(rhs.m_max_idle_time) { + std::abort(); // shouldn't be called +} + +thread_pool_worker::~thread_pool_worker() noexcept { + assert(m_status == status::idle); + assert(!m_thread.joinable()); +} + +void thread_pool_worker::balance_work() { + const auto task_count = m_private_queue.size(); + if (task_count == 0) { + return; + } + + // we assume all threads but us are idle + const auto idle_worker_count = std::min(m_pool_size - 1, task_count); + if (idle_worker_count == 0) { + return; // a thread-pool with a single thread + } + + m_parent_pool.find_idle_workers(m_index, m_idle_worker_list, idle_worker_count); + if (m_idle_worker_list.empty()) { + return; + } + + for (auto idle_worker_index : m_idle_worker_list) { + assert(idle_worker_index != m_index); + assert(idle_worker_index < m_pool_size); + + const auto task = m_private_queue.front(); + m_private_queue.pop_front(); + m_parent_pool.worker_at(idle_worker_index).enqueue_foreign(task); + } + + m_idle_worker_list.clear(); +} + +bool thread_pool_worker::wait_for_task(std::unique_lock& lock) noexcept { + assert(lock.owns_lock()); + + m_parent_pool.mark_worker_idle(m_index); + m_status = status::waiting; + + const auto timeout = !m_condition.wait_for(lock, m_max_idle_time, [this] { + return !m_public_queue.empty() || m_abort; + }); + + if (timeout || m_abort) { + m_status = status::idle; + lock.unlock(); + return false; + } + + assert(!m_public_queue.empty()); + m_status = status::working; + m_parent_pool.mark_worker_active(m_index); + return true; +} + +bool thread_pool_worker::drain_queue_impl() { + while (!m_private_queue.empty()) { + auto task = m_private_queue.back(); + m_private_queue.pop_back(); + + balance_work(); + + if (m_atomic_abort.load(std::memory_order_relaxed)) { + std::unique_lock lock(m_lock); + m_status = status::idle; + return false; + } + + task(); + } + + return true; +} + +bool thread_pool_worker::drain_queue() { + std::unique_lock lock(m_lock); + if (m_public_queue.empty() && m_abort == false) { + if (!wait_for_task(lock)) { + return false; + } + } + + assert(lock.owns_lock()); + + if (m_abort) { + m_status = status::idle; + return false; + } + + assert(m_private_queue.empty()); + std::swap(m_private_queue, m_public_queue); // reuse underlying allocations. + lock.unlock(); + + return drain_queue_impl(); +} + +void thread_pool_worker::work_loop() noexcept { + s_tl_thread_pool_data.this_worker = this; + s_tl_thread_pool_data.this_thread_index = m_index; + + while (true) { + if (!drain_queue()) { + return; + } + } +} + +void thread_pool_worker::destroy_tasks() noexcept { + std::unique_lock lock(m_lock); + for (auto task : m_private_queue) { + task.destroy(); + } + + m_private_queue.clear(); + + for (auto task : m_public_queue) { + task.destroy(); + } + + m_public_queue.clear(); +} + +void thread_pool_worker::ensure_worker_active(std::unique_lock& lock) { + assert(lock.owns_lock()); + const auto status = m_status; + + switch (status) { + case status::working: { + lock.unlock(); + return; + } + + case status::waiting: { + lock.unlock(); + m_condition.notify_one(); + return; + } + + case status::idle: { + auto stale_worker = std::move(m_thread); + m_thread = thread(m_worker_name, [this] { + work_loop(); + }); + + m_status = status::working; + lock.unlock(); + + if (stale_worker.joinable()) { + stale_worker.join(); + } + } + } +} + +void thread_pool_worker::enqueue_foreign(std::experimental::coroutine_handle<> task) { + std::unique_lock lock(m_lock); + if (m_abort) { + throw_executor_shutdown_exception(m_parent_pool.name); + } + + m_public_queue.emplace_back(task); + ensure_worker_active(lock); +} + +void thread_pool_worker::enqueue_foreign(std::span> tasks) { + std::unique_lock lock(m_lock); + if (m_abort) { + throw_executor_shutdown_exception(m_parent_pool.name); + } + + m_public_queue.insert(m_public_queue.end(), tasks.begin(), tasks.end()); + ensure_worker_active(lock); +} + +void thread_pool_worker::enqueue_local(std::experimental::coroutine_handle<> task) { + if (m_atomic_abort.load(std::memory_order_relaxed)) { + throw_executor_shutdown_exception(m_parent_pool.name); + } + + m_private_queue.emplace_back(task); +} + +void thread_pool_worker::enqueue_local(std::span> tasks) { + if (m_atomic_abort.load(std::memory_order_relaxed)) { + throw_executor_shutdown_exception(m_parent_pool.name); + } + + m_private_queue.insert(m_private_queue.end(), tasks.begin(), tasks.end()); +} + +void thread_pool_worker::abort() noexcept { + assert(m_atomic_abort.load(std::memory_order_relaxed) == false); + m_atomic_abort.store(true, std::memory_order_relaxed); + + { + std::unique_lock lock(m_lock); + m_abort = true; + } + + m_condition.notify_all(); +} + +void thread_pool_worker::join() noexcept { + if (m_thread.joinable()) { + m_thread.join(); + } + + destroy_tasks(); +} + +thread_pool_executor::thread_pool_executor(std::string_view name, size_t size, std::chrono::seconds max_idle_time) : + derivable_executor(name), m_round_robin_cursor(0), m_idle_workers(size), m_abort(false) { + m_workers.reserve(size); + + std::srand(static_cast(std::time(nullptr))); + + for (size_t i = 0; i < size; i++) { + m_workers.emplace_back(*this, i, size, max_idle_time); + } + + for (size_t i = 0; i < size; i++) { + m_idle_workers.set_idle(i); + } +} + +thread_pool_executor::~thread_pool_executor() noexcept {} + +void thread_pool_executor::find_idle_workers(size_t caller_index, std::vector& buffer, size_t max_count) noexcept { + m_idle_workers.find_idle_workers(caller_index, buffer, max_count); +} + +void thread_pool_executor::mark_worker_idle(size_t index) noexcept { + assert(index < m_workers.size()); + m_idle_workers.set_idle(index); +} + +void thread_pool_executor::mark_worker_active(size_t index) noexcept { + assert(index < m_workers.size()); + m_idle_workers.set_active(index); +} + +void thread_pool_executor::enqueue(std::experimental::coroutine_handle<> task) { + const auto idle_worker_pos = m_idle_workers.find_idle_worker(details::s_tl_thread_pool_data.this_thread_index); + if (idle_worker_pos != static_cast(-1)) { + return m_workers[idle_worker_pos].enqueue_foreign(task); + } + + if (details::s_tl_thread_pool_data.this_worker != nullptr) { + return details::s_tl_thread_pool_data.this_worker->enqueue_local(task); + } + + const auto next_worker = m_round_robin_cursor.fetch_add(1, std::memory_order_relaxed) % m_workers.size(); + m_workers[next_worker].enqueue_foreign(task); +} + +void thread_pool_executor::enqueue(std::span> tasks) { + if (details::s_tl_thread_pool_data.this_worker != nullptr) { + return details::s_tl_thread_pool_data.this_worker->enqueue_local(tasks); + } + + if (tasks.size() < m_workers.size() * 2) { + for (auto task : tasks) { + enqueue(task); + } + + return; + } + + const auto approx_bulk_size = static_cast(tasks.size()) / static_cast(m_workers.size()); + const auto bulk_size = static_cast(std::ceil(approx_bulk_size)); + + size_t worker_index = 0; + auto cursor = tasks.data(); + const auto absolute_end = tasks.data() + tasks.size(); + + while (cursor < absolute_end) { + auto end = (cursor + bulk_size > absolute_end) ? absolute_end : (cursor + bulk_size); + std::span> range = {cursor, end}; + m_workers[worker_index].enqueue_foreign(range); + cursor += bulk_size; + ++worker_index; + } +} + +int thread_pool_executor::max_concurrency_level() const noexcept { + return static_cast(m_workers.size()); +} + +bool thread_pool_executor::shutdown_requested() const noexcept { + return m_abort.load(std::memory_order_relaxed); +} + +void concurrencpp::thread_pool_executor::shutdown() noexcept { + const auto abort = m_abort.exchange(true, std::memory_order_relaxed); + if (abort) { + return; // shutdown had been called before. + } + + for (auto& worker : m_workers) { + worker.abort(); + worker.join(); + } +} diff --git a/source/executors/worker_thread_executor.cpp b/source/executors/worker_thread_executor.cpp new file mode 100644 index 00000000..8a25ccba --- /dev/null +++ b/source/executors/worker_thread_executor.cpp @@ -0,0 +1,163 @@ +#include "concurrencpp/executors/worker_thread_executor.h" +#include "concurrencpp/executors/constants.h" + +#include "concurrencpp/errors.h" + +namespace concurrencpp::details { + static thread_local worker_thread_executor* s_tl_this_worker = nullptr; +} + +using concurrencpp::worker_thread_executor; + +worker_thread_executor::worker_thread_executor() : + derivable_executor(details::consts::k_worker_thread_executor_name), m_private_atomic_abort(false), m_abort(false), + m_atomic_abort(false) { + m_thread = details::thread(details::make_executor_worker_name(name), [this] { + work_loop(); + }); +} + +worker_thread_executor::~worker_thread_executor() noexcept { + assert(!m_thread.joinable()); +} + +void worker_thread_executor::destroy_tasks() noexcept { + std::unique_lock lock(m_lock); + for (auto task : m_private_queue) { + task.destroy(); + } + + m_private_queue.clear(); + + for (auto task : m_public_queue) { + task.destroy(); + } + + m_public_queue.clear(); +} + +bool worker_thread_executor::drain_queue_impl() { + while (!m_private_queue.empty()) { + auto task = m_private_queue.front(); + m_private_queue.pop_front(); + + if (m_private_atomic_abort.load(std::memory_order_relaxed)) { + return false; + } + + task(); + } + + return true; +} + +bool worker_thread_executor::drain_queue() { + std::unique_lock lock(m_lock); + m_condition.wait(lock, [this] { + return !m_public_queue.empty() || m_abort; + }); + + if (m_abort) { + return false; + } + + assert(m_private_queue.empty()); + std::swap(m_private_queue, m_public_queue); // reuse underlying allocations. + lock.unlock(); + + return drain_queue_impl(); +} + +void worker_thread_executor::work_loop() noexcept { + details::s_tl_this_worker = this; + + while (true) { + if (!drain_queue()) { + return; + } + } +} + +void worker_thread_executor::enqueue_local(std::experimental::coroutine_handle<> task) { + if (m_private_atomic_abort.load(std::memory_order_relaxed)) { + details::throw_executor_shutdown_exception(name); + } + + m_private_queue.emplace_back(task); +} + +void worker_thread_executor::enqueue_local(std::span> tasks) { + if (m_private_atomic_abort.load(std::memory_order_relaxed)) { + details::throw_executor_shutdown_exception(name); + } + + m_private_queue.insert(m_private_queue.end(), tasks.begin(), tasks.end()); +} + +void worker_thread_executor::enqueue_foreign(std::experimental::coroutine_handle<> task) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + m_public_queue.emplace_back(task); + lock.unlock(); + + m_condition.notify_one(); +} + +void worker_thread_executor::enqueue_foreign(std::span> tasks) { + std::unique_lock lock(m_lock); + if (m_abort) { + details::throw_executor_shutdown_exception(name); + } + + m_public_queue.insert(m_public_queue.end(), tasks.begin(), tasks.end()); + lock.unlock(); + + m_condition.notify_one(); +} + +void worker_thread_executor::enqueue(std::experimental::coroutine_handle<> task) { + if (details::s_tl_this_worker == this) { + return enqueue_local(task); + } + + enqueue_foreign(task); +} + +void worker_thread_executor::enqueue(std::span> tasks) { + if (details::s_tl_this_worker == this) { + return enqueue_local(tasks); + } + + enqueue_foreign(tasks); +} + +int worker_thread_executor::max_concurrency_level() const noexcept { + return details::consts::k_worker_thread_max_concurrency_level; +} + +bool concurrencpp::worker_thread_executor::shutdown_requested() const noexcept { + return m_atomic_abort.load(std::memory_order_relaxed); +} + +void worker_thread_executor::shutdown() noexcept { + const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); + if (abort) { + return; // shutdown had been called before. + } + + assert(m_private_atomic_abort.load(std::memory_order_relaxed) == false); + m_private_atomic_abort.store(true, std::memory_order_relaxed); + + { + std::unique_lock lock(m_lock); + m_abort = true; + } + + m_condition.notify_one(); + m_thread.join(); + + destroy_tasks(); +} diff --git a/source/results/promises.cpp b/source/results/promises.cpp new file mode 100644 index 00000000..84b5c120 --- /dev/null +++ b/source/results/promises.cpp @@ -0,0 +1,13 @@ +#include "concurrencpp/results/promises.h" + +using concurrencpp::details::coroutine_per_thread_data; + +thread_local coroutine_per_thread_data coroutine_per_thread_data::s_tl_per_thread_data; + +void concurrencpp::details::initial_accumulating_awaiter::await_suspend(std::experimental::coroutine_handle<> handle) const noexcept { + auto& per_thread_data = coroutine_per_thread_data::s_tl_per_thread_data; + auto accumulator = std::exchange(per_thread_data.accumulator, nullptr); + + assert(accumulator != nullptr); + accumulator->push_back(handle); +} diff --git a/source/results/result_core.cpp b/source/results/result_core.cpp new file mode 100644 index 00000000..efc77b58 --- /dev/null +++ b/source/results/result_core.cpp @@ -0,0 +1,179 @@ +#include "concurrencpp/results/result_core.h" +#include "concurrencpp/executors/executor.h" + +using concurrencpp::details::wait_context; +using concurrencpp::details::await_context; +using concurrencpp::details::result_core_base; + +void wait_context::wait() noexcept { + std::unique_lock lock(m_lock); + m_condition.wait(lock, [this] { + return m_ready; + }); +} + +bool wait_context::wait_for(size_t milliseconds) noexcept { + std::unique_lock lock(m_lock); + return m_condition.wait_for(lock, std::chrono::milliseconds(milliseconds), [this] { + return m_ready; + }); +} + +void wait_context::notify() noexcept { + { + std::unique_lock lock(m_lock); + m_ready = true; + } + + m_condition.notify_all(); +} + +void result_core_base::wait() { + const auto state = m_pc_state.load(std::memory_order_acquire); + if (state == pc_state::producer) { + return; + } + + auto wait_ctx = std::make_shared(); + + assert_consumer_idle(); + m_consumer.emplace<3>(wait_ctx); + + auto expected_state = pc_state::idle; + const auto idle = m_pc_state.compare_exchange_strong(expected_state, pc_state::consumer, std::memory_order_acq_rel); + + if (!idle) { + assert_done(); + return; + } + + wait_ctx->wait(); + assert_done(); +} + +bool result_core_base::await(std::experimental::coroutine_handle<> caller_handle) noexcept { + const auto state = m_pc_state.load(std::memory_order_acquire); + if (state == pc_state::producer) { + return false; // don't suspend + } + + assert_consumer_idle(); + m_consumer.emplace<1>(caller_handle); + + auto expected_state = pc_state::idle; + const auto idle = m_pc_state.compare_exchange_strong(expected_state, pc_state::consumer, std::memory_order_acq_rel); + + if (!idle) { + assert_done(); + } + + return idle; // if idle = true, suspend +} + +bool result_core_base::await_via(std::shared_ptr executor, std::experimental::coroutine_handle<> caller_handle, bool force_rescheduling) { + assert(static_cast(executor)); + auto handle_done_state = [this](await_context& await_ctx, bool force_rescheduling) -> bool { + assert_done(); + + if (!force_rescheduling) { + return false; // resume caller. + } + + await_ctx.first->enqueue(await_ctx.second); + return true; + }; + + const auto state = m_pc_state.load(std::memory_order_acquire); + if (state == pc_state::producer) { + await_context await_ctx(std::move(executor), caller_handle); + return handle_done_state(await_ctx, force_rescheduling); + } + + assert_consumer_idle(); + m_consumer.emplace<2>(std::move(executor), caller_handle); + + auto expected_state = pc_state::idle; + const auto idle = m_pc_state.compare_exchange_strong(expected_state, pc_state::consumer, std::memory_order_acq_rel); + + if (idle) { + return true; + } + + // the result is available + auto await_ctx = std::move(std::get<2>(m_consumer)); + return handle_done_state(await_ctx, force_rescheduling); +} + +void result_core_base::when_all(std::shared_ptr when_all_state) noexcept { + const auto state = m_pc_state.load(std::memory_order_acquire); + if (state == pc_state::producer) { + return when_all_state->on_result_ready(); + } + + assert_consumer_idle(); + m_consumer.emplace<4>(std::move(when_all_state)); + + auto expected_state = pc_state::idle; + const auto idle = m_pc_state.compare_exchange_strong(expected_state, pc_state::consumer, std::memory_order_acq_rel); + + if (idle) { + return; + } + + assert_done(); + auto& state_ptr = std::get<4>(m_consumer); + state_ptr->on_result_ready(); +} + +concurrencpp::details::when_any_status result_core_base::when_any(std::shared_ptr when_any_state, size_t index) noexcept { + const auto state = m_pc_state.load(std::memory_order_acquire); + if (state == pc_state::producer) { + return when_any_status::result_ready; + } + + assert_consumer_idle(); + m_consumer.emplace<5>(std::move(when_any_state), index); + + auto expected_state = pc_state::idle; + const auto idle = m_pc_state.compare_exchange_strong(expected_state, pc_state::consumer, std::memory_order_acq_rel); + + if (idle) { + return when_any_status::set; + } + + // no need to continue the iteration of when_any, we've found a ready task. + // tell all predecessors to rewind their state. + assert_done(); + return when_any_status::result_ready; +} + +void result_core_base::try_rewind_consumer() noexcept { + const auto pc_state = this->m_pc_state.load(std::memory_order_acquire); + if (pc_state != pc_state::consumer) { + return; + } + + auto expected_consumer_state = pc_state::consumer; + const auto consumer = this->m_pc_state.compare_exchange_strong(expected_consumer_state, pc_state::idle, std::memory_order_acq_rel); + + if (!consumer) { + assert_done(); + return; + } + + m_consumer.emplace<0>(); +} + +void result_core_base::schedule_coroutine(concurrencpp::executor& executor, std::experimental::coroutine_handle<> coro_handle) { + assert(static_cast(coro_handle)); + assert(!coro_handle.done()); + executor.enqueue(coro_handle); +} + +void result_core_base::schedule_coroutine(await_context& await_ctx) { + auto executor = await_ctx.first.get(); + auto coro_handle = await_ctx.second; + + assert(executor != nullptr); + schedule_coroutine(*executor, coro_handle); +} diff --git a/source/runtime/runtime.cpp b/source/runtime/runtime.cpp new file mode 100644 index 00000000..7feabba4 --- /dev/null +++ b/source/runtime/runtime.cpp @@ -0,0 +1,122 @@ +#include "concurrencpp/runtime/runtime.h" +#include "concurrencpp/runtime/constants.h" + +#include "concurrencpp/executors/constants.h" +#include "concurrencpp/executors/inline_executor.h" +#include "concurrencpp/executors/thread_pool_executor.h" +#include "concurrencpp/executors/thread_executor.h" +#include "concurrencpp/executors/worker_thread_executor.h" +#include "concurrencpp/executors/manual_executor.h" + +#include "concurrencpp/timers/timer_queue.h" + +namespace concurrencpp::details { + size_t default_max_cpu_workers() noexcept { + return static_cast(thread::hardware_concurrency() * consts::k_cpu_threadpool_worker_count_factor); + } + + size_t default_max_background_workers() noexcept { + return static_cast(thread::hardware_concurrency() * consts::k_background_threadpool_worker_count_factor); + } + + constexpr static auto k_default_max_worker_wait_time = std::chrono::seconds(consts::k_max_worker_waiting_time_sec); +} // namespace concurrencpp::details + +using concurrencpp::runtime; +using concurrencpp::runtime_options; +using concurrencpp::details::executor_collection; + +/* + executor_collection; +*/ + +void executor_collection::register_executor(std::shared_ptr executor) { + std::unique_lock lock(m_lock); + assert(std::find(m_executors.begin(), m_executors.end(), executor) == m_executors.end()); + m_executors.emplace_back(std::move(executor)); +} + +void executor_collection::shutdown_all() noexcept { + std::unique_lock lock(m_lock); + for (auto& executor : m_executors) { + assert(static_cast(executor)); + executor->shutdown(); + } + + m_executors = {}; +} + +/* + runtime_options +*/ + +runtime_options::runtime_options() noexcept : + max_cpu_threads(details::default_max_cpu_workers()), max_cpu_thread_waiting_time(details::k_default_max_worker_wait_time), + max_background_threads(details::default_max_background_workers()), max_background_thread_waiting_time(details::k_default_max_worker_wait_time) {} + +/* + runtime +*/ + +runtime::runtime() : runtime(runtime_options()) {} + +runtime::runtime(const runtime_options& options) { + m_timer_queue = std::make_shared<::concurrencpp::timer_queue>(); + + m_inline_executor = std::make_shared<::concurrencpp::inline_executor>(); + m_registered_executors.register_executor(m_inline_executor); + + m_thread_pool_executor = std::make_shared<::concurrencpp::thread_pool_executor>(details::consts::k_thread_pool_executor_name, + options.max_cpu_threads, + options.max_cpu_thread_waiting_time); + m_registered_executors.register_executor(m_thread_pool_executor); + + m_background_executor = std::make_shared<::concurrencpp::thread_pool_executor>(details::consts::k_background_executor_name, + options.max_background_threads, + options.max_background_thread_waiting_time); + m_registered_executors.register_executor(m_background_executor); + + m_thread_executor = std::make_shared<::concurrencpp::thread_executor>(); + m_registered_executors.register_executor(m_thread_executor); +} + +concurrencpp::runtime::~runtime() noexcept { + m_timer_queue->shutdown(); + m_registered_executors.shutdown_all(); +} + +std::shared_ptr runtime::timer_queue() const noexcept { + return m_timer_queue; +} + +std::shared_ptr runtime::inline_executor() const noexcept { + return m_inline_executor; +} + +std::shared_ptr runtime::thread_pool_executor() const noexcept { + return m_thread_pool_executor; +} + +std::shared_ptr runtime::background_executor() const noexcept { + return m_background_executor; +} + +std::shared_ptr runtime::thread_executor() const noexcept { + return m_thread_executor; +} + +std::shared_ptr runtime::make_worker_thread_executor() { + auto executor = std::make_shared(); + m_registered_executors.register_executor(executor); + return executor; +} + +std::shared_ptr runtime::make_manual_executor() { + auto executor = std::make_shared(); + m_registered_executors.register_executor(executor); + return executor; +} + +std::tuple runtime::version() noexcept { + return {details::consts::k_concurrencpp_version_major, details::consts::k_concurrencpp_version_minor, details::consts::k_concurrencpp_version_revision}; +} diff --git a/source/threads/thread.cpp b/source/threads/thread.cpp new file mode 100644 index 00000000..27bbd249 --- /dev/null +++ b/source/threads/thread.cpp @@ -0,0 +1,71 @@ +#include "concurrencpp/threads/thread.h" + +#include "concurrencpp/platform_defs.h" + +#include + +#include "concurrencpp/runtime/constants.h" + +using concurrencpp::details::thread; + +namespace concurrencpp::details { + std::uintptr_t generate_thread_id() noexcept { + static std::atomic_uintptr_t s_id_seed = 1; + return s_id_seed.fetch_add(1, std::memory_order_relaxed); + } + + struct thread_per_thread_data { + const std::uintptr_t id = generate_thread_id(); + }; + + static thread_local thread_per_thread_data s_tl_thread_per_data; +} // namespace concurrencpp::details + +std::thread::id thread::get_id() const noexcept { + return m_thread.get_id(); +} + +std::uintptr_t thread::get_current_virtual_id() noexcept { + return s_tl_thread_per_data.id; +} + +bool thread::joinable() const noexcept { + return m_thread.joinable(); +} + +void thread::join() { + m_thread.join(); +} + +size_t thread::hardware_concurrency() noexcept { + const auto hc = std::thread::hardware_concurrency(); + return (hc != 0) ? hc : consts::k_default_number_of_cores; +} + +#ifdef CRCPP_WIN_OS + +# include + +void thread::set_name(std::string_view name) noexcept { + const std::wstring utf16_name(name.begin(), + name.end()); // concurrencpp strings are always ASCII (english only) + ::SetThreadDescription(::GetCurrentThread(), utf16_name.data()); +} + +#elif defined(CRCPP_UNIX_OS) + +# include + +void thread::set_name(std::string_view name) noexcept { + ::pthread_setname_np(::pthread_self(), name.data()); +} + +#elif defined(CRCPP_MACH_OS) + +# include + +void thread::set_name(std::string_view name) noexcept { + ::pthread_setname_np(name.data()); +} + +#endif diff --git a/source/timers/timer.cpp b/source/timers/timer.cpp new file mode 100644 index 00000000..862235f0 --- /dev/null +++ b/source/timers/timer.cpp @@ -0,0 +1,99 @@ +#include "concurrencpp/timers/timer.h" +#include "concurrencpp/timers/timer_queue.h" +#include "concurrencpp/timers/constants.h" + +#include "concurrencpp/errors.h" +#include "concurrencpp/results/result.h" +#include "concurrencpp/executors/executor.h" + +using concurrencpp::timer; +using concurrencpp::details::timer_state; +using concurrencpp::details::timer_state_base; + +timer_state_base::timer_state_base(size_t due_time, + size_t frequency, + std::shared_ptr executor, + std::weak_ptr timer_queue, + bool is_oneshot) noexcept : + m_timer_queue(std::move(timer_queue)), + m_executor(std::move(executor)), m_due_time(due_time), m_frequency(frequency), m_deadline(make_deadline(milliseconds(due_time))), m_is_oneshot(is_oneshot) { + assert(static_cast(m_executor)); +} + +void timer_state_base::fire() { + const auto frequency = m_frequency.load(std::memory_order_relaxed); + m_deadline = make_deadline(milliseconds(frequency)); + + assert(static_cast(m_executor)); + + m_executor->post([self = shared_from_this()]() mutable { + self->execute(); + }); +} + +timer::timer(std::shared_ptr timer_impl) noexcept : m_state(std::move(timer_impl)) {} + +timer::~timer() noexcept { + cancel(); +} + +void timer::throw_if_empty(const char* error_message) const { + if (static_cast(m_state)) { + return; + } + + throw concurrencpp::errors::empty_timer(error_message); +} + +std::chrono::milliseconds timer::get_due_time() const { + throw_if_empty(details::consts::k_timer_empty_get_due_time_err_msg); + return std::chrono::milliseconds(m_state->get_due_time()); +} + +std::chrono::milliseconds timer::get_frequency() const { + throw_if_empty(details::consts::k_timer_empty_get_frequency_err_msg); + return std::chrono::milliseconds(m_state->get_frequency()); +} + +std::shared_ptr timer::get_executor() const { + throw_if_empty(details::consts::k_timer_empty_get_executor_err_msg); + return m_state->get_executor(); +} + +std::weak_ptr timer::get_timer_queue() const { + throw_if_empty(details::consts::k_timer_empty_get_timer_queue_err_msg); + return m_state->get_timer_queue(); +} + +void timer::cancel() { + if (!static_cast(m_state)) { + return; + } + + auto state = std::move(m_state); + auto timer_queue = state->get_timer_queue().lock(); + + if (!static_cast(timer_queue)) { + return; + } + + timer_queue->remove_timer(std::move(state)); +} + +void timer::set_frequency(std::chrono::milliseconds new_frequency) { + throw_if_empty(details::consts::k_timer_empty_set_frequency_err_msg); + return m_state->set_new_frequency(new_frequency.count()); +} + +timer& timer::operator=(timer&& rhs) noexcept { + if (this == &rhs) { + return *this; + } + + if (static_cast(*this)) { + cancel(); + } + + m_state = std::move(rhs.m_state); + return *this; +} diff --git a/concurrencpp/src/timers/timer_queue.cpp b/source/timers/timer_queue.cpp similarity index 76% rename from concurrencpp/src/timers/timer_queue.cpp rename to source/timers/timer_queue.cpp index 228fb355..ff0c435a 100644 --- a/concurrencpp/src/timers/timer_queue.cpp +++ b/source/timers/timer_queue.cpp @@ -1,7 +1,7 @@ -#include "timer_queue.h" -#include "timer.h" +#include "concurrencpp/timers/timer_queue.h" +#include "concurrencpp/timers/timer.h" -#include "../results/result.h" +#include "concurrencpp/results/result.h" #include #include @@ -22,7 +22,7 @@ using request_queue = timer_queue::request_queue; namespace concurrencpp::details { struct deadline_comparator { bool operator()(const timer_ptr& a, const timer_ptr& b) const noexcept { - return a->get_deadline() < b->get_deadline(); + return a->get_deadline() < b->get_deadline(); } }; @@ -31,7 +31,7 @@ namespace concurrencpp::details { using timer_set_iterator = typename timer_set::iterator; using iterator_map = std::unordered_map; - private: + private: timer_set m_timers; iterator_map m_iterator_mapper; @@ -44,7 +44,8 @@ namespace concurrencpp::details { void remove_timer_internal(timer_ptr existing_timer) { auto timer_it = m_iterator_mapper.find(existing_timer); if (timer_it == m_iterator_mapper.end()) { - assert(existing_timer->is_oneshot()); //the timer was already deleted by the queue when it was fired. + assert(existing_timer->is_oneshot()); // the timer was already deleted by + // the queue when it was fired. return; } @@ -60,8 +61,7 @@ namespace concurrencpp::details { if (opt == timer_request::add) { add_timer_internal(std::move(timer_ptr)); - } - else { + } else { remove_timer_internal(std::move(timer_ptr)); } } @@ -75,7 +75,7 @@ namespace concurrencpp::details { std::swap(m_iterator_mapper, iterator_mapper); } - public: + public: bool empty() const noexcept { assert(m_iterator_mapper.size() == m_timers.size()); return m_timers.empty(); @@ -93,33 +93,37 @@ namespace concurrencpp::details { timer_set temp_set; - auto first_timer_it = m_timers.begin(); //closest deadline + auto first_timer_it = m_timers.begin(); // closest deadline auto timer_ptr = *first_timer_it; const auto is_oneshot = timer_ptr->is_oneshot(); if (!timer_ptr->expired(now)) { - //if this timer is not expired, the next ones are guaranteed not to, as the set is ordered by deadlines. + // if this timer is not expired, the next ones are guaranteed not to, as + // the set is ordered by deadlines. break; } - //we are going to modify the timer, so first we extract it + // we are going to modify the timer, so first we extract it auto timer_node = m_timers.extract(first_timer_it); - //we cannot use the naked node_handle according to the standard. it must be contained somewhere. + // we cannot use the naked node_handle according to the standard. it must + // be contained somewhere. auto temp_it = temp_set.insert(std::move(timer_node)); (*temp_it)->fire(); if (is_oneshot) { m_iterator_mapper.erase(timer_ptr); - continue; //let the timer die inside the temp_set + continue; // let the timer die inside the temp_set } - //regular timer, re-insert into the right position + // regular timer, re-insert into the right position timer_node = temp_set.extract(temp_it); auto new_it = m_timers.insert(std::move(timer_node)); - assert(m_iterator_mapper.contains(timer_ptr)); - m_iterator_mapper[timer_ptr] = new_it; //update the iterator map, multiset::extract invalidates the timer + // AppleClang doesn't have std::unordered_map::contains yet + assert(m_iterator_mapper.find(timer_ptr) != m_iterator_mapper.end()); + m_iterator_mapper[timer_ptr] = new_it; // update the iterator map, multiset::extract invalidates the + // timer } if (m_timers.empty()) { @@ -127,15 +131,13 @@ namespace concurrencpp::details { return now + std::chrono::hours(24); } - //get the closest deadline. + // get the closest deadline. return (**m_timers.begin()).get_deadline(); } }; -} +} // namespace concurrencpp::details -timer_queue::timer_queue() noexcept : - m_atomic_abort(false), - m_abort(false) {} +timer_queue::timer_queue() noexcept : m_atomic_abort(false), m_abort(false) {} timer_queue::~timer_queue() noexcept { shutdown(); @@ -169,8 +171,7 @@ void timer_queue::work_loop() noexcept { m_condition.wait(lock, [this] { return !m_request_queue.empty() || m_abort; }); - } - else { + } else { m_condition.wait_until(lock, next_deadline, [this] { return !m_request_queue.empty() || m_abort; }); @@ -197,21 +198,21 @@ bool timer_queue::shutdown_requested() const noexcept { void timer_queue::shutdown() noexcept { const auto state_before = m_atomic_abort.exchange(true, std::memory_order_relaxed); - if (state_before) { - return; //timer_queue has been shut down already. - } + if (state_before) { + return; // timer_queue has been shut down already. + } std::unique_lock lock(m_lock); m_abort = true; - if (!m_worker.joinable()) { - return; //nothing to shut down + if (!m_worker.joinable()) { + return; // nothing to shut down } m_request_queue.clear(); lock.unlock(); - m_condition.notify_all(); + m_condition.notify_all(); m_worker.join(); } @@ -235,12 +236,9 @@ concurrencpp::result timer_queue::make_delay_object(std::chrono::milliseco concurrencpp::result_promise promise; auto task = promise.get_result(); - make_timer_impl( - due_time.count(), - 0, - std::move(executor), - true, - [tcs = std::move(promise)]() mutable { tcs.set_result(); }); + make_timer_impl(due_time.count(), 0, std::move(executor), true, [tcs = std::move(promise)]() mutable { + tcs.set_result(); + }); return task; -} \ No newline at end of file +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..c8edbc87 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,138 @@ +cmake_minimum_required(VERSION 3.16) + +project(concurrencppTests LANGUAGES CXX) + +include(../cmake/coroutineOptions.cmake) + +# ---- Add root project ---- + +option(ENABLE_THREAD_SANITIZER "\ +Build concurrencpp with LLVM thread sanitizer. \ +Does not have an effect if the compiler is not Clang based." OFF) + +if(ENABLE_THREAD_SANITIZER AND CXX_COMPILER_ID MATCHES "Clang") + # Instead of polluting the lists file, we inject a command definition + # override that will apply the sanitizer flag + set(CMAKE_PROJECT_concurrencpp_INCLUDE + "${CMAKE_CURRENT_LIST_DIR}/../cmake/concurrencppInjectTSAN.cmake" + CACHE INTERNAL "") +endif() + +# Enable warnings from includes +set(concurrencpp_INCLUDE_WITHOUT_SYSTEM ON CACHE INTERNAL "") + +include(FetchContent) +FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +FetchContent_MakeAvailable(concurrencpp) + +# ---- Test ---- + +enable_testing() + +# TODO: remove this once tests were properly fixed +set(test_sources + source/helpers/assertions.cpp + source/helpers/object_observer.cpp + source/main.cpp + source/tester/tester.cpp + source/tests/all_tests.cpp + source/tests/coroutine_tests/coroutine_promises_test.cpp + source/tests/coroutine_tests/coroutines_tests.cpp + source/tests/executor_tests/inline_executor_tests.cpp + source/tests/executor_tests/manual_executor_tests.cpp + source/tests/executor_tests/thread_executor_tests.cpp + source/tests/executor_tests/thread_pool_executor_tests.cpp + source/tests/executor_tests/worker_thread_executor_tests.cpp + source/tests/result_tests/make_result_tests.cpp + source/tests/result_tests/result_await_tests.cpp + source/tests/result_tests/result_promise_tests.cpp + source/tests/result_tests/result_resolve_tests.cpp + source/tests/result_tests/result_tests.cpp + source/tests/result_tests/when_all_tests.cpp + source/tests/result_tests/when_any_tests.cpp + source/tests/runtime_tests.cpp + source/tests/timer_tests/timer_queue_tests.cpp + source/tests/timer_tests/timer_tests.cpp) + +set(test_headers + include/helpers/assertions.h + include/helpers/object_observer.h + include/helpers/random.h + include/helpers/result_generator.h + include/helpers/thread_helpers.h + include/tester/tester.h + include/tests/all_tests.h + include/tests/executor_tests/executor_test_helpers.h + include/tests/test_utils/executor_shutdowner.h + include/tests/test_utils/proxy_coro.h + include/tests/test_utils/result_factory.h + include/tests/test_utils/test_executors.h + include/tests/test_utils/test_ready_result.h) + +function(temp_test NAMESPACE NAME) + set(target ${NAMESPACE}_${NAME}) + add_executable(${target} ${ARGN}) + target_link_libraries(${target} PRIVATE concurrencpp::concurrencpp) + target_compile_features(${target} PRIVATE cxx_std_20) + target_coroutine_options(${target}) + target_include_directories(${target} + PRIVATE + "$") + add_test(NAME ${NAMESPACE}.${NAME} COMMAND ${target}) + set_property(TEST ${NAMESPACE}.${NAME} PROPERTY RUN_SERIAL YES) +endfunction() + +temp_test(dummy main ${test_headers} ${test_sources}) + +if(NOT ENABLE_THREAD_SANITIZER) + return() +endif() + +set(tsan_sources executors fibbonacci matrix_multiplication quick_sort result when_all when_any) +foreach(source IN LISTS tsan_sources) + temp_test(dummy tsan_${source} ${test_headers} source/thread_sanitizer/${source}.cpp) +endforeach() + +return() + +# add_test(NAMESPACE NAME [PROPERTIES ...]) +# +# Add test with the name . with the source file at +# source//.cpp with project specific options that outputs the +# exectuable _. +# +# Additional properties may be forwarded to `set_tests_properties` using the +# PROPERTIES arguments. +# See: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-tests +# +function(add_test) + cmake_parse_arguments(TEST "" "NAMESPACE;NAME" "PROPERTIES" ${ARGN}) + + set(target "${TEST_NAMESPACE}_${TEST_NAME}") + set(test_name "${TEST_NAMESPACE}.${TEST_NAME}") + + add_executable(${target} "source/${TEST_NAMESPACE}/${TEST_NAME}.cpp") + + target_link_libraries(${target} PRIVATE concurrencpp::concurrencpp) + + target_compile_features(${target} PRIVATE cxx_std_20) + + target_coroutine_options(${target}) + + target_include_directories(${target} PRIVATE "${PROJECT_SOURCE_DIR}/include") + + # Call the original add_test + _add_test(NAME ${test_name} COMMAND ${target}) + + if(TEST_PROPERTIES) + set_tests_properties(${test_name} PROPERTIES ${TEST_PROPERTIES}) + endif() +endfunction() + +# TODO: add tests + +if(NOT ENABLE_THREAD_SANITIZER) + return() +endif() + +# TODO: add thread sanitizer tests diff --git a/test/include/helpers/assertions.h b/test/include/helpers/assertions.h new file mode 100644 index 00000000..edf42062 --- /dev/null +++ b/test/include/helpers/assertions.h @@ -0,0 +1,128 @@ +#ifndef CONCURRENCPP_ASSERTIONS_H +#define CONCURRENCPP_ASSERTIONS_H + +#include +#include +#include + +namespace concurrencpp::tests::details { + std::string to_string(bool value); + std::string to_string(int value); + std::string to_string(long value); + std::string to_string(long long value); + std::string to_string(unsigned value); + std::string to_string(unsigned long value); + std::string to_string(unsigned long long value); + std::string to_string(float value); + std::string to_string(double value); + std::string to_string(long double value); + const std::string& to_string(const std::string& str); + std::string to_string(const char* str); + std::string to_string(const std::string_view str); + std::string to_string(std::thread::id); + std::string to_string(std::chrono::time_point time_point); + + template + std::string to_string(type* value) { + return std::string("pointer[") + to_string(reinterpret_cast(value)) + "]"; + } + + template + std::string to_string(const type&) { + return "{object}"; + } + + void assert_same_failed_impl(const std::string& a, const std::string& b); + void assert_not_same_failed_impl(const std::string& a, const std::string& b); + void assert_bigger_failed_impl(const std::string& a, const std::string& b); + void assert_smaller_failed_impl(const std::string& a, const std::string& b); + void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b); + void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b); +} // namespace concurrencpp::tests::details + +namespace concurrencpp::tests { + void assert_true(bool condition); + void assert_false(bool condition); + + template + void assert_equal(const a_type& given_value, const b_type& expected_value) { + if (given_value == expected_value) { + return; + } + + details::assert_same_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + inline void assert_not_equal(const a_type& given_value, const b_type& expected_value) { + if (given_value != expected_value) { + return; + } + + details::assert_same_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + void assert_bigger(const a_type& given_value, const b_type& expected_value) { + if (given_value > expected_value) { + return; + } + + details::assert_bigger_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + void assert_smaller(const a_type& given_value, const b_type& expected_value) { + if (given_value < expected_value) { + return; + } + + details::assert_smaller_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + void assert_bigger_equal(const a_type& given_value, const b_type& expected_value) { + if (given_value >= expected_value) { + return; + } + + details::assert_bigger_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + void assert_smaller_equal(const a_type& given_value, const b_type& expected_value) { + if (given_value <= expected_value) { + return; + } + + details::assert_smaller_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); + } + + template + void assert_throws(task_type&& task) { + try { + task(); + } catch (const exception_type&) { + return; + } catch (...) { + } + + assert_false(true); + } + + template + void assert_throws_with_error_message(task_type&& task, std::string_view error_msg) { + try { + task(); + } catch (const exception_type& e) { + assert_equal(error_msg, e.what()); + return; + } catch (...) { + } + + assert_false(true); + } + +} // namespace concurrencpp::tests + +#endif // CONCURRENCPP_ASSERTIONS_H diff --git a/test/include/helpers/object_observer.h b/test/include/helpers/object_observer.h new file mode 100644 index 00000000..06934c50 --- /dev/null +++ b/test/include/helpers/object_observer.h @@ -0,0 +1,82 @@ +#ifndef CONCURRENCPP_OBJECT_OBSERVER_H +#define CONCURRENCPP_OBJECT_OBSERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace concurrencpp::tests { + + namespace details { + class object_observer_state; + } + + class testing_stub { + + protected: + std::shared_ptr m_state; + const std::chrono::milliseconds m_dummy_work_time; + + public: + testing_stub() noexcept : m_dummy_work_time(0) {} + + testing_stub(std::shared_ptr state, const std::chrono::milliseconds dummy_work_time) noexcept : + m_state(std::move(state)), m_dummy_work_time(dummy_work_time) {} + + testing_stub(testing_stub&& rhs) noexcept = default; + + ~testing_stub() noexcept; + + testing_stub& operator=(testing_stub&& rhs) noexcept; + + void operator()() noexcept; + }; + + class value_testing_stub : public testing_stub { + + private: + const size_t m_return_value; + + public: + value_testing_stub(size_t return_value) noexcept : m_return_value(return_value) {} + + value_testing_stub(std::shared_ptr state, const std::chrono::milliseconds dummy_work_time, int return_value) noexcept : + testing_stub(std::move(state), dummy_work_time), m_return_value(return_value) {} + + value_testing_stub(value_testing_stub&& rhs) noexcept = default; + + value_testing_stub& operator=(value_testing_stub&& rhs) noexcept; + + size_t operator()() noexcept; + }; + + class object_observer { + + private: + const std::shared_ptr m_state; + + public: + object_observer(); + object_observer(object_observer&&) noexcept = default; + + testing_stub get_testing_stub(std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; + value_testing_stub get_testing_stub(int value, std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; + value_testing_stub get_testing_stub(size_t value, std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; + + bool wait_execution_count(size_t count, std::chrono::milliseconds timeout); + bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout); + + size_t get_destruction_count() const noexcept; + size_t get_execution_count() const noexcept; + + std::unordered_map get_execution_map() const noexcept; + }; + +} // namespace concurrencpp::tests + +#endif // !CONCURRENCPP_BEHAVIOURAL_TESTERS_H diff --git a/test/include/helpers/random.h b/test/include/helpers/random.h new file mode 100644 index 00000000..eee818c9 --- /dev/null +++ b/test/include/helpers/random.h @@ -0,0 +1,29 @@ +#ifndef CONCURRENCPP_RANDOM_H +#define CONCURRENCPP_RANDOM_H + +#include + +namespace concurrencpp::tests { + class random { + + private: + std::random_device rd; + std::mt19937 mt; + std::uniform_int_distribution dist; + + public: + random() noexcept : mt(rd()), dist(-1'000'000, 1'000'000) {} + + intptr_t operator()() { + return dist(mt); + } + + int64_t operator()(int64_t min, int64_t max) { + const auto r = (*this)(); + const auto upper_limit = max - min + 1; + return std::abs(r) % upper_limit + min; + } + }; +} // namespace concurrencpp::tests + +#endif // CONCURRENCPP_RANDOM_H diff --git a/test/include/helpers/result_generator.h b/test/include/helpers/result_generator.h new file mode 100644 index 00000000..b86c5bab --- /dev/null +++ b/test/include/helpers/result_generator.h @@ -0,0 +1,39 @@ +#ifndef CONCURRENCPP_RESULT_GENERATOR_H +#define CONCURRENCPP_RESULT_GENERATOR_H + +#include "fast_randomizer.h" + +#include + +namespace concurrencpp::tests { + + template + class result_generator {}; + + template<> + class result_generator { + + private: + random m_rand; + + public: + int operator()() noexcept { + return static_cast(m_rand()); + } + }; + + template<> + class result_generator { + + private: + random m_rand; + + public: + std::string operator()() noexcept { + return std::string("123456#") + std::to_string(m_rand()); + } + }; + +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/helpers/thread_helpers.h b/test/include/helpers/thread_helpers.h new file mode 100644 index 00000000..692934fe --- /dev/null +++ b/test/include/helpers/thread_helpers.h @@ -0,0 +1,128 @@ +#ifndef CONCURRENCPP_THREAD_HELPERS_H +#define CONCURRENCPP_THREAD_HELPERS_H + +#include "concurrencpp.h" + +#include +#include + +#include + +namespace concurrencpp::tests { + class test_listener { + + private: + std::atomic_size_t m_total_created; + std::atomic_size_t m_total_destroyed; + std::atomic_size_t m_total_waiting; + std::atomic_size_t m_total_resuming; + std::atomic_size_t m_total_idling; + + public: + test_listener() noexcept : m_total_created(0), m_total_destroyed(0), m_total_waiting(0), m_total_resuming(0), m_total_idling(0) {} + + virtual ~test_listener() noexcept = default; + + size_t total_created() const noexcept { + return m_total_created.load(); + } + size_t total_destroyed() const noexcept { + return m_total_destroyed.load(); + } + size_t total_waiting() const noexcept { + return m_total_waiting.load(); + } + size_t total_resuming() const noexcept { + return m_total_resuming.load(); + } + size_t total_idling() const noexcept { + return m_total_idling.load(); + } + + /* + virtual void on_thread_created(std::thread::id id) override { + ++m_total_created; + } + + virtual void on_thread_waiting(std::thread::id id) override { + ++m_total_waiting; + } + + virtual void on_thread_resuming(std::thread::id id) override { + ++m_total_resuming; + } + + virtual void on_thread_idling(std::thread::id id) override { + ++m_total_idling; + } + + virtual void on_thread_destroyed(std::thread::id id) override { + ++m_total_destroyed; + } + + void reset() { + m_total_created = 0; + m_total_destroyed = 0; + m_total_waiting = 0; + m_total_resuming = 0; + m_total_idling = 0; + } + */ + }; + + inline std::shared_ptr make_test_listener() { + return std::make_shared(); + } + + class waiter { + + class waiter_impl { + + private: + std::mutex m_lock; + std::condition_variable m_cv; + bool m_unblocked; + + public: + waiter_impl() : m_unblocked(false) {} + + ~waiter_impl() { + notify_all(); + } + + void wait() { + std::unique_lock lock(m_lock); + m_cv.wait(lock, [this] { + return m_unblocked; + }); + + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + void notify_all() { + std::unique_lock lock(m_lock); + m_unblocked = true; + m_cv.notify_all(); + } + }; + + private: + const std::shared_ptr m_impl; + + public: + waiter() : m_impl(std::make_shared()) {} + + waiter(const waiter&) noexcept = default; + + void wait() { + m_impl->wait(); + } + + void notify_all() { + m_impl->notify_all(); + } + }; +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/tester/tester.h b/test/include/tester/tester.h new file mode 100644 index 00000000..cb849b21 --- /dev/null +++ b/test/include/tester/tester.h @@ -0,0 +1,38 @@ +#ifndef CONCURRENCPP_TESTER_H +#define CONCURRENCPP_TESTER_H + +#include +#include + +namespace concurrencpp::tests { + class test_step { + + private: + const char* m_step_name; + std::function m_step; + + public: + test_step(const char* step_name, std::function callable); + + void launch_test_step() noexcept; + }; + + class tester { + + private: + const char* m_test_name; + std::deque m_steps; + + void start_test(); + void end_test(); + + public: + tester(const char* test_name) noexcept; + + void launch_test() noexcept; + void add_step(const char* step_name, std::function callable); + }; + +} // namespace concurrencpp::tests + +#endif // CONCURRENCPP_TESTER_H diff --git a/test/include/tests/all_tests.h b/test/include/tests/all_tests.h new file mode 100644 index 00000000..03a1ec7f --- /dev/null +++ b/test/include/tests/all_tests.h @@ -0,0 +1,31 @@ +#ifndef CONCURRENCPP_ALL_TESTS_H +#define CONCURRENCPP_ALL_TESTS_H + +namespace concurrencpp::tests { + void test_inline_executor(); + void test_thread_pool_executor(); + void test_thread_executor(); + void test_worker_thread_executor(); + void test_manual_executor(); + + void test_result_promise(); + void test_result(); + void test_result_resolve_all(); + void test_result_await_all(); + void test_result_await(); + void test_make_result(); + void test_when_all(); + void test_when_any(); + + void test_coroutine_promises(); + void test_coroutines(); + + void test_timer_queue(); + void test_timer(); + + void test_runtime(); + + void test_all(); +} // namespace concurrencpp::tests + +#endif // CONCURRENCPP_ALL_TESTS_H diff --git a/test/include/tests/executor_tests/executor_test_helpers.h b/test/include/tests/executor_tests/executor_test_helpers.h new file mode 100644 index 00000000..775b6e83 --- /dev/null +++ b/test/include/tests/executor_tests/executor_test_helpers.h @@ -0,0 +1,20 @@ +#ifndef CONCURRENCPP_EXECUTOR_TEST_HELPERS_H +#define CONCURRENCPP_EXECUTOR_TEST_HELPERS_H + +#include "../../helpers/assertions.h" +#include "../../concurrencpp/src/executors/constants.h" +#include "../../concurrencpp/src/executors/executor.h" + +namespace concurrencpp::tests { + struct executor_shutdowner { + std::shared_ptr executor; + + executor_shutdowner(std::shared_ptr executor) noexcept : executor(std::move(executor)) {} + + ~executor_shutdowner() noexcept { + executor->shutdown(); + } + }; +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/tests/test_utils/executor_shutdowner.h b/test/include/tests/test_utils/executor_shutdowner.h new file mode 100644 index 00000000..f443cfea --- /dev/null +++ b/test/include/tests/test_utils/executor_shutdowner.h @@ -0,0 +1,18 @@ +#ifndef CONCURRENCPP_EXECUTOR_TEST_HELPERS_H +#define CONCURRENCPP_EXECUTOR_TEST_HELPERS_H + +#include "concurrencpp/executors/executor.h" + +namespace concurrencpp::tests { + struct executor_shutdowner { + std::shared_ptr executor; + + executor_shutdowner(std::shared_ptr executor) noexcept : executor(std::move(executor)) {} + + ~executor_shutdowner() noexcept { + executor->shutdown(); + } + }; +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/tests/test_utils/proxy_coro.h b/test/include/tests/test_utils/proxy_coro.h new file mode 100644 index 00000000..ad66230a --- /dev/null +++ b/test/include/tests/test_utils/proxy_coro.h @@ -0,0 +1,25 @@ +#ifndef CONCURRENCPP_PROXY_CORO_H +#define CONCURRENCPP_PROXY_CORO_H + +#include "concurrencpp/concurrencpp.h" + +namespace concurrencpp::tests { + template + class proxy_coro { + + private: + result m_result; + std::shared_ptr m_test_executor; + bool m_force_rescheduling; + + public: + proxy_coro(result result, std::shared_ptr test_executor, bool force_rescheduling) noexcept : + m_result(std::move(result)), m_test_executor(std::move(test_executor)), m_force_rescheduling(force_rescheduling) {} + + result operator()() { + co_return co_await m_result.await_via(std::move(m_test_executor), m_force_rescheduling); + } + }; +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/tests/test_utils/result_factory.h b/test/include/tests/test_utils/result_factory.h new file mode 100644 index 00000000..d95758f3 --- /dev/null +++ b/test/include/tests/test_utils/result_factory.h @@ -0,0 +1,173 @@ +#ifndef CONCURRENCPP_RESULT_FACTORY_H +#define CONCURRENCPP_RESULT_FACTORY_H + +#include +#include +#include +#include + +namespace concurrencpp::tests { + template + struct result_factory { + static type get() { + return type(); + } + + static type throw_ex() { + throw std::underflow_error(""); + return get(); + } + + static result make_ready() { + return make_ready_result(get()); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; + + template<> + struct result_factory { + static int get() { + return 123456789; + } + + static std::vector get_many(size_t count) { + std::vector res(count); + std::iota(res.begin(), res.end(), 0); + return res; + } + + static int throw_ex() { + throw std::underflow_error(""); + return get(); + } + + static result make_ready() { + return make_ready_result(get()); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; + + template<> + struct result_factory { + static std::string get() { + return "abcdefghijklmnopqrstuvwxyz123456789!@#$%^&*()"; + } + + static std::vector get_many(size_t count) { + std::vector res; + res.reserve(count); + + for (size_t i = 0; i < count; i++) { + res.emplace_back(std::to_string(i)); + } + + return res; + } + + static std::string throw_ex() { + throw std::underflow_error(""); + return get(); + } + + static result make_ready() { + return make_ready_result(get()); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; + + template<> + struct result_factory { + static void get() {} + + static void throw_ex() { + throw std::underflow_error(""); + } + + static std::vector get_many(size_t count) { + return std::vector {count}; + } + + static result make_ready() { + return make_ready_result(); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; + + template<> + struct result_factory { + static int& get() { + static int i = 0; + return i; + } + + static std::vector> get_many(size_t count) { + static std::vector s_res(64); + std::vector> res; + res.reserve(count); + for (size_t i = 0; i < count; i++) { + res.emplace_back(s_res[i % 64]); + } + + return res; + } + + static int& throw_ex() { + throw std::underflow_error(""); + return get(); + } + + static result make_ready() { + return make_ready_result(get()); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; + + template<> + struct result_factory { + static std::string& get() { + static std::string str; + return str; + } + + static std::vector> get_many(size_t count) { + static std::vector s_res(64); + std::vector> res; + res.reserve(count); + for (size_t i = 0; i < count; i++) { + res.emplace_back(s_res[i % 64]); + } + + return res; + } + + static std::string& throw_ex() { + throw std::underflow_error(""); + return get(); + } + + static result make_ready() { + return make_ready_result(get()); + } + + static result make_exceptional() { + return make_exceptional_result(std::underflow_error("")); + } + }; +} // namespace concurrencpp::tests + +#endif diff --git a/test/include/tests/test_utils/test_executors.h b/test/include/tests/test_utils/test_executors.h new file mode 100644 index 00000000..f9a9d8a1 --- /dev/null +++ b/test/include/tests/test_utils/test_executors.h @@ -0,0 +1,158 @@ +#ifndef CONCURRENCPP_RESULT_TEST_EXECUTORS_H +#define CONCURRENCPP_RESULT_TEST_EXECUTORS_H + +#include "concurrencpp/concurrencpp.h" + +#include "result_factory.h" +#include "test_ready_result.h" + +#include +#include + +#include "../../helpers/random.h" + +namespace concurrencpp::tests { + class test_executor : public ::concurrencpp::executor { + + private: + mutable std::mutex m_lock; + std::thread m_execution_thread; + std::thread m_setting_thread; + bool m_abort; + + public: + test_executor() noexcept : executor("test_executor"), m_abort(false) {} + + ~test_executor() noexcept { + shutdown(); + } + + int max_concurrency_level() const noexcept override { + return 1; + } + + bool shutdown_requested() const noexcept override { + std::unique_lock lock(m_lock); + return m_abort; + } + + void shutdown() noexcept override { + std::unique_lock lock(m_lock); + if (m_abort) { + return; + } + + m_abort = true; + + if (m_execution_thread.joinable()) { + m_execution_thread.join(); + } + + if (m_setting_thread.joinable()) { + m_setting_thread.join(); + } + } + + bool scheduled_async() const noexcept { + std::unique_lock lock(m_lock); + return std::this_thread::get_id() == m_execution_thread.get_id(); + } + + bool scheduled_inline() const noexcept { + std::unique_lock lock(m_lock); + return std::this_thread::get_id() == m_setting_thread.get_id(); + } + + void enqueue(std::experimental::coroutine_handle<> task) override { + std::unique_lock lock(m_lock); + assert(!m_execution_thread.joinable()); + m_execution_thread = std::thread([task]() mutable { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + task(); + }); + } + + void enqueue(std::span> span) override { + (void)span; + std::abort(); // not neeeded. + } + + template + void set_rp_value(result_promise rp) { + std::unique_lock lock(m_lock); + assert(!m_setting_thread.joinable()); + m_setting_thread = std::thread([rp = std::move(rp)]() mutable { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + rp.set_from_function(result_factory::get); + }); + } + + template + size_t set_rp_err(result_promise rp) { + random randomizer; + const auto id = static_cast(randomizer()); + + std::unique_lock lock(m_lock); + assert(!m_setting_thread.joinable()); + m_setting_thread = std::thread([id, rp = std::move(rp)]() mutable { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + }); + return id; + } + }; + + inline std::shared_ptr make_test_executor() { + return std::make_shared(); + } +} // namespace concurrencpp::tests + +namespace concurrencpp::tests { + struct executor_enqueue_exception {}; + + struct throwing_executor : public concurrencpp::executor { + throwing_executor() : executor("throwing_executor") {} + + void enqueue(std::experimental::coroutine_handle<>) override { + throw executor_enqueue_exception(); + } + + void enqueue(std::span>) override { + throw executor_enqueue_exception(); + } + + int max_concurrency_level() const noexcept override { + return 0; + } + + bool shutdown_requested() const noexcept override { + return false; + } + + void shutdown() noexcept override { + // do nothing + } + }; + + template + void test_executor_error_thrown(concurrencpp::result result, std::shared_ptr throwing_executor) { + assert_equal(result.status(), concurrencpp::result_status::exception); + try { + result.get(); + } catch (concurrencpp::errors::executor_exception ex) { + assert_equal(ex.throwing_executor.get(), throwing_executor.get()); + + try { + std::rethrow_exception(ex.thrown_exception); + } catch (executor_enqueue_exception) { + // OK + } catch (...) { + assert_false(true); + } + } catch (...) { + assert_true(false); + } + } +} // namespace concurrencpp::tests + +#endif // CONCURRENCPP_RESULT_HELPERS_H diff --git a/test/include/tests/test_utils/test_ready_result.h b/test/include/tests/test_utils/test_ready_result.h new file mode 100644 index 00000000..4566afc1 --- /dev/null +++ b/test/include/tests/test_utils/test_ready_result.h @@ -0,0 +1,77 @@ +#ifndef CONCURRENCPP_TEST_READY_RESULT_H +#define CONCURRENCPP_TEST_READY_RESULT_H + +#include "concurrencpp/concurrencpp.h" + +#include "result_factory.h" + +#include "../../helpers/assertions.h" + +#include + +namespace concurrencpp::tests { + struct costume_exception : public std::exception { + const intptr_t id; + + costume_exception(intptr_t id) noexcept : id(id) {} + }; +} // namespace concurrencpp::tests + +namespace concurrencpp::tests { + template + void test_ready_result_result(::concurrencpp::result result, const type& o) { + assert_true(static_cast(result)); + assert_equal(result.status(), concurrencpp::result_status::value); + + try { + assert_equal(result.get(), o); + } catch (...) { + assert_true(false); + } + } + + template + void test_ready_result_result(::concurrencpp::result result, std::reference_wrapper> ref) { + assert_true(static_cast(result)); + assert_equal(result.status(), concurrencpp::result_status::value); + + try { + assert_equal(&result.get(), &ref.get()); + } catch (...) { + assert_true(false); + } + } + + template + void test_ready_result_result(::concurrencpp::result result) { + test_ready_result_result(std::move(result), result_factory::get()); + } + + template<> + inline void test_ready_result_result(::concurrencpp::result result) { + assert_true(static_cast(result)); + assert_equal(result.status(), concurrencpp::result_status::value); + try { + result.get(); // just make sure no exception is thrown. + } catch (...) { + assert_true(false); + } + } + + template + void test_ready_result_costume_exception(concurrencpp::result result, const intptr_t id) { + assert_true(static_cast(result)); + assert_equal(result.status(), concurrencpp::result_status::exception); + + try { + result.get(); + } catch (costume_exception e) { + return assert_equal(e.id, id); + } catch (...) { + } + + assert_true(false); + } +} // namespace concurrencpp::tests + +#endif diff --git a/test/source/helpers/assertions.cpp b/test/source/helpers/assertions.cpp new file mode 100644 index 00000000..96f2fd65 --- /dev/null +++ b/test/source/helpers/assertions.cpp @@ -0,0 +1,161 @@ +#include "helpers/assertions.h" + +#include + +#include +#include + +namespace concurrencpp::tests::details { + std::string to_string(bool value) { + return value ? "true" : "false"; + } + + std::string to_string(int value) { + return std::to_string(value); + } + + std::string to_string(long value) { + return std::to_string(value); + } + + std::string to_string(long long value) { + return std::to_string(value); + } + + std::string to_string(unsigned value) { + return std::to_string(value); + } + + std::string to_string(unsigned long value) { + return std::to_string(value); + } + + std::string to_string(unsigned long long value) { + return std::to_string(value); + } + + std::string to_string(float value) { + return std::to_string(value); + } + + std::string to_string(double value) { + return std::to_string(value); + } + + std::string to_string(long double value) { + return std::to_string(value); + } + + const std::string& to_string(const std::string& str) { + return str; + } + + std::string to_string(const char* str) { + return str; + } + + std::string to_string(const std::string_view str) { + return {str.begin(), str.end()}; + } + + std::string to_string(std::thread::id id) { + std::stringstream stream; + stream << id; + return stream.str(); + } + + std::string to_string(std::chrono::time_point time_point) { + auto time_from_epoch = time_point.time_since_epoch(); + auto seconds_from_epoch = std::chrono::duration_cast(time_from_epoch); + return std::string("time_from_epoch[") + std::to_string(seconds_from_epoch.count()) + "]"; + } + + void assert_same_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected ["; + error_msg += a; + error_msg += "] == ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + + void assert_not_same_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected: ["; + error_msg += a; + error_msg += "] =/= ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + + void assert_bigger_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected ["; + error_msg += a; + error_msg += "] > ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + + void assert_smaller_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected ["; + error_msg += a; + error_msg += "] < ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + + void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected ["; + error_msg += a; + error_msg += "] >= ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + + void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b) { + std::string error_msg = "assertion failed. "; + error_msg += "expected ["; + error_msg += a; + error_msg += "] <= ["; + error_msg += b; + error_msg += "]"; + + fprintf(stderr, "%s", error_msg.data()); + std::abort(); + } + +} // namespace concurrencpp::tests::details + +namespace concurrencpp::tests { + void assert_true(bool condition) { + if (!condition) { + fprintf(stderr, "%s", "assertion faild. expected: [true] actual: [false]."); + std::abort(); + } + } + + void assert_false(bool condition) { + if (condition) { + fprintf(stderr, "%s", "assertion faild. expected: [false] actual: [true]."); + std::abort(); + } + } +} // namespace concurrencpp::tests diff --git a/test/source/helpers/object_observer.cpp b/test/source/helpers/object_observer.cpp new file mode 100644 index 00000000..a5fec549 --- /dev/null +++ b/test/source/helpers/object_observer.cpp @@ -0,0 +1,144 @@ +#include "concurrencpp/concurrencpp.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests::details { + class object_observer_state { + + private: + mutable std::mutex m_lock; + mutable std::condition_variable m_condition; + std::unordered_map m_execution_map; + size_t m_destruction_count; + size_t m_execution_count; + + public: + object_observer_state() : m_destruction_count(0), m_execution_count(0) {} + + size_t get_destruction_count() const noexcept { + std::unique_lock lock(m_lock); + return m_destruction_count; + } + + size_t get_execution_count() const noexcept { + std::unique_lock lock(m_lock); + return m_execution_count; + } + + std::unordered_map get_execution_map() const noexcept { + std::unique_lock lock(m_lock); + return m_execution_map; + } + + bool wait_execution_count(size_t count, std::chrono::milliseconds timeout) { + std::unique_lock lock(m_lock); + return m_condition.wait_for(lock, timeout, [count, this] { + return count == m_execution_count; + }); + } + + void on_execute() { + const auto this_id = ::concurrencpp::details::thread::get_current_virtual_id(); + + { + std::unique_lock lock(m_lock); + ++m_execution_count; + ++m_execution_map[this_id]; + } + + m_condition.notify_all(); + } + + bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { + std::unique_lock lock(m_lock); + return m_condition.wait_for(lock, timeout, [count, this] { + return count == m_destruction_count; + }); + } + + void on_destroy() { + { + std::unique_lock lock(m_lock); + ++m_destruction_count; + } + + m_condition.notify_all(); + } + }; +} // namespace concurrencpp::tests::details + +using concurrencpp::tests::testing_stub; +using concurrencpp::tests::object_observer; +using concurrencpp::tests::value_testing_stub; + +testing_stub& testing_stub::operator=(testing_stub&& rhs) noexcept { + if (this == &rhs) { + return *this; + } + + if (static_cast(m_state)) { + m_state->on_destroy(); + } + + m_state = std::move(rhs.m_state); + return *this; +} + +void testing_stub::operator()() noexcept { + if (static_cast(m_state)) { + if (m_dummy_work_time != std::chrono::milliseconds(0)) { + std::this_thread::sleep_for(m_dummy_work_time); + } + + m_state->on_execute(); + } +} + +value_testing_stub& value_testing_stub::operator=(value_testing_stub&& rhs) noexcept { + testing_stub::operator=(std::move(rhs)); + return *this; +} + +size_t value_testing_stub::operator()() noexcept { + testing_stub::operator()(); + return m_return_value; +} + +object_observer::object_observer() : m_state(std::make_shared()) {} + +testing_stub object_observer::get_testing_stub(std::chrono::milliseconds dummy_work_time) noexcept { + return {m_state, dummy_work_time}; +} + +value_testing_stub object_observer::get_testing_stub(int value, std::chrono::milliseconds dummy_work_time) noexcept { + return {m_state, dummy_work_time, value}; +} + +value_testing_stub object_observer::get_testing_stub(size_t value, std::chrono::milliseconds dummy_work_time) noexcept { + return {m_state, dummy_work_time, static_cast(value)}; +} + +bool object_observer::wait_execution_count(size_t count, std::chrono::milliseconds timeout) { + return m_state->wait_execution_count(count, timeout); +} + +bool object_observer::wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { + return m_state->wait_destruction_count(count, timeout); +} + +size_t object_observer::get_destruction_count() const noexcept { + return m_state->get_destruction_count(); +} + +size_t object_observer::get_execution_count() const noexcept { + return m_state->get_execution_count(); +} + +std::unordered_map object_observer::get_execution_map() const noexcept { + return m_state->get_execution_map(); +} + +testing_stub::~testing_stub() noexcept { + if (static_cast(m_state)) { + m_state->on_destroy(); + } +} diff --git a/test/source/main.cpp b/test/source/main.cpp new file mode 100644 index 00000000..a7f10f7b --- /dev/null +++ b/test/source/main.cpp @@ -0,0 +1,7 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +int main() { + concurrencpp::tests::test_all(); + return 0; +} diff --git a/test/source/tester/tester.cpp b/test/source/tester/tester.cpp new file mode 100644 index 00000000..1a306695 --- /dev/null +++ b/test/source/tester/tester.cpp @@ -0,0 +1,43 @@ +#include "tester/tester.h" + +#include + +#include + +using concurrencpp::tests::test_step; +using concurrencpp::tests::tester; +using namespace std::chrono; + +test_step::test_step(const char* step_name, std::function callable) : m_step_name(step_name), m_step(std::move(callable)) {} + +void test_step::launch_test_step() noexcept { + const auto test_start_time = system_clock::now(); + printf("\tTest-step started: %s\n", m_step_name); + + m_step(); + + auto elapsed_time = duration_cast(system_clock::now() - test_start_time).count(); + printf("\tTest-step ended (%lldms).\n", elapsed_time); +} + +tester::tester(const char* test_name) noexcept : m_test_name(test_name) {} + +void tester::add_step(const char* step_name, std::function callable) { + m_steps.emplace_back(step_name, std::move(callable)); +} + +void tester::launch_test() noexcept { + const auto test_start_time = system_clock::now(); + printf("Test started: %s\n", m_test_name); + + for (auto& test_step : m_steps) { + try { + test_step.launch_test_step(); + } catch (const std::exception& ex) { + ::fprintf(stderr, "\tTest step terminated with an exception : %s\n", ex.what()); + } + } + + auto elapsed_time = duration_cast(system_clock::now() - test_start_time).count(); + printf("Test ended (%lldms).\n____________________\n", elapsed_time); +} diff --git a/test/source/tests/all_tests.cpp b/test/source/tests/all_tests.cpp new file mode 100644 index 00000000..d054490b --- /dev/null +++ b/test/source/tests/all_tests.cpp @@ -0,0 +1,26 @@ +#include "tests/all_tests.h" + +void concurrencpp::tests::test_all() { + test_inline_executor(); + test_thread_pool_executor(); + test_thread_executor(); + test_worker_thread_executor(); + test_manual_executor(); + + test_result(); + test_result_resolve_all(); + test_result_await_all(); + test_result_promise(); + test_make_result(); + + test_coroutine_promises(); + test_coroutines(); + + test_when_all(); + test_when_any(); + + test_timer_queue(); + test_timer(); + + test_runtime(); +} diff --git a/test/source/tests/coroutine_tests/coroutine_promises_test.cpp b/test/source/tests/coroutine_tests/coroutine_promises_test.cpp new file mode 100644 index 00000000..dd36e843 --- /dev/null +++ b/test/source/tests/coroutine_tests/coroutine_promises_test.cpp @@ -0,0 +1,277 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests { + void test_initialy_resumed_null_result_promise(); + void test_initialy_resumed_result_promise(); + void test_initialy_rescheduled_null_result_promise(); + void test_initialy_rescheduled_result_promise(); +} // namespace concurrencpp::tests + +using worker_ptr = std::shared_ptr; + +namespace concurrencpp::tests { + void init_workers(std::span span) { + for (auto& worker : span) { + worker = std::make_shared(); + } + } + + void shutdown_workers(std::span span) { + for (auto& worker : span) { + worker->shutdown(); + } + } +} // namespace concurrencpp::tests + +namespace concurrencpp::tests { + null_result initialy_resumed_null_result_coro(concurrencpp::details::wait_context& wc, + worker_ptr w0, + worker_ptr w1, + worker_ptr w2, + worker_ptr w3, + testing_stub stub) { + int i = 0; + std::string s = ""; + + co_await w0->submit([] { + }); + + ++i; + s += "a"; + + co_await w1->submit([] { + }); + + ++i; + s += "a"; + + co_await w2->submit([] { + }); + + ++i; + s += "a"; + + co_await w3->submit([] { + }); + + assert_equal(i, 3); + assert_equal(s, "aaa"); + + wc.notify(); + } +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_initialy_resumed_null_result_promise() { + worker_ptr workers[4]; + init_workers(workers); + + object_observer observer; + concurrencpp::details::wait_context wc; + initialy_resumed_null_result_coro(wc, workers[0], workers[1], workers[2], workers[3], observer.get_testing_stub()); + + wc.wait(); + assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); + + shutdown_workers(workers); +} + +namespace concurrencpp::tests { + result> initialy_resumed_result_coro(worker_ptr w0, + worker_ptr w1, + worker_ptr w2, + worker_ptr w3, + testing_stub stub, + const bool terminate_by_exception) { + int i = 0; + std::string s = ""; + + co_await w0->submit([] { + }); + + ++i; + s += "a"; + + co_await w1->submit([] { + }); + + ++i; + s += "a"; + + co_await w2->submit([] { + }); + + ++i; + s += "a"; + + co_await w3->submit([] { + }); + + if (terminate_by_exception) { + throw costume_exception(1234); + } + + co_return std::make_pair(i, s); + } +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_initialy_resumed_result_promise() { + worker_ptr workers[4]; + init_workers(workers); + + object_observer observer; + auto [i, s] = initialy_resumed_result_coro(workers[0], workers[1], workers[2], workers[3], observer.get_testing_stub(), false).get(); + + assert_equal(i, 3); + assert_equal(s, "aaa"); + + assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); + + auto result = initialy_resumed_result_coro(workers[0], workers[1], workers[2], workers[3], observer.get_testing_stub(), true); + + result.wait(); + + test_ready_result_costume_exception(std::move(result), 1234); + assert_true(observer.wait_destruction_count(2, std::chrono::seconds(10))); + + shutdown_workers(workers); +} + +namespace concurrencpp::tests { + null_result initialy_rescheduled_null_result_coro(executor_tag, + worker_ptr w0, + const std::thread::id caller_thread_id, + concurrencpp::details::wait_context& wc, + worker_ptr w1, + worker_ptr w2, + worker_ptr w3, + testing_stub stub) { + + assert_not_equal(caller_thread_id, std::this_thread::get_id()); + + int i = 0; + std::string s = ""; + + ++i; + s += "a"; + + co_await w1->submit([] { + }); + + ++i; + s += "a"; + + co_await w2->submit([] { + }); + + ++i; + s += "a"; + + co_await w3->submit([] { + }); + + assert_equal(i, 3); + assert_equal(s, "aaa"); + + wc.notify(); + } +} // namespace concurrencpp::tests +void concurrencpp::tests::test_initialy_rescheduled_null_result_promise() { + worker_ptr workers[4]; + init_workers(workers); + + object_observer observer; + concurrencpp::details::wait_context wc; + initialy_rescheduled_null_result_coro({}, workers[0], std::this_thread::get_id(), wc, workers[1], workers[2], workers[3], observer.get_testing_stub()); + + wc.wait(); + assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); + + shutdown_workers(workers); +} + +namespace concurrencpp::tests { + result> initialy_rescheduled_result_coro(executor_tag, + worker_ptr w0, + const std::thread::id caller_id, + worker_ptr w1, + worker_ptr w2, + worker_ptr w3, + testing_stub stub, + const bool terminate_by_exception) { + + assert_not_equal(caller_id, std::this_thread::get_id()); + + int i = 0; + std::string s = ""; + + co_await w0->submit([] { + }); + + ++i; + s += "a"; + + co_await w1->submit([] { + }); + + ++i; + s += "a"; + + co_await w2->submit([] { + }); + + ++i; + s += "a"; + + co_await w3->submit([] { + }); + + if (terminate_by_exception) { + throw costume_exception(1234); + } + + co_return std::make_pair(i, s); + } +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_initialy_rescheduled_result_promise() { + worker_ptr workers[4]; + init_workers(workers); + + object_observer observer; + auto [i, s] = + initialy_rescheduled_result_coro({}, workers[0], std::this_thread::get_id(), workers[1], workers[2], workers[3], observer.get_testing_stub(), false).get(); + + assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); + + assert_equal(i, 3); + assert_equal(s, "aaa"); + + auto result = + initialy_rescheduled_result_coro({}, workers[0], std::this_thread::get_id(), workers[1], workers[2], workers[3], observer.get_testing_stub(), true); + + result.wait(); + test_ready_result_costume_exception(std::move(result), 1234); + + assert_true(observer.wait_destruction_count(2, std::chrono::seconds(10))); + + shutdown_workers(workers); +} + +void concurrencpp::tests::test_coroutine_promises() { + tester tester("coroutine promises test"); + + tester.add_step("initialy_resumed_null_result_promise", test_initialy_resumed_null_result_promise); + tester.add_step("initialy_resumed_result_promise", test_initialy_resumed_result_promise); + tester.add_step("initialy_rescheduled_null_result_promise", test_initialy_rescheduled_null_result_promise); + tester.add_step("initialy_rescheduled_result_promise", test_initialy_rescheduled_result_promise); + + tester.launch_test(); +} diff --git a/test/source/tests/coroutine_tests/coroutines_tests.cpp b/test/source/tests/coroutine_tests/coroutines_tests.cpp new file mode 100644 index 00000000..f7849776 --- /dev/null +++ b/test/source/tests/coroutine_tests/coroutines_tests.cpp @@ -0,0 +1,136 @@ +#include "concurrencpp/concurrencpp.h" + +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +#include + +#include "tests/test_utils/executor_shutdowner.h" + +namespace concurrencpp::tests { + template + result recursive_coroutine(executor_tag, + std::shared_ptr te, + const size_t cur_depth, + const size_t max_depth, + const bool terminate_by_exception); + + template + void test_recursive_coroutines_impl(); + void test_recursive_coroutines(); + + result test_combo_coroutine_impl(std::shared_ptr te, const bool terminate_by_exception); + void test_combo_coroutine(); +} // namespace concurrencpp::tests + +using concurrencpp::result; + +template +result concurrencpp::tests::recursive_coroutine(executor_tag, + std::shared_ptr te, + const size_t cur_depth, + const size_t max_depth, + const bool terminate_by_exception) { + + if (cur_depth < max_depth) { + co_return co_await recursive_coroutine({}, te, cur_depth + 1, max_depth, terminate_by_exception); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + if (terminate_by_exception) { + throw costume_exception(1234); + } + + co_return result_factory::get(); +} + +template +void concurrencpp::tests::test_recursive_coroutines_impl() { + // value + { + auto te = std::make_shared(); + executor_shutdowner es(te); + auto result = recursive_coroutine({}, te, 0, 20, false); + result.wait(); + test_ready_result_result(std::move(result)); + } + + // exception + { + auto te = std::make_shared(); + executor_shutdowner es(te); + auto result = recursive_coroutine({}, te, 0, 20, true); + result.wait(); + test_ready_result_costume_exception(std::move(result), 1234); + } +} + +void concurrencpp::tests::test_recursive_coroutines() { + test_recursive_coroutines_impl(); + test_recursive_coroutines_impl(); + test_recursive_coroutines_impl(); + test_recursive_coroutines_impl(); + test_recursive_coroutines_impl(); +} + +result concurrencpp::tests::test_combo_coroutine_impl(std::shared_ptr te, const bool terminate_by_exception) { + auto int_result = co_await te->submit([] { + return result_factory::get(); + }); + + auto string_result = co_await te->submit([int_result] { + return std::to_string(int_result); + }); + + assert_equal(string_result, std::to_string(result_factory::get())); + + auto& int_ref_result = co_await te->submit([]() -> int& { + return result_factory::get(); + }); + + auto& str_ref_result = co_await te->submit([]() -> std::string& { + return result_factory::get(); + }); + + assert_equal(&int_ref_result, &result_factory::get()); + assert_equal(&str_ref_result, &result_factory::get()); + + if (terminate_by_exception) { + throw costume_exception(1234); + } +} + +void concurrencpp::tests::test_combo_coroutine() { + // value + { + auto te = std::make_shared(); + executor_shutdowner es(te); + auto res = test_combo_coroutine_impl(te, false); + res.wait(); + test_ready_result_result(std::move(res)); + } + + // exception + { + auto te = std::make_shared(); + executor_shutdowner es(te); + auto res = test_combo_coroutine_impl(te, true); + res.wait(); + test_ready_result_costume_exception(std::move(res), 1234); + } +} + +void concurrencpp::tests::test_coroutines() { + tester tester("coroutines test"); + + tester.add_step("recursive coroutines", test_recursive_coroutines); + tester.add_step("combo coroutine", test_combo_coroutine); + + tester.launch_test(); +} diff --git a/test/source/tests/executor_tests/inline_executor_tests.cpp b/test/source/tests/executor_tests/inline_executor_tests.cpp new file mode 100644 index 00000000..635b0436 --- /dev/null +++ b/test/source/tests/executor_tests/inline_executor_tests.cpp @@ -0,0 +1,310 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +#include "concurrencpp/executors/constants.h" + +namespace concurrencpp::tests { + void test_inline_executor_name(); + + void test_inline_executor_shutdown(); + + void test_inline_executor_max_concurrency_level(); + + void test_inline_executor_post_foreign(); + void test_inline_executor_post_inline(); + void test_inline_executor_post(); + + void test_inline_executor_submit_foreign(); + void test_inline_executor_submit_inline(); + void test_inline_executor_submit(); + + void test_inline_executor_bulk_post_foreign(); + void test_inline_executor_bulk_post_inline(); + void test_inline_executor_bulk_post(); + + void test_inline_executor_bulk_submit_foreign(); + void test_inline_executor_bulk_submit_inline(); + void test_inline_executor_bulk_submit(); +} // namespace concurrencpp::tests + +using concurrencpp::details::thread; + +void concurrencpp::tests::test_inline_executor_name() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->name, concurrencpp::details::consts::k_inline_executor_name); +} + +void concurrencpp::tests::test_inline_executor_shutdown() { + auto executor = std::make_shared(); + assert_false(executor->shutdown_requested()); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + // it's ok to shut down an executor more than once + executor->shutdown(); + + assert_throws([executor] { + executor->enqueue(std::experimental::coroutine_handle {}); + }); + + assert_throws([executor] { + std::experimental::coroutine_handle<> array[4]; + std::span> span = array; + executor->enqueue(span); + }); +} + +void concurrencpp::tests::test_inline_executor_max_concurrency_level() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->max_concurrency_level(), concurrencpp::details::consts::k_inline_executor_max_concurrency_level); +} + +void concurrencpp::tests::test_inline_executor_post_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_inline_executor_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + }); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_inline_executor_post() { + test_inline_executor_post_inline(); + test_inline_executor_post_foreign(); +} + +void concurrencpp::tests::test_inline_executor_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), size_t(i)); + } +} + +void concurrencpp::tests::test_inline_executor_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector> results; + results.resize(task_count); + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + return results; + }); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), size_t(i)); + } +} + +void concurrencpp::tests::test_inline_executor_submit() { + test_inline_executor_submit_inline(); + test_inline_executor_submit_foreign(); +} + +void concurrencpp::tests::test_inline_executor_bulk_post_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_inline_executor_bulk_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer]() mutable { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + }); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_inline_executor_bulk_post() { + test_inline_executor_bulk_post_foreign(); + test_inline_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_inline_executor_bulk_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_inline_executor_bulk_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + return executor->bulk_submit(stubs); + }); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_inline_executor_bulk_submit() { + test_inline_executor_bulk_submit_foreign(); + test_inline_executor_bulk_submit_inline(); +} + +void concurrencpp::tests::test_inline_executor() { + tester tester("inline_executor test"); + + tester.add_step("name", test_inline_executor_name); + tester.add_step("shutdown", test_inline_executor_shutdown); + tester.add_step("max_concurrency_level", test_inline_executor_max_concurrency_level); + tester.add_step("post", test_inline_executor_post); + tester.add_step("submit", test_inline_executor_submit); + tester.add_step("bulk_post", test_inline_executor_bulk_post); + tester.add_step("bulk_submit", test_inline_executor_bulk_submit); + + tester.launch_test(); +} diff --git a/test/source/tests/executor_tests/manual_executor_tests.cpp b/test/source/tests/executor_tests/manual_executor_tests.cpp new file mode 100644 index 00000000..30d93af1 --- /dev/null +++ b/test/source/tests/executor_tests/manual_executor_tests.cpp @@ -0,0 +1,643 @@ +#include "concurrencpp/concurrencpp.h" + +#include "tests/all_tests.h" + +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +#include "concurrencpp/executors/constants.h" + +namespace concurrencpp::tests { + void test_manual_executor_name(); + + void test_manual_executor_shutdown_method_access(); + void test_manual_executor_shutdown_coro_raii(); + void test_manual_executor_shutdown_more_than_once(); + void test_manual_executor_shutdown(); + + void test_manual_executor_max_concurrency_level(); + + void test_manual_executor_post_foreign(); + void test_manual_executor_post_inline(); + void test_manual_executor_post(); + + void test_manual_executor_submit_foreign(); + void test_manual_executor_submit_inline(); + void test_manual_executor_submit(); + + void test_manual_executor_bulk_post_foreign(); + void test_manual_executor_bulk_post_inline(); + void test_manual_executor_bulk_post(); + + void test_manual_executor_bulk_submit_foreign(); + void test_manual_executor_bulk_submit_inline(); + void test_manual_executor_bulk_submit(); + + void test_manual_executor_loop_once(); + void test_manual_executor_loop_once_timed(); + + void test_manual_executor_loop(); + + void test_manual_executor_clear(); + + void test_manual_executor_wait_for_task(); + void test_manual_executor_wait_for_task_timed(); +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_manual_executor_name() { + auto executor = std::make_shared(); + assert_equal(executor->name, concurrencpp::details::consts::k_manual_executor_name); +} + +void concurrencpp::tests::test_manual_executor_shutdown_method_access() { + auto executor = std::make_shared(); + assert_false(executor->shutdown_requested()); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_throws([executor] { + executor->enqueue(std::experimental::coroutine_handle {}); + }); + + assert_throws([executor] { + std::experimental::coroutine_handle<> array[4]; + std::span> span = array; + executor->enqueue(span); + }); + + assert_throws([executor] { + executor->wait_for_task(); + }); + + assert_throws([executor] { + executor->wait_for_task(std::chrono::milliseconds(100)); + }); + + assert_throws([executor] { + executor->loop_once(); + }); + + assert_throws([executor] { + executor->loop(100); + }); +} + +void concurrencpp::tests::test_manual_executor_shutdown_coro_raii() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), task_count); + + for (auto& result : results) { + assert_throws([&result] { + result.get(); + }); + } +} + +void concurrencpp::tests::test_manual_executor_shutdown_more_than_once() { + const size_t task_count = 64; + auto executor = std::make_shared(); + + for (size_t i = 0; i < task_count; i++) { + executor->post([] { + }); + } + + for (size_t i = 0; i < 4; i++) { + executor->shutdown(); + } +} + +void concurrencpp::tests::test_manual_executor_shutdown() { + test_manual_executor_shutdown_method_access(); + test_manual_executor_shutdown_coro_raii(); + test_manual_executor_shutdown_more_than_once(); +} + +void concurrencpp::tests::test_manual_executor_max_concurrency_level() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->max_concurrency_level(), concurrencpp::details::consts::k_manual_executor_max_concurrency_level); +} + +void concurrencpp::tests::test_manual_executor_post_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + assert_equal(executor->size(), 1 + i); + assert_false(executor->empty()); + } + + // manual executor doesn't execute the tasks automatically, hence manual. + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + executor->post([executor, &observer] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + assert_equal(executor->size(), 1 + i); + assert_false(executor->empty()); + } + }); + + assert_true(executor->loop_once()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + } + + executor->shutdown(); + + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_post() { + test_manual_executor_post_foreign(); + test_manual_executor_post_inline(); +} + +void concurrencpp::tests::test_manual_executor_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + assert_equal(results[i].get(), i); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + auto results_res = executor->submit([executor, &observer] { + std::vector> results; + results.resize(task_count); + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + return results; + }); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + assert_true(executor->loop_once()); + auto results = results_res.get(); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + assert_equal(results[i].get(), i); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_submit() { + test_manual_executor_submit_foreign(); + test_manual_executor_submit_inline(); +} + +void concurrencpp::tests::test_manual_executor_bulk_post_foreign() { + object_observer observer; + const size_t task_count = 1'000; + auto executor = std::make_shared(); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + + assert_false(executor->empty()); + assert_equal(executor->size(), task_count); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_bulk_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'000; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer]() mutable { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + executor->bulk_post(stubs); + }); + + assert_true(executor->loop_once()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_bulk_post() { + test_manual_executor_bulk_post_foreign(); + test_manual_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_manual_executor_bulk_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + assert_false(executor->empty()); + assert_equal(executor->size(), task_count); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_bulk_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + return executor->bulk_submit(stubs); + }); + + assert_true(executor->loop_once()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count / 2; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + assert_equal(results[i].get(), i); + } + + executor->shutdown(); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_manual_executor_bulk_submit() { + test_manual_executor_bulk_submit_foreign(); + test_manual_executor_bulk_submit_inline(); +} + +void concurrencpp::tests::test_manual_executor_loop_once() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + for (size_t i = 0; i < 10; i++) { + assert_false(executor->loop_once()); + } + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + for (size_t i = 0; i < task_count; i++) { + assert_true(executor->loop_once()); + assert_equal(observer.get_execution_count(), i + 1); + assert_equal(executor->size(), task_count - (i + 1)); + assert_equal(results[i].get(), i); + } + + assert_equal(observer.get_destruction_count(), task_count); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, concurrencpp::details::thread::get_current_virtual_id()); + + for (size_t i = 0; i < 10; i++) { + assert_false(executor->loop_once()); + } +} + +void concurrencpp::tests::test_manual_executor_loop_once_timed() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + object_observer observer; + const auto waiting_time = 20; + + // case 1: timeout + { + for (size_t i = 0; i < 10; i++) { + const auto before = std::chrono::high_resolution_clock::now(); + assert_false(executor->loop_once(std::chrono::milliseconds(waiting_time))); + const auto after = std::chrono::high_resolution_clock::now(); + const auto ms = std::chrono::duration_cast(after - before).count(); + assert_bigger_equal(ms, waiting_time); + } + } + + // case 2: tasks already exist + { + executor->post(observer.get_testing_stub()); + const auto before = std::chrono::high_resolution_clock::now(); + assert_true(executor->loop_once(std::chrono::milliseconds(waiting_time))); + const auto after = std::chrono::high_resolution_clock::now(); + const auto ms = std::chrono::duration_cast(after - before).count(); + assert_smaller_equal(ms, 5); + assert_equal(observer.get_execution_count(), size_t(1)); + assert_equal(observer.get_destruction_count(), size_t(1)); + } + + // case 3: goes to sleep, then woken by an incoming task + { + const auto later = std::chrono::high_resolution_clock::now() + std::chrono::seconds(2); + + std::thread thread([executor, later, stub = observer.get_testing_stub()]() mutable { + std::this_thread::sleep_until(later); + executor->post(std::move(stub)); + }); + + assert_true(executor->loop_once(std::chrono::seconds(100))); + const auto now = std::chrono::high_resolution_clock::now(); + + assert_bigger_equal(now, later); + assert_smaller_equal(now, later + std::chrono::seconds(2)); + + thread.join(); + } +} + +void concurrencpp::tests::test_manual_executor_loop() { + object_observer observer; + const size_t task_count = 1'000; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->size(), size_t(0)); + assert_true(executor->empty()); + + assert_equal(executor->loop(100), size_t(0)); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(0)); + + const size_t chunk_size = 150; + const auto cycles = task_count / chunk_size; + const auto remained = task_count - (cycles * chunk_size); + + for (size_t i = 0; i < cycles; i++) { + const auto executed = executor->loop(chunk_size); + assert_equal(executed, chunk_size); + + const auto total_executed = (i + 1) * chunk_size; + assert_equal(observer.get_execution_count(), total_executed); + assert_equal(observer.get_destruction_count(), total_executed); + assert_equal(executor->size(), task_count - total_executed); + } + + // execute the remaining 100 tasks + const auto executed = executor->loop(chunk_size); + assert_equal(executed, remained); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); + + assert_true(executor->empty()); + assert_equal(executor->size(), size_t(0)); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_equal(execution_map.begin()->first, concurrencpp::details::thread::get_current_virtual_id()); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_manual_executor_clear() { + object_observer observer; + const size_t task_count = 100; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->clear(), size_t(0)); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_equal(executor->clear(), task_count); + assert_true(executor->empty()); + assert_equal(executor->size(), size_t(0)); + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), task_count); + + for (auto& result : results) { + assert_throws([&result]() mutable { + result.get(); + }); + } +} + +void concurrencpp::tests::test_manual_executor_wait_for_task() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + auto enqueuing_time = std::chrono::system_clock::now() + std::chrono::milliseconds(2'500); + + std::thread enqueuing_thread([executor, enqueuing_time]() mutable { + std::this_thread::sleep_until(enqueuing_time); + executor->post([] { + }); + }); + + executor->wait_for_task(); + assert_bigger_equal(std::chrono::system_clock::now(), enqueuing_time); + + enqueuing_thread.join(); +} + +void concurrencpp::tests::test_manual_executor_wait_for_task_timed() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + const auto waiting_time = std::chrono::milliseconds(200); + + for (size_t i = 0; i < 10; i++) { + const auto before = std::chrono::system_clock::now(); + const auto task_found = executor->wait_for_task(waiting_time); + const auto after = std::chrono::system_clock::now(); + const auto time_elapsed = std::chrono::duration_cast(after - before); + + assert_false(task_found); + assert_bigger_equal(time_elapsed, waiting_time); + } + + auto enqueuing_time = std::chrono::system_clock::now() + std::chrono::milliseconds(2'500); + + std::thread enqueuing_thread([executor, enqueuing_time]() mutable { + std::this_thread::sleep_until(enqueuing_time); + executor->post([] { + }); + }); + + const auto task_found = executor->wait_for_task(std::chrono::seconds(10)); + assert_true(task_found); + assert_bigger_equal(std::chrono::system_clock::now(), enqueuing_time); + + enqueuing_thread.join(); +} + +void concurrencpp::tests::test_manual_executor() { + tester tester("manual_executor test"); + + tester.add_step("name", test_manual_executor_name); + tester.add_step("shutdown", test_manual_executor_shutdown); + tester.add_step("max_concurrency_level", test_manual_executor_max_concurrency_level); + tester.add_step("post", test_manual_executor_post); + tester.add_step("submit", test_manual_executor_submit); + tester.add_step("bulk_post", test_manual_executor_bulk_post); + tester.add_step("bulk_submit", test_manual_executor_bulk_submit); + tester.add_step("loop_once", test_manual_executor_loop_once); + tester.add_step("loop_once (ms)", test_manual_executor_loop_once_timed); + tester.add_step("loop", test_manual_executor_loop); + tester.add_step("wait_for_task", test_manual_executor_wait_for_task); + tester.add_step("wait_for_task (ms)", test_manual_executor_wait_for_task_timed); + tester.add_step("clear", test_manual_executor_clear); + + tester.launch_test(); +} diff --git a/test/source/tests/executor_tests/thread_executor_tests.cpp b/test/source/tests/executor_tests/thread_executor_tests.cpp new file mode 100644 index 00000000..114c59f9 --- /dev/null +++ b/test/source/tests/executor_tests/thread_executor_tests.cpp @@ -0,0 +1,324 @@ +#include "concurrencpp/concurrencpp.h" + +#include "tests/all_tests.h" + +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +#include "concurrencpp/executors/constants.h" + +namespace concurrencpp::tests { + void test_thread_executor_name(); + + void test_thread_executor_shutdown_join(); + void test_thread_executor_shutdown_method_access(); + void test_thread_executor_shutdown_more_than_once(); + void test_thread_executor_shutdown(); + + void test_thread_executor_max_concurrency_level(); + + void test_thread_executor_post_foreign(); + void test_thread_executor_post_inline(); + void test_thread_executor_post(); + + void test_thread_executor_submit_foreign(); + void test_thread_executor_submit_inline(); + void test_thread_executor_submit(); + + void test_thread_executor_bulk_post_foreign(); + void test_thread_executor_bulk_post_inline(); + void test_thread_executor_bulk_post(); + + void test_thread_executor_bulk_submit_foreign(); + void test_thread_executor_bulk_submit_inline(); + void test_thread_executor_bulk_submit(); + + void assert_execution_threads(const std::unordered_map& execution_map, const size_t expected_thread_count) { + assert_equal(execution_map.size(), expected_thread_count); + + for (const auto& thread_pair : execution_map) { + assert_not_equal(thread_pair.first, size_t(0)); + assert_equal(thread_pair.second, size_t(1)); + } + } +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_thread_executor_name() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->name, concurrencpp::details::consts::k_thread_executor_name); +} + +void concurrencpp::tests::test_thread_executor_shutdown_method_access() { + auto executor = std::make_shared(); + assert_false(executor->shutdown_requested()); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_throws([executor] { + executor->enqueue(std::experimental::coroutine_handle {}); + }); + + assert_throws([executor] { + std::experimental::coroutine_handle<> array[4]; + std::span> span = array; + executor->enqueue(span); + }); +} + +void concurrencpp::tests::test_thread_executor_shutdown_join() { + // the executor returns only when all tasks are done. + auto executor = std::make_shared(); + object_observer observer; + const size_t task_count = 16; + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub(std::chrono::milliseconds(100))); + } + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_equal(observer.get_execution_count(), task_count); + assert_equal(observer.get_destruction_count(), task_count); +} + +void concurrencpp::tests::test_thread_executor_shutdown_more_than_once() { + auto executor = std::make_shared(); + for (size_t i = 0; i < 4; i++) { + executor->shutdown(); + } +} + +void concurrencpp::tests::test_thread_executor_shutdown() { + test_thread_executor_shutdown_method_access(); + test_thread_executor_shutdown_join(); + test_thread_executor_shutdown_more_than_once(); +} + +void concurrencpp::tests::test_thread_executor_max_concurrency_level() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->max_concurrency_level(), concurrencpp::details::consts::k_thread_executor_max_concurrency_level); +} + +void concurrencpp::tests::test_thread_executor_post_foreign() { + object_observer observer; + const size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); +} + +void concurrencpp::tests::test_thread_executor_post_inline() { + object_observer observer; + constexpr size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); +} + +void concurrencpp::tests::test_thread_executor_post() { + test_thread_executor_post_foreign(); + test_thread_executor_post_inline(); +} + +void concurrencpp::tests::test_thread_executor_submit_foreign() { + object_observer observer; + const size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_executor_submit_inline() { + object_observer observer; + constexpr size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector> results; + results.resize(task_count); + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + return results; + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), size_t(i)); + } +} + +void concurrencpp::tests::test_thread_executor_submit() { + test_thread_executor_submit_foreign(); + test_thread_executor_submit_inline(); +} + +void concurrencpp::tests::test_thread_executor_bulk_post_foreign() { + object_observer observer; + const size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); +} + +void concurrencpp::tests::test_thread_executor_bulk_post_inline() { + object_observer observer; + constexpr size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer]() mutable { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); +} + +void concurrencpp::tests::test_thread_executor_bulk_post() { + test_thread_executor_bulk_post_foreign(); + test_thread_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_thread_executor_bulk_submit_foreign() { + object_observer observer; + const size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_executor_bulk_submit_inline() { + object_observer observer; + constexpr size_t task_count = 128; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + return executor->bulk_submit(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + assert_execution_threads(observer.get_execution_map(), task_count); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_executor_bulk_submit() { + test_thread_executor_bulk_post_foreign(); + test_thread_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_thread_executor() { + tester tester("thread_executor test"); + + tester.add_step("shutdown", test_thread_executor_shutdown); + tester.add_step("name", test_thread_executor_name); + tester.add_step("max_concurrency_level", test_thread_executor_max_concurrency_level); + tester.add_step("post", test_thread_executor_post); + tester.add_step("submit", test_thread_executor_submit); + tester.add_step("bulk_post", test_thread_executor_bulk_post); + tester.add_step("bulk_submit", test_thread_executor_bulk_submit); + + tester.launch_test(); +} diff --git a/test/source/tests/executor_tests/thread_pool_executor_tests.cpp b/test/source/tests/executor_tests/thread_pool_executor_tests.cpp new file mode 100644 index 00000000..09587f34 --- /dev/null +++ b/test/source/tests/executor_tests/thread_pool_executor_tests.cpp @@ -0,0 +1,499 @@ +#include "concurrencpp/concurrencpp.h" + +#include "tests/all_tests.h" +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests { + void test_thread_pool_executor_name(); + + void test_thread_pool_executor_shutdown_coro_raii(); + void test_thread_pool_executor_shutdown_thread_join(); + void test_thread_pool_executor_shutdown_method_access(); + void test_thread_pool_executor_shutdown_method_more_than_once(); + void test_thread_pool_executor_shutdown(); + + void test_thread_pool_executor_post_foreign(); + void test_thread_pool_executor_post_inline(); + void test_thread_pool_executor_post(); + + void test_thread_pool_executor_submit_foreign(); + void test_thread_pool_executor_submit_inline(); + void test_thread_pool_executor_submit(); + + void test_thread_pool_executor_bulk_post_foreign(); + void test_thread_pool_executor_bulk_post_inline(); + void test_thread_pool_executor_bulk_post(); + + void test_thread_pool_executor_bulk_submit_foreign(); + void test_thread_pool_executor_bulk_submit_inline(); + void test_thread_pool_executor_bulk_submit(); + + void test_thread_pool_executor_enqueue_algorithm(); + void test_thread_pool_executor_dynamic_resizing(); +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_thread_pool_executor_name() { + const auto name = "abcde12345&*("; + auto executor = std::make_shared(name, 4, std::chrono::seconds(10)); + executor_shutdowner shutdowner(executor); + assert_equal(executor->name, name); +} + +void concurrencpp::tests::test_thread_pool_executor_shutdown_coro_raii() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared("threadpool", 1, std::chrono::seconds(4)); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + }); + + auto results = executor->bulk_submit(stubs); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), task_count); + + for (auto& result : results) { + assert_throws([&result] { + result.get(); + }); + } +} + +void concurrencpp::tests::test_thread_pool_executor_shutdown_thread_join() { + auto executor = std::make_shared("threadpool", 9, std::chrono::seconds(1)); + + for (size_t i = 0; i < 3; i++) { + executor->post([] { + }); + } + + for (size_t i = 0; i < 3; i++) { + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + }); + } + + // allow threads time to start working + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // 1/3 of the threads are waiting, 1/3 are working, 1/3 are idle. all should + // be joined when tp is shut-down. + executor->shutdown(); + assert_true(executor->shutdown_requested()); +} + +void concurrencpp::tests::test_thread_pool_executor_shutdown_method_access() { + auto executor = std::make_shared("threadpool", 4, std::chrono::seconds(10)); + assert_false(executor->shutdown_requested()); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_throws([executor] { + executor->enqueue(std::experimental::coroutine_handle {}); + }); + + assert_throws([executor] { + std::experimental::coroutine_handle<> array[4]; + std::span> span = array; + executor->enqueue(span); + }); +} + +void concurrencpp::tests::test_thread_pool_executor_shutdown_method_more_than_once() { + const size_t task_count = 64; + auto executor = std::make_shared("threadpool", 4, std::chrono::seconds(10)); + + for (size_t i = 0; i < task_count; i++) { + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(18)); + }); + } + + for (size_t i = 0; i < 4; i++) { + executor->shutdown(); + } +} + +void concurrencpp::tests::test_thread_pool_executor_shutdown() { + test_thread_pool_executor_shutdown_coro_raii(); + test_thread_pool_executor_shutdown_thread_join(); + test_thread_pool_executor_shutdown_method_access(); + test_thread_pool_executor_shutdown_method_more_than_once(); +} + +void concurrencpp::tests::test_thread_pool_executor_post_foreign() { + object_observer observer; + const size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); +} + +void concurrencpp::tests::test_thread_pool_executor_post_inline() { + object_observer observer; + constexpr size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); +} + +void concurrencpp::tests::test_thread_pool_executor_post() { + test_thread_pool_executor_post_foreign(); + test_thread_pool_executor_post_inline(); +} + +void concurrencpp::tests::test_thread_pool_executor_submit_foreign() { + object_observer observer; + const size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_pool_executor_submit_inline() { + object_observer observer; + constexpr size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector> results; + results.resize(task_count); + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + return results; + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), size_t(i)); + } +} + +void concurrencpp::tests::test_thread_pool_executor_submit() { + test_thread_pool_executor_submit_foreign(); + test_thread_pool_executor_submit_inline(); +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_post_foreign() { + object_observer observer; + const size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer]() mutable { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + executor->bulk_post(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_post() { + test_thread_pool_executor_bulk_post_foreign(); + test_thread_pool_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_submit_foreign() { + object_observer observer; + const size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_submit_inline() { + object_observer observer; + constexpr size_t task_count = 50'000; + const size_t worker_count = 6; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + return executor->bulk_submit(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_thread_pool_executor_bulk_submit() { + test_thread_pool_executor_bulk_submit_foreign(); + test_thread_pool_executor_bulk_submit_inline(); +} + +void concurrencpp::tests::test_thread_pool_executor_enqueue_algorithm() { + // case 1 : if an idle thread exists, enqueue it to the idle thread + { + object_observer observer; + const size_t worker_count = 6; + auto wc = std::make_shared(); + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < worker_count; i++) { + executor->post([wc, stub = observer.get_testing_stub()]() mutable { + wc->wait(); + stub(); + }); + } + + wc->notify(); + + observer.wait_execution_count(worker_count, std::chrono::seconds(6)); + + assert_equal(observer.get_execution_map().size(), worker_count); + + // make sure each task was executed on one task - a task wasn't posted to a + // working thread + for (const auto& execution_thread : observer.get_execution_map()) { + assert_equal(execution_thread.second, size_t(1)); + } + } + + // case 2 : if (1) is false => if this is a thread-pool thread, enqueue to + // self + { + object_observer observer; + auto wc = std::make_shared(); + auto executor = std::make_shared("threadpool", 2, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + executor->post([wc]() { + wc->wait(); + }); + + constexpr size_t task_count = 1'024; + + executor->post([&observer, executor] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + }); + + observer.wait_execution_count(task_count, std::chrono::minutes(1)); + observer.wait_destruction_count(task_count, std::chrono::minutes(1)); + + assert_equal(observer.get_execution_map().size(), size_t(1)); + + wc->notify(); + } + + // case 3 : if (2) is false, choose a worker using round robin + { + const size_t task_count = 1'024; + const size_t worker_count = 2; + object_observer observer; + auto wc = std::make_shared(); + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < worker_count; i++) { + executor->post([wc]() { + wc->wait(); + }); + } + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + + wc->notify(); + + observer.wait_execution_count(task_count, std::chrono::minutes(1)); + observer.wait_destruction_count(task_count, std::chrono::minutes(1)); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(2)); + + auto worker_it = execution_map.begin(); + assert_bigger(worker_it->second, task_count / 10); + + ++worker_it; + assert_bigger(worker_it->second, task_count / 10); + } +} + +void concurrencpp::tests::test_thread_pool_executor_dynamic_resizing() { + // if the workers are only waiting - notify them + { + const size_t worker_count = 4; + const size_t iterations = 4; + const size_t task_count = 1'024; + object_observer observer; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(5)); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < iterations; i++) { + for (size_t j = 0; j < task_count; j++) { + executor->post(observer.get_testing_stub()); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(350)); + + // in between, threads are waiting for an event (abort/task) + } + + observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); + observer.wait_destruction_count(task_count * iterations, std::chrono::minutes(1)); + + // if all the tasks were launched by <> workers, then no new + // workers were injected + assert_equal(observer.get_execution_map().size(), worker_count); + } + + // case 2 : if max_idle_time reached, idle threads exit. new threads are + // injeced when new tasks arrive + { + const size_t iterations = 4; + const size_t worker_count = 4; + const size_t task_count = 4'000; + object_observer observer; + auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(1)); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < iterations; i++) { + for (size_t j = 0; j < task_count; j++) { + executor->post(observer.get_testing_stub()); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1250)); + + // in between, threads are idling + } + + observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); + observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); + + /* + If all the tasks were executed by <> * iterations + workers, then in every iteration a new set of threads was injected, + meaning that the previous set of threads had exited. + */ + assert_equal(observer.get_execution_map().size(), worker_count * iterations); + } +} + +void concurrencpp::tests::test_thread_pool_executor() { + tester tester("thread_pool_executor test"); + + tester.add_step("name", test_thread_pool_executor_name); + tester.add_step("shutdown", test_thread_pool_executor_shutdown); + + tester.add_step("post", test_thread_pool_executor_post); + tester.add_step("submit", test_thread_pool_executor_submit); + tester.add_step("bulk_post", test_thread_pool_executor_bulk_post); + tester.add_step("bulk_submit", test_thread_pool_executor_bulk_submit); + tester.add_step("enqueuing algorithm", test_thread_pool_executor_enqueue_algorithm); + tester.add_step("dynamic resizing", test_thread_pool_executor_dynamic_resizing); + + tester.launch_test(); +} diff --git a/test/source/tests/executor_tests/worker_thread_executor_tests.cpp b/test/source/tests/executor_tests/worker_thread_executor_tests.cpp new file mode 100644 index 00000000..973a86c3 --- /dev/null +++ b/test/source/tests/executor_tests/worker_thread_executor_tests.cpp @@ -0,0 +1,376 @@ +#include "concurrencpp/concurrencpp.h" + +#include "tests/all_tests.h" + +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" + +#include "concurrencpp/executors/constants.h" + +namespace concurrencpp::tests { + void test_worker_thread_executor_name(); + + void test_worker_thread_executor_shutdown_method_access(); + void test_worker_thread_executor_shutdown_thread_join(); + void test_worker_thread_executor_shutdown_coro_raii(); + void test_worker_thread_executor_shutdown_coro_more_than_once(); + void test_worker_thread_executor_shutdown(); + + void test_worker_thread_executor_max_concurrency_level(); + + void test_worker_thread_executor_post_foreign(); + void test_worker_thread_executor_post_inline(); + void test_worker_thread_executor_post(); + + void test_worker_thread_executor_submit_foreign(); + void test_worker_thread_executor_submit_inline(); + void test_worker_thread_executor_submit(); + + void test_worker_thread_executor_bulk_post_foreign(); + void test_worker_thread_executor_bulk_post_inline(); + void test_worker_thread_executor_bulk_post(); + + void test_worker_thread_executor_bulk_submit_foreign(); + void test_worker_thread_executor_bulk_submit_inline(); + void test_worker_thread_executor_bulk_submit(); +} // namespace concurrencpp::tests + +using concurrencpp::details::thread; + +void concurrencpp::tests::test_worker_thread_executor_name() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->name, concurrencpp::details::consts::k_worker_thread_executor_name); +} + +void concurrencpp::tests::test_worker_thread_executor_shutdown_method_access() { + auto executor = std::make_shared(); + assert_false(executor->shutdown_requested()); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_throws([executor] { + executor->enqueue(std::experimental::coroutine_handle {}); + }); + + assert_throws([executor] { + std::experimental::coroutine_handle<> array[4]; + std::span> span = array; + executor->enqueue(span); + }); +} + +void concurrencpp::tests::test_worker_thread_executor_shutdown_thread_join() { + auto executor = std::make_shared(); + + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + }); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); +} + +void concurrencpp::tests::test_worker_thread_executor_shutdown_coro_raii() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + }); + + auto results = executor->bulk_submit(stubs); + + executor->shutdown(); + assert_true(executor->shutdown_requested()); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), task_count); + + for (auto& result : results) { + assert_throws([&result] { + result.get(); + }); + } +} + +void concurrencpp::tests::test_worker_thread_executor_shutdown_coro_more_than_once() { + const size_t task_count = 64; + auto executor = std::make_shared(); + + for (size_t i = 0; i < task_count; i++) { + executor->post([] { + std::this_thread::sleep_for(std::chrono::milliseconds(18)); + }); + } + + for (size_t i = 0; i < 4; i++) { + executor->shutdown(); + } +} + +void concurrencpp::tests::test_worker_thread_executor_shutdown() { + test_worker_thread_executor_shutdown_method_access(); + test_worker_thread_executor_shutdown_coro_raii(); + test_worker_thread_executor_shutdown_thread_join(); + test_worker_thread_executor_shutdown_coro_more_than_once(); +} + +void concurrencpp::tests::test_worker_thread_executor_max_concurrency_level() { + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + assert_equal(executor->max_concurrency_level(), 1); +} + +void concurrencpp::tests::test_worker_thread_executor_post_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_worker_thread_executor_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer] { + for (size_t i = 0; i < task_count; i++) { + executor->post(observer.get_testing_stub()); + } + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_worker_thread_executor_post() { + test_worker_thread_executor_post_foreign(); + test_worker_thread_executor_post_inline(); +} + +void concurrencpp::tests::test_worker_thread_executor_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector> results; + results.resize(task_count); + + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_worker_thread_executor_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector> results; + results.resize(task_count); + for (size_t i = 0; i < task_count; i++) { + results[i] = executor->submit(observer.get_testing_stub(i)); + } + + return results; + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), size_t(i)); + } +} + +void concurrencpp::tests::test_worker_thread_executor_submit() { + test_worker_thread_executor_submit_foreign(); + test_worker_thread_executor_submit_inline(); +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_post_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_post_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + executor->post([executor, &observer]() mutable { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub()); + } + + executor->bulk_post(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_post() { + test_worker_thread_executor_bulk_post_foreign(); + test_worker_thread_executor_bulk_post_inline(); +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_submit_foreign() { + object_observer observer; + const size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + auto results = executor->bulk_submit(stubs); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_submit_inline() { + object_observer observer; + constexpr size_t task_count = 1'024; + auto executor = std::make_shared(); + executor_shutdowner shutdown(executor); + + auto results_res = executor->submit([executor, &observer] { + std::vector stubs; + stubs.reserve(task_count); + + for (size_t i = 0; i < task_count; i++) { + stubs.emplace_back(observer.get_testing_stub(i)); + } + + return executor->bulk_submit(stubs); + }); + + assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); + assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); + + const auto& execution_map = observer.get_execution_map(); + + assert_equal(execution_map.size(), size_t(1)); + assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); + + auto results = results_res.get(); + for (size_t i = 0; i < task_count; i++) { + assert_equal(results[i].get(), i); + } +} + +void concurrencpp::tests::test_worker_thread_executor_bulk_submit() { + test_worker_thread_executor_bulk_submit_foreign(); + test_worker_thread_executor_bulk_submit_inline(); +} + +void concurrencpp::tests::test_worker_thread_executor() { + tester tester("worker_thread_executor test"); + + tester.add_step("name", test_worker_thread_executor_name); + tester.add_step("shutdown", test_worker_thread_executor_shutdown); + tester.add_step("max_concurrency_level", test_worker_thread_executor_max_concurrency_level); + tester.add_step("post", test_worker_thread_executor_post); + tester.add_step("submit", test_worker_thread_executor_submit); + tester.add_step("bulk_post", test_worker_thread_executor_bulk_post); + tester.add_step("bulk_submit", test_worker_thread_executor_bulk_submit); + + tester.launch_test(); +} diff --git a/test/source/tests/result_tests/make_result_tests.cpp b/test/source/tests/result_tests/make_result_tests.cpp new file mode 100644 index 00000000..1569b987 --- /dev/null +++ b/test/source/tests/result_tests/make_result_tests.cpp @@ -0,0 +1,73 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests { + template + void test_make_ready_result_impl(); + void test_make_ready_result(); + + void test_make_exceptional_result(); +} // namespace concurrencpp::tests + +template +void concurrencpp::tests::test_make_ready_result_impl() { + result result; + + if constexpr (std::is_same_v) { + result = concurrencpp::make_ready_result(); + } else { + result = make_ready_result(result_factory::get()); + } + + test_ready_result_result(std::move(result)); +} + +void concurrencpp::tests::test_make_ready_result() { + test_make_ready_result_impl(); + test_make_ready_result_impl(); + test_make_ready_result_impl(); + test_make_ready_result_impl(); + test_make_ready_result_impl(); +} + +void concurrencpp::tests::test_make_exceptional_result() { + assert_throws_with_error_message( + [] { + make_exceptional_result({}); + }, + concurrencpp::details::consts::k_make_exceptional_result_exception_null_error_msg); + + auto assert_ok = [](result& result) { + assert_equal(result.status(), result_status::exception); + try { + result.get(); + } catch (const std::runtime_error& re) { + assert_equal(std::string("error"), re.what()); + return; + } catch (...) { + } + assert_false(true); + }; + + result res = make_exceptional_result(std::runtime_error("error")); + assert_ok(res); + + res = make_exceptional_result(std::make_exception_ptr(std::runtime_error("error"))); + assert_ok(res); +} + +void concurrencpp::tests::test_make_result() { + tester tester("make_result test"); + + tester.add_step("make_ready_result", test_make_ready_result); + tester.add_step("make_exceptional_result", test_make_exceptional_result); + + tester.launch_test(); +} diff --git a/test/source/tests/result_tests/result_await_tests.cpp b/test/source/tests/result_tests/result_await_tests.cpp new file mode 100644 index 00000000..c556a23e --- /dev/null +++ b/test/source/tests/result_tests/result_await_tests.cpp @@ -0,0 +1,475 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/result_factory.h" +#include "tests/test_utils/test_ready_result.h" +#include "tests/test_utils/test_executors.h" +#include "tests/test_utils/executor_shutdowner.h" +#include "tests/test_utils/proxy_coro.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" + +namespace concurrencpp::tests { + template + result test_result_await_ready_val(); + + template + result test_result_await_ready_err(); + + template + result test_result_await_not_ready_val(std::shared_ptr executor); + + template + result test_result_await_not_ready_err(std::shared_ptr executor); + + template + void test_result_await_impl(); + void test_result_await(); + + template + result test_result_await_via_ready_val(std::shared_ptr executor); + + template + result test_result_await_via_ready_err(std::shared_ptr executor); + + template + result test_result_await_via_ready_val_force_rescheduling(std::shared_ptr executor); + + template + result test_result_await_via_ready_err_force_rescheduling(std::shared_ptr executor); + + template + result test_result_await_via_ready_val_force_rescheduling_executor_threw(); + + template + result test_result_await_via_ready_err_force_rescheduling_executor_threw(); + + template + result test_result_await_via_not_ready_val(std::shared_ptr executor); + template + result test_result_await_via_not_ready_err(std::shared_ptr executor); + + template + result test_result_await_via_not_ready_val_executor_threw(std::shared_ptr executor); + template + result test_result_await_via_not_ready_err_executor_threw(std::shared_ptr executor); + + template + void test_result_await_via_impl(); + void test_result_await_via(); +} // namespace concurrencpp::tests + +using concurrencpp::result; +using concurrencpp::result_promise; +using namespace std::chrono; + +/* + At this point, we know result::resolve(_via) works perfectly so we wrap + co_await operator in another resolving-coroutine and we continue testing it + as a resolve test. co_await will do its thing and it'll forward the + result/exception to a resolving coroutine. since result::resolve(_via) works + with no bugs, every failure is caused by result::co_await +*/ + +template +result concurrencpp::tests::test_result_await_ready_val() { + auto result = result_factory::make_ready(); + const auto thread_id_0 = std::this_thread::get_id(); + + auto result_proxy = [result = std::move(result)]() mutable -> concurrencpp::result { + co_return co_await result; + }; + + auto done_result = co_await result_proxy().resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_await_ready_err() { + random randomizer; + const auto id = randomizer(); + auto result = make_exceptional_result(costume_exception(id)); + + const auto thread_id_0 = std::this_thread::get_id(); + + auto result_proxy = [result = std::move(result)]() mutable -> concurrencpp::result { + co_return co_await result; + }; + + auto done_result = co_await result_proxy().resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_await_not_ready_val(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + executor->set_rp_value(std::move(rp)); + + auto result_proxy = [result = std::move(result)]() mutable -> concurrencpp::result { + co_return co_await result; + }; + + auto done_result = co_await result_proxy().resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // the thread that set the value is the + // thread that resumes the coroutine. + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_await_not_ready_err(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + const auto id = executor->set_rp_err(std::move(rp)); + + auto result_proxy = [result = std::move(result)]() mutable -> concurrencpp::result { + co_return co_await result; + }; + + auto done_result = co_await result_proxy().resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // the thread that set the value is the + // thread that resumes the coroutine. + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +void concurrencpp::tests::test_result_await_impl() { + // empty result throws + { + assert_throws([] { + result result; + result.operator co_await(); + }); + } + + // ready result resumes immediately in the awaiting thread with no + // rescheduling + test_result_await_ready_val().get(); + + // ready result resumes immediately in the awaiting thread with no + // rescheduling + test_result_await_ready_err().get(); + + // if value or exception are not available - suspend and resume in the setting + // thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_not_ready_val(te).get(); + } + + // if value or exception are not available - suspend and resume in the setting + // thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_not_ready_err(te).get(); + } +} + +void concurrencpp::tests::test_result_await() { + test_result_await_impl(); + test_result_await_impl(); + test_result_await_impl(); + test_result_await_impl(); + test_result_await_impl(); +} + +template +result concurrencpp::tests::test_result_await_via_ready_val(std::shared_ptr executor) { + auto result = result_factory::make_ready(); + + const auto thread_id_0 = std::this_thread::get_id(); + + proxy_coro coro(std::move(result), executor, false); + auto done_result = co_await coro().resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + assert_false(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_await_via_ready_err(std::shared_ptr executor) { + random randomizer; + const auto id = randomizer(); + auto result = make_exceptional_result(costume_exception(id)); + + const auto thread_id_0 = std::this_thread::get_id(); + + proxy_coro coro(std::move(result), executor, false); + auto done_result = co_await coro().resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + assert_false(executor->scheduled_async()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_await_via_ready_val_force_rescheduling(std::shared_ptr executor) { + auto result = result_factory::make_ready(); + + proxy_coro coro(std::move(result), executor, true); + auto done_result = co_await coro().resolve(); + + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_await_via_ready_err_force_rescheduling(std::shared_ptr executor) { + random randomizer; + auto id = static_cast(randomizer()); + auto result = make_exceptional_result(costume_exception(id)); + + proxy_coro coro(std::move(result), executor, true); + auto done_result = co_await coro().resolve(); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_await_via_ready_val_force_rescheduling_executor_threw() { + auto result = result_factory::make_ready(); + auto te = std::make_shared(); + + auto thread_id_before = std::this_thread::get_id(); + + try { + proxy_coro coro(std::move(result), te, true); + auto done_result = co_await coro().resolve(); + co_await done_result; + } catch (const executor_enqueue_exception&) { + // do nothing + } catch (...) { + assert_false(true); + } + + auto thread_id_after = std::this_thread::get_id(); + + assert_equal(thread_id_before, thread_id_after); + assert_false(static_cast(result)); +} + +template +result concurrencpp::tests::test_result_await_via_ready_err_force_rescheduling_executor_threw() { + auto result = result_factory::make_exceptional(); + auto te = std::make_shared(); + + auto thread_id_before = std::this_thread::get_id(); + + try { + proxy_coro coro(std::move(result), te, true); + auto done_result = co_await coro().resolve(); + assert_true(static_cast(done_result)); + + co_await done_result; + } catch (const executor_enqueue_exception&) { + // do nothing + } catch (...) { + assert_false(true); + } + + auto thread_id_after = std::this_thread::get_id(); + + assert_equal(thread_id_before, thread_id_after); + assert_false(static_cast(result)); +} + +template +result concurrencpp::tests::test_result_await_via_not_ready_val(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + executor->set_rp_value(std::move(rp)); + + proxy_coro coro(std::move(result), executor, true); + auto done_result = co_await coro().resolve(); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_await_via_not_ready_err(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + const auto id = executor->set_rp_err(std::move(rp)); + + proxy_coro coro(std::move(result), executor, true); + auto done_result = co_await coro().resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_async()); + assert_false(executor->scheduled_inline()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_await_via_not_ready_val_executor_threw(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + auto te = std::make_shared(); + + executor->set_rp_value(std::move(rp)); + + proxy_coro coro(std::move(result), te, true); + auto done_result = co_await coro().resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // since te threw, execution is + // resumed in ex::m_setting_thread + test_executor_error_thrown(std::move(done_result), te); +} + +template +result concurrencpp::tests::test_result_await_via_not_ready_err_executor_threw(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + auto te = std::make_shared(); + + const auto id = executor->set_rp_err(std::move(rp)); + (void)id; + + proxy_coro coro(std::move(result), te, true); + auto done_result = co_await coro().resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // since te threw, execution is + // resumed in ex::m_setting_thread + test_executor_error_thrown(std::move(done_result), te); +} + +template +void concurrencpp::tests::test_result_await_via_impl() { + // empty result throws + assert_throws([] { + result result; + auto executor = make_test_executor(); + result.await_via(executor); + }); + + assert_throws_with_error_message( + [] { + auto result = result_factory::make_ready(); + result.await_via({}, true); + }, + concurrencpp::details::consts::k_result_await_via_executor_null_error_msg); + + // if the result is ready by value, and force_rescheduling = false, resume in + // the calling thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_ready_val(te).get(); + } + + // if the result is ready by exception, and force_rescheduling = false, resume + // in the calling thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_ready_err(te).get(); + } + + // if the result is ready by value, and force_rescheduling = true, forcefully + // resume execution through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_ready_val_force_rescheduling(te).get(); + } + + // if the result is ready by exception, and force_rescheduling = true, + // forcefully resume execution through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_ready_err_force_rescheduling(te).get(); + } + + // if execution is rescheduled by a throwing executor, reschdule inline and + // throw executor_exception + test_result_await_via_ready_val_force_rescheduling_executor_threw().get(); + + // if execution is rescheduled by a throwing executor, reschdule inline and + // throw executor_exception + test_result_await_via_ready_err_force_rescheduling_executor_threw().get(); + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_not_ready_val(te).get(); + } + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_not_ready_err(te).get(); + } + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_not_ready_val_executor_threw(te).get(); + } + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_await_via_not_ready_err_executor_threw(te).get(); + } +} + +void concurrencpp::tests::test_result_await_via() { + test_result_await_via_impl(); + test_result_await_via_impl(); + test_result_await_via_impl(); + test_result_await_via_impl(); + test_result_await_via_impl(); +} + +void concurrencpp::tests::test_result_await_all() { + tester tester("result::await, result::await_via test"); + + tester.add_step("await", test_result_await); + tester.add_step("await_via", test_result_await_via); + + tester.launch_test(); +} diff --git a/tests/tests/result_tests/result_promise_tests.cpp b/test/source/tests/result_tests/result_promise_tests.cpp similarity index 90% rename from tests/tests/result_tests/result_promise_tests.cpp rename to test/source/tests/result_tests/result_promise_tests.cpp index b27ced37..c9bb03fc 100644 --- a/tests/tests/result_tests/result_promise_tests.cpp +++ b/test/source/tests/result_tests/result_promise_tests.cpp @@ -1,12 +1,12 @@ -#include "concurrencpp.h" -#include "../all_tests.h" +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" -#include "../test_utils/test_ready_result.h" +#include "tests/test_utils/test_ready_result.h" -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" -#include "../../helpers/object_observer.h" +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" +#include "helpers/object_observer.h" #include @@ -52,7 +52,7 @@ namespace concurrencpp::tests { template void test_rp_assignment_operator_impl(); void test_rp_assignment_operator(); -} +} // namespace concurrencpp::tests using concurrencpp::result_promise; using concurrencpp::result; @@ -87,7 +87,9 @@ void concurrencpp::tests::test_result_promise_destructor_impl() { assert_true(static_cast(result)); assert_equal(result.status(), result_status::exception); - assert_throws([&] { result.get(); }); + assert_throws([&] { + result.get(); + }); } void concurrencpp::tests::test_result_promise_RAII_impl() { @@ -125,18 +127,22 @@ void concurrencpp::tests::test_result_promise_destructor() { template void concurrencpp::tests::test_result_promise_get_result_impl() { - //trying to get the future more than once throws + // trying to get the future more than once throws ::concurrencpp::result_promise rp; auto result = rp.get_result(); assert_true(static_cast(result)); assert_equal(result.status(), concurrencpp::result_status::idle); - assert_throws([&] { rp.get_result(); }); + assert_throws([&] { + rp.get_result(); + }); - //empty rp should throw + // empty rp should throw auto dummy = std::move(rp); - assert_throws([&] { rp.get_result(); }); + assert_throws([&] { + rp.get_result(); + }); } void concurrencpp::tests::test_result_promise_get_result() { @@ -150,14 +156,14 @@ void concurrencpp::tests::test_result_promise_get_result() { template void concurrencpp::tests::test_result_promise_set_value_impl(const arguments_tuple_type& tuple_args) { auto set_rp = [](auto rp, auto tuple) { - auto setter = [rp = std::move(rp)](auto&& ... args) mutable { + auto setter = [rp = std::move(rp)](auto&&... args) mutable { rp.set_result(args...); }; std::apply(setter, tuple); }; - //basic test: + // basic test: { concurrencpp::result_promise rp; auto result = rp.get_result(); @@ -167,7 +173,7 @@ void concurrencpp::tests::test_result_promise_set_value_impl(const arguments_tup test_ready_result_result(std::move(result)); } - //async test + // async test { concurrencpp::result_promise rp; auto result = rp.get_result(); @@ -183,11 +189,13 @@ void concurrencpp::tests::test_result_promise_set_value_impl(const arguments_tup thread.join(); } - //if an exception is thrown inside set_value + // if an exception is thrown inside set_value //(by building an object in place) the future is un-ready { struct throws_on_construction { - throws_on_construction(int, int) { throw std::exception(); } + throws_on_construction(int, int) { + throw std::exception(); + } }; concurrencpp::result_promise rp; @@ -202,14 +210,13 @@ void concurrencpp::tests::test_result_promise_set_value_impl(const arguments_tup assert_equal(result.status(), result_status::idle); } - //setting result to an empty rp throws + // setting result to an empty rp throws { concurrencpp::result_promise rp; auto dummy = std::move(rp); - assert_throws([ - rp = std::move(rp), - set_rp, - tuple_args]() mutable { set_rp(std::move(rp), tuple_args); }); + assert_throws([rp = std::move(rp), set_rp, tuple_args]() mutable { + set_rp(std::move(rp), tuple_args); + }); } } @@ -227,11 +234,11 @@ void concurrencpp::tests::test_result_promise_set_value() { test_result_promise_set_value_impl(empty_tuple); auto& expected_int_ref_result = result_factory::get(); - std::tuple int_ref_tuple = { expected_int_ref_result }; + std::tuple int_ref_tuple = {expected_int_ref_result}; test_result_promise_set_value_impl(int_ref_tuple); auto& expected_str_ref_result = result_factory::get(); - std::tuple str_ref_tuple = { expected_str_ref_result }; + std::tuple str_ref_tuple = {expected_str_ref_result}; test_result_promise_set_value_impl(str_ref_tuple); } @@ -239,7 +246,7 @@ template void concurrencpp::tests::test_result_promise_set_exception_impl() { random randomizer; - //basic test: + // basic test: { concurrencpp::result_promise rp; auto result = rp.get_result(); @@ -251,7 +258,7 @@ void concurrencpp::tests::test_result_promise_set_exception_impl() { test_ready_result_costume_exception(std::move(result), id); } - //async test: + // async test: { concurrencpp::result_promise rp; auto result = rp.get_result(); @@ -268,7 +275,7 @@ void concurrencpp::tests::test_result_promise_set_exception_impl() { thread.join(); } - { //can't set result to an empty rp + { // can't set result to an empty rp result_promise rp; auto dummy = std::move(rp); @@ -279,7 +286,7 @@ void concurrencpp::tests::test_result_promise_set_exception_impl() { assert_throws(setter); } - //null exception-ptr throws std::invalid_argument + // null exception-ptr throws std::invalid_argument { result_promise rp; @@ -384,7 +391,9 @@ void concurrencpp::tests::test_rp_assignment_operator_impl_empty_to_non_empty() assert_false(static_cast(rp2)); assert_equal(result.status(), result_status::exception); - assert_throws([&] { result.get(); }); + assert_throws([&] { + result.get(); + }); } template @@ -400,12 +409,14 @@ void concurrencpp::tests::test_rp_assignment_operator_impl_non_empty_to_non_empt assert_false(static_cast(rp2)); assert_equal(result1.status(), result_status::exception); - assert_throws([&] { result1.get(); }); + assert_throws([&] { + result1.get(); + }); assert_equal(result2.status(), result_status::idle); } -template +template void concurrencpp::tests::test_rp_assignment_operator_assign_to_self() { result_promise rp; rp = std::move(rp); @@ -441,4 +452,4 @@ void concurrencpp::tests::test_result_promise() { tester.add_step("operator =", test_rp_assignment_operator); tester.launch_test(); -} \ No newline at end of file +} diff --git a/test/source/tests/result_tests/result_resolve_tests.cpp b/test/source/tests/result_tests/result_resolve_tests.cpp new file mode 100644 index 00000000..2a7b5d31 --- /dev/null +++ b/test/source/tests/result_tests/result_resolve_tests.cpp @@ -0,0 +1,444 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/result_factory.h" +#include "tests/test_utils/test_ready_result.h" +#include "tests/test_utils/test_executors.h" +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" + +namespace concurrencpp::tests { + template + result test_result_resolve_ready_val(); + + template + result test_result_resolve_ready_err(); + + template + result test_result_resolve_not_ready_val(std::shared_ptr executor); + + template + result test_result_resolve_not_ready_err(std::shared_ptr executor); + + template + void test_result_resolve_impl(); + void test_result_resolve(); + + template + result test_result_resolve_via_ready_val(std::shared_ptr executor); + + template + result test_result_resolve_via_ready_err(std::shared_ptr executor); + + template + result test_result_resolve_via_ready_val_force_rescheduling(std::shared_ptr executor); + + template + result test_result_resolve_via_ready_err_force_rescheduling(std::shared_ptr executor); + + template + result test_result_resolve_via_ready_val_force_rescheduling_executor_threw(); + + template + result test_result_resolve_via_ready_err_force_rescheduling_executor_threw(); + + template + result test_result_resolve_via_not_ready_val(std::shared_ptr executor); + template + result test_result_resolve_via_not_ready_err(std::shared_ptr executor); + + template + result test_result_resolve_via_not_ready_val_executor_threw(std::shared_ptr executor); + template + result test_result_resolve_via_not_ready_err_executor_threw(std::shared_ptr executor); + + template + void test_result_resolve_via_impl(); + void test_result_resolve_via(); + +} // namespace concurrencpp::tests + +using concurrencpp::result; +using concurrencpp::result_promise; +using namespace std::chrono; + +template +result concurrencpp::tests::test_result_resolve_ready_val() { + auto result = result_factory::make_ready(); + + const auto thread_id_0 = std::this_thread::get_id(); + + auto done_result = co_await result.resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_resolve_ready_err() { + random randomizer; + const auto id = randomizer(); + auto result = make_exceptional_result(costume_exception(id)); + + const auto thread_id_0 = std::this_thread::get_id(); + + auto done_result = co_await result.resolve(); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_resolve_not_ready_val(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + executor->set_rp_value(std::move(rp)); + + auto done_result = co_await result.resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // the thread that set the value is the + // thread that resumes the coroutine. + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_resolve_not_ready_err(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + const auto id = executor->set_rp_err(std::move(rp)); + + auto done_result = co_await result.resolve(); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_inline()); // the thread that set the err is the + // thread that resumes the coroutine. + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +void concurrencpp::tests::test_result_resolve_impl() { + // empty result throws + { + assert_throws([] { + result result; + result.resolve(); + }); + } + + // ready result resumes immediately in the awaiting thread with no + // rescheduling + test_result_resolve_ready_val().get(); + + // ready result resumes immediately in the awaiting thread with no + // rescheduling + test_result_resolve_ready_err().get(); + + // if value or exception are not available - suspend and resume in the setting + // thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_not_ready_val(te).get(); + } + + // if value or exception are not available - suspend and resume in the setting + // thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_not_ready_err(te).get(); + } +} + +void concurrencpp::tests::test_result_resolve() { + test_result_resolve_impl(); + test_result_resolve_impl(); + test_result_resolve_impl(); + test_result_resolve_impl(); + test_result_resolve_impl(); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_val(std::shared_ptr executor) { + auto result = result_factory::make_ready(); + + const auto thread_id_0 = std::this_thread::get_id(); + + auto done_result = co_await result.resolve_via(executor, false); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + assert_false(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_err(std::shared_ptr executor) { + random randomizer; + const auto id = randomizer(); + auto result = make_exceptional_result(costume_exception(id)); + + const auto thread_id_0 = std::this_thread::get_id(); + + auto done_result = co_await result.resolve_via(executor, false); + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_false(static_cast(result)); + assert_equal(thread_id_0, thread_id_1); + assert_false(executor->scheduled_async()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_val_force_rescheduling(std::shared_ptr executor) { + auto result = result_factory::make_ready(); + + auto done_result = co_await result.resolve_via(executor, true); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_err_force_rescheduling(std::shared_ptr executor) { + random randomizer; + auto id = static_cast(randomizer()); + auto result = make_exceptional_result(costume_exception(id)); + + auto done_result = co_await result.resolve_via(executor, true); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_val_force_rescheduling_executor_threw() { + auto result = result_factory::make_ready(); + auto te = std::make_shared(); + + const auto thread_id_0 = std::this_thread::get_id(); + + try { + auto done_result = co_await result.resolve_via(te, true); + } catch (const executor_enqueue_exception&) { + // do nothing + } catch (...) { + assert_false(true); + } + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_equal(thread_id_0, thread_id_1); + assert_false(static_cast(result)); +} + +template +result concurrencpp::tests::test_result_resolve_via_ready_err_force_rescheduling_executor_threw() { + auto result = result_factory::make_exceptional(); + auto te = std::make_shared(); + + const auto thread_id_0 = std::this_thread::get_id(); + + try { + auto done_result = co_await result.resolve_via(te, true); + } catch (const executor_enqueue_exception&) { + // do nothing + } catch (...) { + assert_false(true); + } + + const auto thread_id_1 = std::this_thread::get_id(); + + assert_equal(thread_id_0, thread_id_1); + assert_false(static_cast(result)); +} + +template +result concurrencpp::tests::test_result_resolve_via_not_ready_val(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + executor->set_rp_value(std::move(rp)); + + auto done_result = co_await result.resolve_via(executor, false); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_inline()); + assert_true(executor->scheduled_async()); + test_ready_result_result(std::move(done_result)); +} + +template +result concurrencpp::tests::test_result_resolve_via_not_ready_err(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + const auto id = executor->set_rp_err(std::move(rp)); + + auto done_result = co_await result.resolve_via(executor, false); + + assert_false(static_cast(result)); + assert_true(executor->scheduled_async()); + assert_false(executor->scheduled_inline()); + test_ready_result_costume_exception(std::move(done_result), id); +} + +template +result concurrencpp::tests::test_result_resolve_via_not_ready_val_executor_threw(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + auto te = std::make_shared(); + + executor->set_rp_value(std::move(rp)); + + auto done_result = co_await result.resolve_via(te, false); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_async()); + assert_true(executor->scheduled_inline()); // since te threw, execution is + // resumed in ex::m_setting_thread + test_executor_error_thrown(std::move(done_result), te); +} + +template +result concurrencpp::tests::test_result_resolve_via_not_ready_err_executor_threw(std::shared_ptr executor) { + result_promise rp; + auto result = rp.get_result(); + + auto te = std::make_shared(); + + const auto id = executor->set_rp_err(std::move(rp)); + (void)id; + + auto done_result = co_await result.resolve_via(te, false); + + assert_false(static_cast(result)); + assert_false(executor->scheduled_async()); + assert_true(executor->scheduled_inline()); // since te threw, execution is + // resumed in ex::m_setting_thread + test_executor_error_thrown(std::move(done_result), te); +} + +template +void concurrencpp::tests::test_result_resolve_via_impl() { + // empty result throws + assert_throws([] { + result result; + auto executor = make_test_executor(); + result.resolve_via(executor); + }); + + assert_throws_with_error_message( + [] { + auto result = result_factory::make_ready(); + result.resolve_via({}, true); + }, + concurrencpp::details::consts::k_result_resolve_via_executor_null_error_msg); + + // if the result is ready by value, and force_rescheduling = false, resume in + // the calling thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_ready_val(te).get(); + } + + // if the result is ready by exception, and force_rescheduling = false, resume + // in the calling thread + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_ready_err(te).get(); + } + + // if the result is ready by value, and force_rescheduling = true, forcefully + // resume execution through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_ready_val_force_rescheduling(te).get(); + } + + // if the result is ready by exception, and force_rescheduling = true, + // forcefully resume execution through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_ready_err_force_rescheduling(te).get(); + } + + // if execution is rescheduled by a throwing executor, reschedule inline and + // throw executor_exception + test_result_resolve_via_ready_val_force_rescheduling_executor_threw().get(); + + // if execution is rescheduled by a throwing executor, reschedule inline and + // throw executor_exception + test_result_resolve_via_ready_err_force_rescheduling_executor_threw().get(); + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_not_ready_val(te).get(); + } + + // if result is not ready - the execution resumes through the executor + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_not_ready_err(te).get(); + } + + // if result is not ready & executor threw - resume inline and throw + // concurrencpp::executor_exception + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_not_ready_val_executor_threw(te).get(); + } + + // if result is not ready & executor threw - resume inline and throw + // concurrencpp::executor_exception + { + auto te = make_test_executor(); + executor_shutdowner es(te); + test_result_resolve_via_not_ready_err_executor_threw(te).get(); + } +} + +void concurrencpp::tests::test_result_resolve_via() { + test_result_resolve_via_impl(); + test_result_resolve_via_impl(); + test_result_resolve_via_impl(); + test_result_resolve_via_impl(); + test_result_resolve_via_impl(); +} + +void concurrencpp::tests::test_result_resolve_all() { + tester tester("result::resolve, result::resolve_via test"); + + tester.add_step("reslove", test_result_resolve); + tester.add_step("reslove_via", test_result_resolve_via); + + tester.launch_test(); +} diff --git a/test/source/tests/result_tests/result_tests.cpp b/test/source/tests/result_tests/result_tests.cpp new file mode 100644 index 00000000..19431747 --- /dev/null +++ b/test/source/tests/result_tests/result_tests.cpp @@ -0,0 +1,679 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" +#include "tests/test_utils/result_factory.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" + +namespace concurrencpp::tests { + template + void test_result_constructor_impl(); + void test_result_constructor(); + + template + void test_result_status_impl(); + void test_result_status(); + + template + void test_result_get_impl(); + void test_result_get(); + + template + void test_result_wait_impl(); + void test_result_wait(); + + template + void test_result_wait_for_impl(); + void test_result_wait_for(); + + template + void test_result_wait_until_impl(); + void test_result_wait_until(); + + template + void test_result_assignment_operator_empty_to_empty(); + template + void test_result_assignment_operator_non_empty_to_non_empty(); + template + void test_result_assignment_operator_empty_to_non_empty(); + template + void test_result_assignment_operator_non_empty_to_empty(); + template + void test_result_assignment_operator_assign_to_self(); + template + void test_result_assignment_operator_impl(); + void test_result_assignment_operator(); +} // namespace concurrencpp::tests + +using concurrencpp::result; +using concurrencpp::result_promise; +using namespace std::chrono; +using namespace concurrencpp::tests; + +template +void concurrencpp::tests::test_result_constructor_impl() { + result default_constructed_result; + assert_false(static_cast(default_constructed_result)); + + result_promise rp; + auto rp_result = rp.get_result(); + assert_true(static_cast(rp_result)); + assert_equal(rp_result.status(), result_status::idle); + + auto new_result = std::move(rp_result); + assert_false(static_cast(rp_result)); + assert_true(static_cast(new_result)); + assert_equal(new_result.status(), result_status::idle); +} + +void concurrencpp::tests::test_result_constructor() { + test_result_constructor_impl(); + test_result_constructor_impl(); + test_result_constructor_impl(); + test_result_constructor_impl(); + test_result_constructor_impl(); +} + +template +void concurrencpp::tests::test_result_status_impl() { + // empty result throws + { + result result; + assert_throws([&result] { + result.status(); + }); + } + + // idle result + { + result_promise rp; + auto result = rp.get_result(); + assert_equal(result.status(), result_status::idle); + } + + // ready by value + { + result_promise rp; + auto result = rp.get_result(); + rp.set_from_function(result_factory::get); + assert_equal(result.status(), result_status::value); + } + + // exception result + { + result_promise rp; + auto result = rp.get_result(); + rp.set_from_function(result_factory::throw_ex); + assert_equal(result.status(), result_status::exception); + } + + // multiple calls of status are ok + { + result_promise rp; + auto result = rp.get_result(); + rp.set_from_function(result_factory::get); + + for (size_t i = 0; i < 10; i++) { + assert_equal(result.status(), result_status::value); + } + } +} + +void concurrencpp::tests::test_result_status() { + test_result_status_impl(); + test_result_status_impl(); + test_result_status_impl(); + test_result_status_impl(); + test_result_status_impl(); +} + +template +void concurrencpp::tests::test_result_get_impl() { + // empty result throws + { + result result; + assert_throws([&result] { + result.get(); + }); + } + + // get blocks until value is present and empties the result + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + result.get(); + const auto now = high_resolution_clock::now(); + + assert_false(static_cast(result)); + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + thread.join(); + } + + // get blocks until exception is present and empties the result + { + random randomizer; + result_promise rp; + auto result = rp.get_result(); + const auto id = randomizer(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), id, unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + }); + + try { + result.get(); + } catch (costume_exception e) { + assert_equal(e.id, id); + } + + const auto now = high_resolution_clock::now(); + + assert_false(static_cast(result)); + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + thread.join(); + } +} + +void concurrencpp::tests::test_result_get() { + test_result_get_impl(); + test_result_get_impl(); + test_result_get_impl(); + test_result_get_impl(); + test_result_get_impl(); +} + +template +void concurrencpp::tests::test_result_wait_impl() { + // empty result throws + { + result result; + assert_throws([&result] { + result.wait(); + }); + } + + // wait blocks until value is present + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + result.wait(); + const auto now = high_resolution_clock::now(); + + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + + test_ready_result_result(std::move(result)); + thread.join(); + } + + // wait blocks until exception is present + { + random randomizer; + result_promise rp; + auto result = rp.get_result(); + const auto id = randomizer(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), id, unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + }); + + result.wait(); + const auto now = high_resolution_clock::now(); + + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + + test_ready_result_costume_exception(std::move(result), id); + thread.join(); + } + + // if result is ready with value, wait returns immediately + { + result_promise rp; + auto result = rp.get_result(); + rp.set_from_function(result_factory::get); + + const auto time_before = high_resolution_clock::now(); + result.wait(); + const auto time_after = high_resolution_clock::now(); + const auto total_blocking_time = duration_cast(time_after - time_before).count(); + + assert_smaller_equal(total_blocking_time, 3); + test_ready_result_result(std::move(result)); + } + + // if result is ready with exception, wait returns immediately + { + random randomizer; + result_promise rp; + auto result = rp.get_result(); + const auto id = randomizer(); + + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + + const auto time_before = high_resolution_clock::now(); + result.wait(); + const auto time_after = high_resolution_clock::now(); + const auto total_blocking_time = duration_cast(time_after - time_before).count(); + + assert_smaller_equal(total_blocking_time, 3); + test_ready_result_costume_exception(std::move(result), id); + } + + // multiple calls to wait are ok + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + for (size_t i = 0; i < 10; i++) { + result.wait(); + } + + test_ready_result_result(std::move(result)); + thread.join(); + } +} + +void concurrencpp::tests::test_result_wait() { + test_result_wait_impl(); + test_result_wait_impl(); + test_result_wait_impl(); + test_result_wait_impl(); + test_result_wait_impl(); +} + +template +void concurrencpp::tests::test_result_wait_for_impl() { + // empty result throws + { + concurrencpp::result result; + assert_throws([&result] { + result.wait_for(seconds(1)); + }); + } + + // if the result is ready by value, don't block and return status::value + { + result_promise rp; + auto result = rp.get_result(); + + rp.set_from_function(result_factory::get); + + const auto before = high_resolution_clock::now(); + const auto status = result.wait_for(seconds(3)); + const auto after = high_resolution_clock::now(); + const auto time = duration_cast(after - before).count(); + + assert_smaller_equal(time, 3); + assert_equal(status, result_status::value); + } + + // if the result is ready by exception, don't block and return + // status::exception + { + result_promise rp; + auto result = rp.get_result(); + + rp.set_from_function(result_factory::throw_ex); + + const auto before = high_resolution_clock::now(); + const auto status = result.wait_for(seconds(3)); + const auto after = high_resolution_clock::now(); + const auto time = duration_cast(after - before).count(); + + assert_smaller_equal(time, 3); + assert_equal(status, result_status::exception); + } + + // if timeout reaches and no value/exception - return status::idle + { + result_promise rp; + auto result = rp.get_result(); + + const auto before = high_resolution_clock::now(); + const auto status = result.wait_for(milliseconds(500)); + const auto after = high_resolution_clock::now(); + const auto time = duration_cast(after - before); + + assert_equal(status, result_status::idle); + assert_bigger_equal(time, milliseconds(500)); + } + + // if result is set before timeout, unblock, and return status::value + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + result.wait_for(seconds(100)); + const auto now = high_resolution_clock::now(); + + test_ready_result_result(std::move(result)); + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + thread.join(); + } + + // if exception is set before timeout, unblock, and return status::exception + { + random randomizer; + result_promise rp; + auto result = rp.get_result(); + const auto id = randomizer(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time, id]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + }); + + result.wait_for(seconds(100)); + const auto now = high_resolution_clock::now(); + + test_ready_result_costume_exception(std::move(result), id); + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(1)); + + thread.join(); + } + + // multiple calls of wait_for are ok + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + for (size_t i = 0; i < 10; i++) { + result.wait_for(seconds(10)); + } + + test_ready_result_result(std::move(result)); + thread.join(); + } +} + +void concurrencpp::tests::test_result_wait_for() { + test_result_wait_for_impl(); + test_result_wait_for_impl(); + test_result_wait_for_impl(); + test_result_wait_for_impl(); + test_result_wait_for_impl(); +} + +template +void concurrencpp::tests::test_result_wait_until_impl() { + // empty result throws + { + concurrencpp::result result; + assert_throws([&result] { + const auto later = high_resolution_clock::now() + seconds(10); + result.wait_until(later); + }); + } + + // if time_point <= now, the function is equivalent to result::status + { + result_promise rp_idle, rp_val, rp_err; + result idle_result = rp_idle.get_result(), value_result = rp_val.get_result(), err_result = rp_err.get_result(); + + rp_val.set_from_function(result_factory::get); + rp_err.set_from_function(result_factory::throw_ex); + + const auto now = high_resolution_clock::now(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + assert_equal(idle_result.wait_until(now), concurrencpp::result_status::idle); + assert_equal(value_result.wait_until(now), concurrencpp::result_status::value); + assert_equal(err_result.wait_until(now), concurrencpp::result_status::exception); + } + + // if the result is ready by value, don't block and return status::value + { + result_promise rp; + auto result = rp.get_result(); + + rp.set_from_function(result_factory::get); + + const auto later = high_resolution_clock::now() + seconds(2); + const auto before = high_resolution_clock::now(); + const auto status = result.wait_until(later); + const auto after = high_resolution_clock::now(); + const auto time = duration_cast(after - before).count(); + + assert_smaller_equal(time, 3); + assert_equal(status, result_status::value); + } + + // if the result is ready by exception, don't block and return + // status::exception + { + result_promise rp; + auto result = rp.get_result(); + + rp.set_from_function(result_factory::throw_ex); + + const auto later = high_resolution_clock::now() + seconds(2); + const auto before = high_resolution_clock::now(); + const auto status = result.wait_until(later); + const auto after = high_resolution_clock::now(); + const auto time = duration_cast(after - before).count(); + + assert_smaller_equal(time, 3); + assert_equal(status, result_status::exception); + } + + // if timeout reaches and no value/exception - return status::idle + { + result_promise rp; + auto result = rp.get_result(); + + const auto later = high_resolution_clock::now() + seconds(1); + const auto status = result.wait_until(later); + const auto now = high_resolution_clock::now(); + + assert_equal(status, result_status::idle); + assert_bigger_equal(now, later); + } + + // if result is set before timeout, unblock, and return status::value + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + const auto later = high_resolution_clock::now() + seconds(10); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + result.wait_until(later); + const auto now = high_resolution_clock::now(); + + test_ready_result_result(std::move(result)); + assert_bigger_equal(now, unblocking_time); + assert_smaller(now, unblocking_time + seconds(2)); + thread.join(); + } + + // if exception is set before timeout, unblock, and return status::exception + { + random randomizer; + result_promise rp; + auto result = rp.get_result(); + const auto id = randomizer(); + + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + const auto later = high_resolution_clock::now() + seconds(10); + + std::thread thread([rp = std::move(rp), unblocking_time, id]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_exception(std::make_exception_ptr(costume_exception(id))); + }); + + result.wait_until(later); + const auto now = high_resolution_clock::now(); + + test_ready_result_costume_exception(std::move(result), id); + assert_bigger_equal(now, unblocking_time); + assert_smaller_equal(now, unblocking_time + seconds(2)); + thread.join(); + } + + // multiple calls to wait_until are ok + { + result_promise rp; + auto result = rp.get_result(); + const auto unblocking_time = high_resolution_clock::now() + seconds(1); + + std::thread thread([rp = std::move(rp), unblocking_time]() mutable { + std::this_thread::sleep_until(unblocking_time); + rp.set_from_function(result_factory::get); + }); + + for (size_t i = 0; i < 10; i++) { + const auto later = high_resolution_clock::now() + seconds(1); + result.wait_until(later); + } + + thread.join(); + } +} + +void concurrencpp::tests::test_result_wait_until() { + test_result_wait_until_impl(); + test_result_wait_until_impl(); + test_result_wait_until_impl(); + test_result_wait_until_impl(); + test_result_wait_until_impl(); +} + +template +void concurrencpp::tests::test_result_assignment_operator_empty_to_empty() { + result result_0, result_1; + result_0 = std::move(result_1); + assert_false(static_cast(result_0)); + assert_false(static_cast(result_1)); +} + +template +void concurrencpp::tests::test_result_assignment_operator_non_empty_to_non_empty() { + result_promise rp_0, rp_1; + result result_0 = rp_0.get_result(), result_1 = rp_1.get_result(); + result_0 = std::move(result_1); + assert_true(static_cast(result_0)); + assert_false(static_cast(result_1)); + + rp_0.set_from_function(result_factory::get); + assert_false(static_cast(result_1)); + assert_equal(result_0.status(), result_status::idle); + + rp_1.set_from_function(result_factory::get); + assert_false(static_cast(result_1)); + test_ready_result_result(std::move(result_0)); +} + +template +void concurrencpp::tests::test_result_assignment_operator_empty_to_non_empty() { + result_promise rp_0; + result result_0 = rp_0.get_result(), result_1; + result_0 = std::move(result_1); + assert_false(static_cast(result_0)); + assert_false(static_cast(result_1)); +} + +template +void concurrencpp::tests::test_result_assignment_operator_non_empty_to_empty() { + result_promise rp_1; + result result_0, result_1 = rp_1.get_result(); + result_0 = std::move(result_1); + assert_true(static_cast(result_0)); + assert_false(static_cast(result_1)); + + rp_1.set_from_function(result_factory::get); + test_ready_result_result(std::move(result_0)); +} + +template +void concurrencpp::tests::test_result_assignment_operator_assign_to_self() { + result res0; + + res0 = std::move(res0); + assert_false(static_cast(res0)); + + result_promise rp_1; + auto res1 = rp_1.get_result(); + + res1 = std::move(res1); + assert_true(static_cast(res1)); +} + +template +void concurrencpp::tests::test_result_assignment_operator_impl() { + test_result_assignment_operator_empty_to_empty(); + test_result_assignment_operator_non_empty_to_empty(); + test_result_assignment_operator_empty_to_non_empty(); + test_result_assignment_operator_non_empty_to_non_empty(); + test_result_assignment_operator_assign_to_self(); +} + +void concurrencpp::tests::test_result_assignment_operator() { + test_result_assignment_operator_impl(); + test_result_assignment_operator_impl(); + test_result_assignment_operator_impl(); + test_result_assignment_operator_impl(); + test_result_assignment_operator_impl(); +} + +void concurrencpp::tests::test_result() { + tester tester("result test"); + + tester.add_step("constructor", test_result_constructor); + tester.add_step("status", test_result_status); + tester.add_step("get", test_result_get); + tester.add_step("wait", test_result_wait); + tester.add_step("wait_for", test_result_wait_for); + tester.add_step("wait_until", test_result_wait_until); + tester.add_step("operator =", test_result_assignment_operator); + + tester.launch_test(); +} diff --git a/test/source/tests/result_tests/when_all_tests.cpp b/test/source/tests/result_tests/when_all_tests.cpp new file mode 100644 index 00000000..69ae6b93 --- /dev/null +++ b/test/source/tests/result_tests/when_all_tests.cpp @@ -0,0 +1,289 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests { + template + void test_when_all_vector_empty_result(); + + template + void test_when_all_vector_empty_range(); + + template + result test_when_all_vector_valid(std::shared_ptr ex); + + template + void test_when_all_vector_impl(); + void test_when_all_vector(); + + void test_when_all_tuple_empty_result(); + + void test_when_all_tuple_empty_range(); + + result test_when_all_tuple_valid(std::shared_ptr ex); + + void test_when_all_tuple(); +} // namespace concurrencpp::tests + +template +void concurrencpp::tests::test_when_all_vector_empty_result() { + const size_t task_count = 63; + std::vector> result_promises(task_count); + std::vector> results; + + for (auto& rp : result_promises) { + results.emplace_back(rp.get_result()); + } + + results.emplace_back(); + + assert_throws_with_error_message( + [&] { + concurrencpp::when_all(results.begin(), results.end()); + }, + concurrencpp::details::consts::k_when_all_empty_result_error_msg); + + const auto all_valid = std::all_of(results.begin(), results.begin() + task_count, [](const auto& result) { + return static_cast(result); + }); + + assert_true(all_valid); +} + +template +void concurrencpp::tests::test_when_all_vector_empty_range() { + std::vector> empty_range; + auto all = concurrencpp::when_all(empty_range.begin(), empty_range.end()); + assert_equal(all.status(), result_status::value); + assert_equal(all.get(), std::vector> {}); +} + +template +concurrencpp::result concurrencpp::tests::test_when_all_vector_valid(std::shared_ptr ex) { + const size_t task_count = 1'024; + auto values = result_factory::get_many(task_count); + std::atomic_size_t counter = 0; + + std::vector> results; + results.reserve(1'024); + + for (size_t i = 0; i < task_count; i++) { + results.emplace_back(ex->submit([&values, &counter, i]() -> type { + (void)values; + counter.fetch_add(1, std::memory_order_relaxed); + + if (i % 4 == 0) { + throw costume_exception(i); + } + + if constexpr (!std::is_same_v) { + return values[i]; + } + })); + } + + auto all = concurrencpp::when_all(results.begin(), results.end()); + + const auto all_empty = std::all_of(results.begin(), results.end(), [](const auto& res) { + return !static_cast(res); + }); + + assert_true(all_empty); + + auto done_results = co_await all; + + assert_equal(counter.load(std::memory_order_relaxed), results.size()); + + const auto all_done = std::all_of(done_results.begin(), done_results.end(), [](const auto& res) { + return static_cast(res) && (res.status() != result_status::idle); + }); + + assert_true(all_done); + + for (size_t i = 0; i < task_count; i++) { + if (i % 4 == 0) { + test_ready_result_costume_exception(std::move(done_results[i]), i); + } else { + if constexpr (!std::is_same_v) { + test_ready_result_result(std::move(done_results[i]), values[i]); + } else { + test_ready_result_result(std::move(done_results[i])); + } + } + } +} + +template +void concurrencpp::tests::test_when_all_vector_impl() { + test_when_all_vector_empty_result(); + test_when_all_vector_empty_range(); + + { + auto ex = std::make_shared(); + executor_shutdowner shutdown(ex); + test_when_all_vector_valid(ex).get(); + } +} + +void concurrencpp::tests::test_when_all_vector() { + test_when_all_vector_impl(); + test_when_all_vector_impl(); + test_when_all_vector_impl(); + test_when_all_vector_impl(); + test_when_all_vector_impl(); +} + +void concurrencpp::tests::test_when_all_tuple_empty_result() { + result_promise rp_int; + auto int_res = rp_int.get_result(); + + result_promise rp_s; + auto s_res = rp_s.get_result(); + + result_promise rp_void; + auto void_res = rp_void.get_result(); + + result_promise rp_int_ref; + auto int_ref_res = rp_int_ref.get_result(); + + result s_ref_res; + + assert_throws_with_error_message( + [&] { + when_all(std::move(int_res), std::move(s_res), std::move(void_res), std::move(int_ref_res), std::move(s_ref_res)); + }, + concurrencpp::details::consts::k_when_all_empty_result_error_msg); + + assert_true(static_cast(int_res)); + assert_true(static_cast(s_res)); + assert_true(static_cast(void_res)); + assert_true(static_cast(int_res)); +} + +void concurrencpp::tests::test_when_all_tuple_empty_range() { + auto all = when_all(); + assert_equal(all.status(), result_status::value); + assert_equal(all.get(), std::tuple<> {}); +} + +concurrencpp::result concurrencpp::tests::test_when_all_tuple_valid(std::shared_ptr ex) { + std::atomic_size_t counter = 0; + + auto int_res_val = ex->submit([&]() -> int { + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + auto int_res_ex = ex->submit([&]() -> int { + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(0); + return result_factory::get(); + }); + + auto s_res_val = ex->submit([&]() -> std::string { + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + auto s_res_ex = ex->submit([&]() -> std::string { + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(1); + return result_factory::get(); + }); + + auto void_res_val = ex->submit([&] { + counter.fetch_add(1, std::memory_order_relaxed); + }); + + auto void_res_ex = ex->submit([&] { + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(2); + }); + + auto int_ref_res_val = ex->submit([&]() -> int& { + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + auto int_ref_res_ex = ex->submit([&]() -> int& { + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(3); + return result_factory::get(); + }); + + auto s_ref_res_val = ex->submit([&]() -> std::string& { + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + auto s_ref_res_ex = ex->submit([&]() -> std::string& { + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(4); + return result_factory::get(); + }); + + auto all = when_all(std::move(int_res_val), + std::move(int_res_ex), + std::move(s_res_val), + std::move(s_res_ex), + std::move(void_res_val), + std::move(void_res_ex), + std::move(int_ref_res_val), + std::move(int_ref_res_ex), + std::move(s_ref_res_val), + std::move(s_ref_res_ex)); + + assert_false(static_cast(int_res_val)); + assert_false(static_cast(int_res_ex)); + assert_false(static_cast(s_res_val)); + assert_false(static_cast(s_res_ex)); + assert_false(static_cast(void_res_val)); + assert_false(static_cast(void_res_ex)); + assert_false(static_cast(int_ref_res_val)); + assert_false(static_cast(int_ref_res_ex)); + assert_false(static_cast(s_ref_res_val)); + assert_false(static_cast(s_ref_res_ex)); + + auto done_results_tuple = co_await all; + + assert_equal(counter.load(std::memory_order_relaxed), size_t(10)); + + test_ready_result_result(std::move(std::get<0>(done_results_tuple)), result_factory::get()); + test_ready_result_result(std::move(std::get<2>(done_results_tuple)), result_factory::get()); + test_ready_result_result(std::move(std::get<4>(done_results_tuple))); + test_ready_result_result(std::move(std::get<6>(done_results_tuple)), result_factory::get()); + test_ready_result_result(std::move(std::get<8>(done_results_tuple)), result_factory::get()); + + test_ready_result_costume_exception(std::move(std::get<1>(done_results_tuple)), 0); + test_ready_result_costume_exception(std::move(std::get<3>(done_results_tuple)), 1); + test_ready_result_costume_exception(std::move(std::get<5>(done_results_tuple)), 2); + test_ready_result_costume_exception(std::move(std::get<7>(done_results_tuple)), 3); + test_ready_result_costume_exception(std::move(std::get<9>(done_results_tuple)), 4); +} + +void concurrencpp::tests::test_when_all_tuple() { + test_when_all_tuple_empty_result(); + test_when_all_tuple_empty_range(); + + { + auto ex = std::make_shared(); + executor_shutdowner shutdown(ex); + test_when_all_tuple_valid(ex); + } +} + +void concurrencpp::tests::test_when_all() { + tester test("when_all test"); + + test.add_step("when_all(begin, end)", test_when_all_vector); + test.add_step("when_all(result_types&& ... results)", test_when_all_tuple); + + test.launch_test(); +} diff --git a/test/source/tests/result_tests/when_any_tests.cpp b/test/source/tests/result_tests/when_any_tests.cpp new file mode 100644 index 00000000..ff43cedf --- /dev/null +++ b/test/source/tests/result_tests/when_any_tests.cpp @@ -0,0 +1,337 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tests/test_utils/test_ready_result.h" +#include "tests/test_utils/executor_shutdowner.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/random.h" +#include "helpers/object_observer.h" + +namespace concurrencpp::tests { + template + void test_when_any_vector_empty_result(); + + template + void test_when_any_vector_empty_range(); + + template + result test_when_any_vector_valid(std::shared_ptr ex); + + template + void test_when_any_vector_impl(); + + void test_when_any_vector(); + + void test_when_any_tuple_empty_result(); + + result test_when_any_tuple_impl(std::shared_ptr ex); + + void test_when_any_tuple(); +} // namespace concurrencpp::tests + +template +void concurrencpp::tests::test_when_any_vector_empty_result() { + const size_t task_count = 63; + std::vector> result_promises(task_count); + std::vector> results; + + for (auto& rp : result_promises) { + results.emplace_back(rp.get_result()); + } + + results.emplace_back(); + + assert_throws_with_error_message( + [&] { + concurrencpp::when_any(results.begin(), results.end()); + }, + concurrencpp::details::consts::k_when_any_empty_result_error_msg); + + const auto all_valid = std::all_of(results.begin(), results.begin() + task_count, [](const auto& result) { + return static_cast(result); + }); + + assert_true(all_valid); +} + +template +void concurrencpp::tests::test_when_any_vector_empty_range() { + std::vector> empty_range; + + assert_throws_with_error_message( + [&] { + when_any(empty_range.begin(), empty_range.end()); + }, + concurrencpp::details::consts::k_when_any_empty_range_error_msg); +} + +template +concurrencpp::result concurrencpp::tests::test_when_any_vector_valid(std::shared_ptr ex) { + const size_t task_count = 64; + auto values = result_factory::get_many(task_count); + std::vector> results; + random randomizer; + + for (size_t i = 0; i < task_count; i++) { + const auto time_to_sleep = randomizer(10, 100); + results.emplace_back(ex->submit([i, time_to_sleep, &values]() -> type { + (void)values; + std::this_thread::sleep_for(std::chrono::milliseconds(time_to_sleep)); + + if (i % 4 == 0) { + throw costume_exception(i); + } + + if constexpr (!std::is_same_v) { + return values[i]; + } + })); + } + + auto any_done = co_await when_any(results.begin(), results.end()); + + auto& done_result = any_done.results[any_done.index]; + + const auto all_valid = std::all_of(any_done.results.begin(), any_done.results.end(), [](const auto& result) { + return static_cast(result); + }); + + assert_true(all_valid); + + if (any_done.index % 4 == 0) { + test_ready_result_costume_exception(std::move(done_result), any_done.index); + } else { + if constexpr (std::is_same_v) { + test_ready_result_result(std::move(done_result)); + } else { + test_ready_result_result(std::move(done_result), values[any_done.index]); + } + } + + // the value vector is a local variable, tasks may outlive it. join them. + for (auto& result : any_done.results) { + if (!static_cast(result)) { + continue; + } + + co_await result.resolve(); + } +} + +template +void concurrencpp::tests::test_when_any_vector_impl() { + test_when_any_vector_empty_result(); + test_when_any_vector_empty_range(); + + { + auto thread_executor = std::make_shared(); + executor_shutdowner es(thread_executor); + test_when_any_vector_valid(thread_executor).get(); + } +} + +void concurrencpp::tests::test_when_any_vector() { + test_when_any_vector_impl(); + test_when_any_vector_impl(); + test_when_any_vector_impl(); + test_when_any_vector_impl(); + test_when_any_vector_impl(); +} + +void concurrencpp::tests::test_when_any_tuple_empty_result() { + result_promise rp_int; + auto int_res = rp_int.get_result(); + + result_promise rp_s; + auto s_res = rp_s.get_result(); + + result_promise rp_void; + auto void_res = rp_void.get_result(); + + result_promise rp_int_ref; + auto int_ref_res = rp_int_ref.get_result(); + + result s_ref_res; + + assert_throws_with_error_message( + [&] { + when_any(std::move(int_res), std::move(s_res), std::move(void_res), std::move(int_ref_res), std::move(s_ref_res)); + }, + concurrencpp::details::consts::k_when_any_empty_result_error_msg); + + // all pre-operation results are still valid + assert_true(static_cast(int_res)); + assert_true(static_cast(s_res)); + assert_true(static_cast(void_res)); + assert_true(static_cast(int_res)); +} + +concurrencpp::result concurrencpp::tests::test_when_any_tuple_impl(std::shared_ptr ex) { + std::atomic_size_t counter = 0; + random randomizer; + + auto tts = randomizer(10, 100); + auto int_res_val = ex->submit([&counter, tts] { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto int_res_ex = ex->submit([&counter, tts] { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(0); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto s_res_val = ex->submit([&counter, tts]() -> std::string { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto s_res_ex = ex->submit([&counter, tts]() -> std::string { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(1); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto void_res_val = ex->submit([&counter, tts] { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + }); + + tts = randomizer(10, 100); + auto void_res_ex = ex->submit([&counter, tts] { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(2); + }); + + tts = randomizer(10, 100); + auto int_ref_res_val = ex->submit([&counter, tts]() -> int& { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto int_ref_res_ex = ex->submit([&counter, tts]() -> int& { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(3); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto s_ref_res_val = ex->submit([&counter, tts]() -> std::string& { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + return result_factory::get(); + }); + + tts = randomizer(10, 100); + auto s_ref_res_ex = ex->submit([&counter, tts]() -> std::string& { + std::this_thread::sleep_for(std::chrono::milliseconds(tts)); + counter.fetch_add(1, std::memory_order_relaxed); + throw costume_exception(4); + return result_factory::get(); + }); + + auto any_done = co_await when_any(std::move(int_res_val), + std::move(int_res_ex), + std::move(s_res_val), + std::move(s_res_ex), + std::move(void_res_val), + std::move(void_res_ex), + std::move(int_ref_res_val), + std::move(int_ref_res_ex), + std::move(s_ref_res_val), + std::move(s_ref_res_ex)); + + assert_bigger_equal(counter.load(std::memory_order_relaxed), size_t(1)); + + switch (any_done.index) { + case 0: { + test_ready_result_result(std::move(std::get<0>(any_done.results)), result_factory::get()); + break; + } + case 1: { + test_ready_result_costume_exception(std::move(std::get<1>(any_done.results)), 0); + break; + } + case 2: { + test_ready_result_result(std::move(std::get<2>(any_done.results)), result_factory::get()); + break; + } + case 3: { + test_ready_result_costume_exception(std::move(std::get<3>(any_done.results)), 1); + break; + } + case 4: { + test_ready_result_result(std::move(std::get<4>(any_done.results))); + break; + } + case 5: { + test_ready_result_costume_exception(std::move(std::get<5>(any_done.results)), 2); + break; + } + case 6: { + test_ready_result_result(std::move(std::get<6>(any_done.results)), result_factory::get()); + break; + } + case 7: { + test_ready_result_costume_exception(std::move(std::get<7>(any_done.results)), 3); + break; + } + case 8: { + test_ready_result_result(std::move(std::get<8>(any_done.results)), result_factory::get()); + break; + } + case 9: { + test_ready_result_costume_exception(std::move(std::get<9>(any_done.results)), 4); + break; + } + default: { + assert_false(true); + } + } + + auto wait = [](auto& result) { + if (static_cast(result)) { + result.wait(); + } + }; + + std::apply( + [wait](auto&... results) { + (wait(results), ...); + }, + any_done.results); +} + +void concurrencpp::tests::test_when_any_tuple() { + test_when_any_tuple_empty_result(); + + { + auto thread_executor = std::make_shared(); + executor_shutdowner es(thread_executor); + test_when_any_tuple_impl(thread_executor).get(); + } +} + +void concurrencpp::tests::test_when_any() { + tester test("when_any test"); + + test.add_step("when_any(begin, end)", test_when_any_vector); + test.add_step("when_any(result_types&& ... results)", test_when_any_tuple); + + test.launch_test(); +} diff --git a/test/source/tests/runtime_tests.cpp b/test/source/tests/runtime_tests.cpp new file mode 100644 index 00000000..8bb18dee --- /dev/null +++ b/test/source/tests/runtime_tests.cpp @@ -0,0 +1,75 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" + +namespace concurrencpp::tests { + void test_runtime_destructor(); + void test_runtime_version(); +} // namespace concurrencpp::tests + +namespace concurrencpp::tests { + struct dummy_executor : public concurrencpp::executor { + + bool shutdown_requested_flag = false; + + dummy_executor(const char* name, int, float) : executor(name) {} + + void enqueue(std::experimental::coroutine_handle<>) override {} + void enqueue(std::span>) override {} + + int max_concurrency_level() const noexcept override { + return 0; + } + + bool shutdown_requested() const noexcept override { + return shutdown_requested_flag; + }; + void shutdown() noexcept override { + shutdown_requested_flag = true; + }; + }; +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_runtime_destructor() { + std::shared_ptr executors[7]; + + { + concurrencpp::runtime runtime; + executors[0] = runtime.inline_executor(); + executors[1] = runtime.thread_pool_executor(); + executors[2] = runtime.background_executor(); + executors[3] = runtime.thread_executor(); + executors[4] = runtime.make_worker_thread_executor(); + executors[5] = runtime.make_manual_executor(); + executors[6] = runtime.template make_executor("dummy_executor", 1, 4.4f); + + for (auto& executor : executors) { + assert_true(static_cast(executor)); + assert_false(executor->shutdown_requested()); + } + } + + for (auto& executor : executors) { + assert_true(executor->shutdown_requested()); + } +} + +void concurrencpp::tests::test_runtime_version() { + concurrencpp::runtime runtime; + + auto version = runtime.version(); + assert_equal(std::get<0>(version), concurrencpp::details::consts::k_concurrencpp_version_major); + assert_equal(std::get<1>(version), concurrencpp::details::consts::k_concurrencpp_version_minor); + assert_equal(std::get<2>(version), concurrencpp::details::consts::k_concurrencpp_version_revision); +} + +void concurrencpp::tests::test_runtime() { + tester tester("runtime test"); + + tester.add_step("~runtime", test_runtime_destructor); + tester.add_step("version", test_runtime_version); + + tester.launch_test(); +} diff --git a/test/source/tests/timer_tests/timer_queue_tests.cpp b/test/source/tests/timer_tests/timer_queue_tests.cpp new file mode 100644 index 00000000..3d682904 --- /dev/null +++ b/test/source/tests/timer_tests/timer_queue_tests.cpp @@ -0,0 +1,94 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" +#include "helpers/random.h" + +#include + +using namespace std::chrono_literals; + +namespace concurrencpp::tests { + void test_timer_queue_make_timer(); + void test_timer_queue_make_oneshot_timer(); + void test_timer_queue_make_delay_object(); +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_timer_queue_make_timer() { + auto timer_queue = std::make_shared(); + assert_false(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + timer_queue->make_timer(100ms, 100ms, {}, [] { + }); + }, + concurrencpp::details::consts::k_timer_queue_make_timer_executor_null_err_msg); + + timer_queue->shutdown(); + assert_true(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + auto inline_executor = std::make_shared(); + timer_queue->make_timer(100ms, 100ms, inline_executor, [] { + }); + }, + concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); +} + +void concurrencpp::tests::test_timer_queue_make_oneshot_timer() { + auto timer_queue = std::make_shared(); + assert_false(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + timer_queue->make_one_shot_timer(100ms, {}, [] { + }); + }, + concurrencpp::details::consts::k_timer_queue_make_oneshot_timer_executor_null_err_msg); + + timer_queue->shutdown(); + assert_true(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + auto inline_executor = std::make_shared(); + timer_queue->make_one_shot_timer(100ms, inline_executor, [] { + }); + }, + concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); +} + +void concurrencpp::tests::test_timer_queue_make_delay_object() { + auto timer_queue = std::make_shared(); + assert_false(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + timer_queue->make_delay_object(100ms, {}); + }, + concurrencpp::details::consts::k_timer_queue_make_delay_object_executor_null_err_msg); + + timer_queue->shutdown(); + assert_true(timer_queue->shutdown_requested()); + + assert_throws_with_error_message( + [timer_queue] { + auto inline_executor = std::make_shared(); + timer_queue->make_delay_object(100ms, inline_executor); + }, + concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); +} + +void concurrencpp::tests::test_timer_queue() { + tester test("timer_queue test"); + + test.add_step("make_timer", test_timer_queue_make_timer); + test.add_step("make_oneshot_timer", test_timer_queue_make_timer); + test.add_step("make_delay_object", test_timer_queue_make_delay_object); + + test.launch_test(); +} diff --git a/test/source/tests/timer_tests/timer_tests.cpp b/test/source/tests/timer_tests/timer_tests.cpp new file mode 100644 index 00000000..aa3cba35 --- /dev/null +++ b/test/source/tests/timer_tests/timer_tests.cpp @@ -0,0 +1,668 @@ +#include "concurrencpp/concurrencpp.h" +#include "tests/all_tests.h" + +#include "tester/tester.h" +#include "helpers/assertions.h" +#include "helpers/object_observer.h" +#include "helpers/random.h" + +#include + +using namespace std::chrono_literals; + +namespace concurrencpp::tests { + void test_one_timer(); + void test_many_timers(); + void test_timer_constructor(); + + void test_timer_destructor_empty_timer(); + void test_timer_destructor_dead_timer_queue(); + void test_timer_destructor_functionality(); + void test_timer_destructor_RAII(); + void test_timer_destructor(); + + void test_timer_cancel_empty_timer(); + void test_timer_cancel_dead_timer_queue(); + void test_timer_cancel_before_due_time(); + void test_timer_cancel_after_due_time_before_beat(); + void test_timer_cancel_after_due_time_after_beat(); + void test_timer_cancel_RAII(); + void test_timer_cancel(); + + void test_timer_operator_bool(); + + void test_timer_set_frequency_before_due_time(); + void test_timer_set_frequency_after_due_time(); + void test_timer_set_frequency(); + + void test_timer_oneshot_timer(); + void test_timer_delay_object(); + + void test_timer_assignment_operator_empty_to_empty(); + void test_timer_assignment_operator_non_empty_to_non_empty(); + void test_timer_assignment_operator_empty_to_non_empty(); + void test_timer_assignment_operator_non_empty_to_empty(); + void test_timer_assignment_operator_assign_to_self(); + void test_timer_assignment_operator(); +} // namespace concurrencpp::tests + +using concurrencpp::timer; + +using namespace std::chrono; +using time_point = std::chrono::time_point; + +namespace concurrencpp::tests { + auto empty_callback = []() noexcept { + }; + + class counter_executor : public concurrencpp::executor { + + private: + std::atomic_size_t m_invocation_count; + + public: + counter_executor() : executor("timer_counter_executor"), m_invocation_count(0) {} + + void enqueue(std::experimental::coroutine_handle<> task) override { + ++m_invocation_count; + task(); + } + + void enqueue(std::span>) override { + // do nothing + } + + int max_concurrency_level() const noexcept override { + return 0; + } + + bool shutdown_requested() const noexcept override { + return false; + } + + void shutdown() noexcept override { + // do nothing + } + + size_t invocation_count() const noexcept { + return m_invocation_count.load(); + } + }; + + class recording_executor : public concurrencpp::executor { + + public: + mutable std::mutex m_lock; + std::vector<::time_point> m_time_points; + ::time_point m_start_time; + + public: + recording_executor() : executor("timer_recording_executor") { + m_time_points.reserve(64); + }; + + virtual void enqueue(std::experimental::coroutine_handle<> task) override { + { + std::unique_lock lock(m_lock); + m_time_points.emplace_back(std::chrono::high_resolution_clock::now()); + } + + task(); + } + + virtual void enqueue(std::span>) override { + // do nothing + } + + virtual int max_concurrency_level() const noexcept override { + return 0; + } + + virtual bool shutdown_requested() const noexcept override { + return false; + } + + virtual void shutdown() noexcept override { + // do nothing + } + + void start_test() { + std::unique_lock lock(m_lock); + m_start_time = std::chrono::high_resolution_clock::now(); + } + + ::time_point get_start_time() { + std::unique_lock lock(m_lock); + return m_start_time; + } + + ::time_point get_first_fire_time() { + std::unique_lock lock(m_lock); + assert(!m_time_points.empty()); + return m_time_points[0]; + } + + std::vector<::time_point> flush_time_points() { + std::unique_lock lock(m_lock); + return std::move(m_time_points); + } + + void reset() { + std::unique_lock lock(m_lock); + m_time_points.clear(); + m_start_time = std::chrono::high_resolution_clock::now(); + } + }; + + class timer_tester { + + private: + const std::chrono::milliseconds m_due_time; + std::chrono::milliseconds m_frequency; + const std::shared_ptr m_executor; + const std::shared_ptr m_timer_queue; + concurrencpp::timer m_timer; + + void assert_timer_stats() noexcept { + assert_equal(m_timer.get_due_time(), m_due_time); + assert_equal(m_timer.get_frequency(), m_frequency); + assert_equal(m_timer.get_executor().get(), m_executor.get()); + assert_equal(m_timer.get_timer_queue().lock(), m_timer_queue); + } + + size_t calculate_due_time() noexcept { + const auto start_time = m_executor->get_start_time(); + const auto first_fire = m_executor->get_first_fire_time(); + const auto due_time = duration_cast(first_fire - start_time).count(); + return static_cast(due_time); + } + + std::vector calculate_frequencies() { + auto fire_times = m_executor->flush_time_points(); + + std::vector intervals; + intervals.reserve(fire_times.size()); + + for (size_t i = 0; i < fire_times.size() - 1; i++) { + const auto period = fire_times[i + 1] - fire_times[i]; + const auto interval = duration_cast(period).count(); + intervals.emplace_back(static_cast(interval)); + } + + return intervals; + } + + void assert_correct_time_points() { + const auto tested_due_time = calculate_due_time(); + interval_ok(tested_due_time, m_due_time.count()); + + const auto intervals = calculate_frequencies(); + for (auto tested_frequency : intervals) { + interval_ok(tested_frequency, m_frequency.count()); + } + } + + public: + timer_tester(const std::chrono::milliseconds due_time, const std::chrono::milliseconds frequency, std::shared_ptr timer_queue) : + m_due_time(due_time), m_frequency(frequency), m_executor(std::make_shared()), m_timer_queue(std::move(timer_queue)) {} + + void start_timer_test() { + m_timer = m_timer_queue->make_timer(m_due_time, m_frequency, m_executor, [] { + }); + + m_executor->start_test(); + } + + void start_once_timer_test() { + m_timer = m_timer_queue->make_one_shot_timer(m_due_time, m_executor, [] { + }); + + m_executor->start_test(); + } + + void reset(std::chrono::milliseconds new_frequency) noexcept { + m_timer.set_frequency(new_frequency); + m_executor->reset(); + m_frequency = new_frequency; + } + + void test_frequencies(size_t frequency) { + const auto frequencies = calculate_frequencies(); + for (auto tested_frequency : frequencies) { + interval_ok(tested_frequency, frequency); + } + } + + void test() { + assert_timer_stats(); + assert_correct_time_points(); + + m_timer.cancel(); + } + + void test_oneshot_timer() { + assert_timer_stats(); + interval_ok(calculate_due_time(), m_due_time.count()); + + auto frequencies = calculate_frequencies(); + assert_true(frequencies.empty()); + + m_timer.cancel(); + } + + static void interval_ok(size_t interval, size_t expected) noexcept { + assert_bigger_equal(interval, expected - 30); + assert_smaller_equal(interval, expected + 100); + } + }; +} // namespace concurrencpp::tests + +void concurrencpp::tests::test_one_timer() { + const auto due_time = 1270ms; + const auto frequency = 3230ms; + auto tq = std::make_shared(); + + timer_tester tester(due_time, frequency, tq); + tester.start_timer_test(); + + std::this_thread::sleep_for(30s); + + tester.test(); +} + +void concurrencpp::tests::test_many_timers() { + random randomizer; + auto timer_queue = std::make_shared(); + std::list testers; + + const size_t due_time_min = 100; + const size_t due_time_max = 2'000; + const size_t frequency_min = 100; + const size_t frequency_max = 3'000; + const size_t timer_count = 1'024 * 4; + + auto round_down = [](size_t num) { + return num - num % 50; + }; + + for (size_t i = 0; i < timer_count; i++) { + const auto due_time = round_down(randomizer(due_time_min, due_time_max)); + const auto frequency = round_down(randomizer(frequency_min, frequency_max)); + + testers.emplace_front(std::chrono::milliseconds(due_time), std::chrono::milliseconds(frequency), timer_queue).start_timer_test(); + } + + std::this_thread::sleep_for(5min); + + for (auto& tester : testers) { + tester.test(); + } +} + +void concurrencpp::tests::test_timer_constructor() { + test_one_timer(); + test_many_timers(); +} + +void concurrencpp::tests::test_timer_destructor_empty_timer() { + timer timer; +} + +void concurrencpp::tests::test_timer_destructor_dead_timer_queue() { + timer timer; + auto executor = std::make_shared(); + + { + auto timer_queue = std::make_shared(); + + timer = timer_queue->make_timer(10 * 1000ms, 10 * 1000ms, executor, empty_callback); + } + + // nothing strange should happen here +} + +void concurrencpp::tests::test_timer_destructor_functionality() { + auto executor = std::make_shared(); + auto timer_queue = std::make_shared(); + + size_t invocation_count_before = 0; + + { + auto timer = timer_queue->make_timer(500ms, 500ms, executor, [] { + }); + + std::this_thread::sleep_for(4150ms); + + invocation_count_before = executor->invocation_count(); + } + + std::this_thread::sleep_for(4s); + + const auto invocation_count_after = executor->invocation_count(); + assert_equal(invocation_count_before, invocation_count_after); +} + +void concurrencpp::tests::test_timer_destructor_RAII() { + const size_t timer_count = 1'024; + object_observer observer; + auto timer_queue = std::make_shared(); + auto inline_executor = std::make_shared(); + + std::vector timers; + timers.reserve(timer_count); + + for (size_t i = 0; i < timer_count; i++) { + timers.emplace_back(timer_queue->make_timer(1'000ms, 1'000ms, inline_executor, observer.get_testing_stub())); + } + + timers.clear(); + timer_queue.reset(); + + assert_equal(observer.get_destruction_count(), timer_count); +} + +void concurrencpp::tests::test_timer_destructor() { + test_timer_destructor_empty_timer(); + test_timer_destructor_dead_timer_queue(); + test_timer_destructor_functionality(); + test_timer_destructor_RAII(); +} + +void concurrencpp::tests::test_timer_cancel_empty_timer() { + timer timer; + timer.cancel(); +} + +void concurrencpp::tests::test_timer_cancel_dead_timer_queue() { + timer timer; + + { + auto timer_queue = std::make_shared(); + auto executor = std::make_shared(); + + timer = timer_queue->make_timer(10 * 1000ms, 10 * 1000ms, executor, empty_callback); + } + + timer.cancel(); + assert_false(timer); +} + +void concurrencpp::tests::test_timer_cancel_before_due_time() { + object_observer observer; + auto timer_queue = std::make_shared(); + auto wt_executor = std::make_shared(); + + auto timer = timer_queue->make_timer(1'000ms, 500ms, wt_executor, observer.get_testing_stub()); + + timer.cancel(); + assert_false(timer); + + std::this_thread::sleep_for(4s); + + assert_equal(observer.get_execution_count(), size_t(0)); + assert_equal(observer.get_destruction_count(), size_t(1)); + + wt_executor->shutdown(); +} + +void concurrencpp::tests::test_timer_cancel_after_due_time_before_beat() { + object_observer observer; + auto timer_queue = std::make_shared(); + auto wt_executor = std::make_shared(); + + auto timer = timer_queue->make_timer(500ms, 500ms, wt_executor, observer.get_testing_stub()); + + std::this_thread::sleep_for(std::chrono::milliseconds(600)); + + timer.cancel(); + assert_false(timer); + + std::this_thread::sleep_for(std::chrono::seconds(3)); + + assert_equal(observer.get_execution_count(), size_t(1)); + assert_equal(observer.get_destruction_count(), size_t(1)); + + wt_executor->shutdown(); +} + +void concurrencpp::tests::test_timer_cancel_after_due_time_after_beat() { + object_observer observer; + auto timer_queue = std::make_shared(); + auto wt_executor = std::make_shared(); + + auto timer = timer_queue->make_timer(1'000ms, 1'000ms, wt_executor, observer.get_testing_stub()); + + std::this_thread::sleep_for(std::chrono::milliseconds(1'000 * 4 + 220)); + + const auto execution_count_0 = observer.get_execution_count(); + + timer.cancel(); + assert_false(timer); + + std::this_thread::sleep_for(std::chrono::seconds(4)); + + const auto execution_count_1 = observer.get_execution_count(); + + assert_equal(execution_count_0, execution_count_1); + assert_equal(observer.get_destruction_count(), size_t(1)); + + wt_executor->shutdown(); +} + +void concurrencpp::tests::test_timer_cancel_RAII() { + const size_t timer_count = 1'024; + object_observer observer; + auto timer_queue = std::make_shared(); + auto inline_executor = std::make_shared(); + + std::vector timers; + timers.reserve(timer_count); + + for (size_t i = 0; i < timer_count; i++) { + timers.emplace_back(timer_queue->make_timer(1'000ms, 1'000ms, inline_executor, observer.get_testing_stub())); + } + + timer_queue.reset(); + + for (auto& timer : timers) { + timer.cancel(); + } + + assert_true(observer.wait_destruction_count(timer_count, 1min)); +} + +void concurrencpp::tests::test_timer_cancel() { + test_timer_cancel_empty_timer(); + test_timer_cancel_dead_timer_queue(); + test_timer_cancel_before_due_time(); + test_timer_cancel_after_due_time_before_beat(); + test_timer_cancel_after_due_time_after_beat(); + test_timer_cancel_RAII(); +} + +void concurrencpp::tests::test_timer_operator_bool() { + auto timer_queue = std::make_shared(); + auto inline_executor = std::make_shared(); + + auto timer_1 = timer_queue->make_timer(10 * 1000ms, 10 * 1000ms, inline_executor, empty_callback); + + assert_true(static_cast(timer_1)); + + auto timer_2 = std::move(timer_1); + assert_false(static_cast(timer_1)); + assert_true(static_cast(timer_2)); + + timer_2.cancel(); + assert_false(static_cast(timer_2)); +} + +void concurrencpp::tests::test_timer_set_frequency_before_due_time() { + auto timer_queue = std::make_shared(); + timer_tester tester(1'000ms, 1'000ms, timer_queue); + tester.start_timer_test(); + + tester.reset(500ms); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + tester.test_frequencies(500); +} + +void concurrencpp::tests::test_timer_set_frequency_after_due_time() { + auto timer_queue = std::make_shared(); + timer_tester tester(1'000ms, 1'000ms, timer_queue); + tester.start_timer_test(); + + std::this_thread::sleep_for(1200ms); + + tester.reset(500ms); + + std::this_thread::sleep_for(5s); + + tester.test_frequencies(500); +} + +void concurrencpp::tests::test_timer_set_frequency() { + assert_throws([] { + timer timer; + timer.set_frequency(200ms); + }); + + test_timer_set_frequency_before_due_time(); + test_timer_set_frequency_after_due_time(); +} + +void concurrencpp::tests::test_timer_oneshot_timer() { + auto timer_queue = std::make_shared(); + timer_tester tester(350ms, 0ms, timer_queue); + tester.start_once_timer_test(); + + std::this_thread::sleep_for(5s); + + tester.test_oneshot_timer(); +} + +void concurrencpp::tests::test_timer_delay_object() { + auto timer_queue = std::make_shared(); + auto wt_executor = std::make_shared(); + const auto expected_interval = 150ms; + + for (size_t i = 0; i < 15; i++) { + const auto before = std::chrono::high_resolution_clock::now(); + auto delay_object = timer_queue->make_delay_object(expected_interval, wt_executor); + assert_true(static_cast(delay_object)); + delay_object.get(); + const auto after = std::chrono::high_resolution_clock::now(); + const auto interval_ms = duration_cast(after - before); + timer_tester::interval_ok(interval_ms.count(), expected_interval.count()); + } + + wt_executor->shutdown(); +} + +void concurrencpp::tests::test_timer_assignment_operator_empty_to_empty() { + concurrencpp::timer timer1, timer2; + assert_false(timer1); + assert_false(timer2); + + timer1 = std::move(timer2); + + assert_false(timer1); + assert_false(timer2); +} + +void concurrencpp::tests::test_timer_assignment_operator_non_empty_to_non_empty() { + auto timer_queue = std::make_shared(); + + auto executor0 = std::make_shared(); + auto executor1 = std::make_shared(); + + object_observer observer0, observer1; + + auto timer0 = timer_queue->make_timer(10 * 1'000ms, 10 * 1'000ms, executor0, observer0.get_testing_stub()); + auto timer1 = timer_queue->make_timer(20 * 1'000ms, 20 * 1'000ms, executor1, observer1.get_testing_stub()); + + timer0 = std::move(timer1); + + assert_true(timer0); + assert_false(timer1); + + assert_true(observer0.wait_destruction_count(1, 20s)); + assert_equal(observer1.get_destruction_count(), size_t(0)); + + assert_equal(timer0.get_due_time(), 20'000ms); + assert_equal(timer0.get_frequency(), 20'000ms); + assert_equal(timer0.get_executor(), executor1); +} + +void concurrencpp::tests::test_timer_assignment_operator_empty_to_non_empty() { + auto timer_queue = std::make_shared(); + auto executor = std::make_shared(); + object_observer observer; + + auto timer0 = timer_queue->make_timer(10 * 1'000ms, 10 * 1'000ms, executor, observer.get_testing_stub()); + concurrencpp::timer timer1; + + timer0 = std::move(timer1); + + assert_false(timer0); + assert_false(timer1); + + assert_true(observer.wait_destruction_count(1, 20s)); +} + +void concurrencpp::tests::test_timer_assignment_operator_non_empty_to_empty() { + auto timer_queue = std::make_shared(); + auto executor = std::make_shared(); + object_observer observer; + + concurrencpp::timer timer0; + auto timer1 = timer_queue->make_timer(10 * 1'000ms, 10 * 1'000ms, executor, observer.get_testing_stub()); + + timer0 = std::move(timer1); + + assert_true(timer0); + assert_false(timer1); + + assert_equal(timer0.get_due_time(), 10'000ms); + assert_equal(timer0.get_frequency(), 10'000ms); + assert_equal(timer0.get_executor(), executor); +} + +void concurrencpp::tests::test_timer_assignment_operator_assign_to_self() { + auto timer_queue = std::make_shared(); + auto executor = std::make_shared(); + object_observer observer; + + auto timer = timer_queue->make_timer(1'000ms, 1'000ms, executor, observer.get_testing_stub()); + + timer = std::move(timer); + + assert_true(timer); + assert_equal(timer.get_due_time(), 1'000ms); + assert_equal(timer.get_frequency(), 1'000ms); + assert_equal(timer.get_executor(), executor); +} + +void concurrencpp::tests::test_timer_assignment_operator() { + test_timer_assignment_operator_empty_to_empty(); + test_timer_assignment_operator_non_empty_to_non_empty(); + test_timer_assignment_operator_empty_to_non_empty(); + test_timer_assignment_operator_non_empty_to_empty(); + test_timer_assignment_operator_assign_to_self(); +} + +void concurrencpp::tests::test_timer() { + tester test("timer test"); + + test.add_step("constructor", test_timer_constructor); + test.add_step("destructor", test_timer_destructor); + test.add_step("cancel", test_timer_cancel); + test.add_step("operator bool", test_timer_operator_bool); + test.add_step("set_frequency", test_timer_set_frequency); + test.add_step("oneshot_timer", test_timer_oneshot_timer); + test.add_step("delay_object", test_timer_delay_object); + test.add_step("operator =", test_timer_assignment_operator); + + test.launch_test(); +} diff --git a/test/source/thread_sanitizer/executors.cpp b/test/source/thread_sanitizer/executors.cpp new file mode 100644 index 00000000..b983386d --- /dev/null +++ b/test/source/thread_sanitizer/executors.cpp @@ -0,0 +1,158 @@ +#include "concurrencpp/concurrencpp.h" + +#include + +void test_worker_thread_executor(); +void test_thread_pool_executor(); +void test_thread_executor(); +void test_manual_executor(); + +int main() { + std::cout << "concurrencpp::worker_thread_executor" << std::endl; + test_worker_thread_executor(); + std::cout << "====================================" << std::endl; + + std::cout << "concurrencpp::thread_pool_executor" << std::endl; + test_thread_pool_executor(); + std::cout << "====================================" << std::endl; + + std::cout << "concurrencpp::thread_executor" << std::endl; + test_thread_executor(); + std::cout << "====================================" << std::endl; + + std::cout << "concurrencpp::manual_executor" << std::endl; + test_manual_executor(); + std::cout << "====================================" << std::endl; +} + +using namespace concurrencpp; + +void worker_thread_task(std::shared_ptr (&executors)[16], + std::atomic_size_t& counter, + std::shared_ptr wc) { + const auto c = counter.fetch_add(1, std::memory_order_relaxed); + + if (c >= 10'000'000) { + if (c == 10'000'000) { + wc->notify(); + } + + return; + } + + const auto worker_pos = ::rand() % std::size(executors); + auto& executor = executors[worker_pos]; + executor->post(worker_thread_task, std::ref(executors), std::ref(counter), wc); +} + +void test_worker_thread_executor() { + concurrencpp::runtime runtime; + + std::srand(::time(nullptr)); + std::shared_ptr executors[16]; + std::atomic_size_t counter = 0; + auto wc = std::make_shared(); + + for (auto& executor : executors) { + executor = runtime.make_worker_thread_executor(); + } + + for (size_t i = 0; i < 16; i++) { + executors[i]->post(worker_thread_task, std::ref(executors), std::ref(counter), wc); + } + + wc->wait(); +} + +void thread_pool_task(std::shared_ptr tpe, std::atomic_size_t& counter, std::shared_ptr wc) { + const auto c = counter.fetch_add(1, std::memory_order_relaxed); + + if (c >= 10'000'000) { + if (c == 10'000'000) { + wc->notify(); + } + + return; + } + + tpe->post(thread_pool_task, tpe, std::ref(counter), wc); +} + +void test_thread_pool_executor() { + concurrencpp::runtime runtime; + auto tpe = runtime.thread_pool_executor(); + std::atomic_size_t counter = 0; + auto wc = std::make_shared(); + const auto max_concurrency_level = tpe->max_concurrency_level(); + + for (size_t i = 0; i < max_concurrency_level; i++) { + tpe->post(thread_pool_task, tpe, std::ref(counter), wc); + } + + wc->wait(); +} + +void thread_task(std::shared_ptr tp, std::atomic_size_t& counter, std::shared_ptr wc) { + const auto c = counter.fetch_add(1, std::memory_order_relaxed); + if (c >= 1'024 * 4) { + if (c == 1'024 * 4) { + wc->notify(); + } + + return; + } + + tp->post(thread_task, tp, std::ref(counter), wc); +} + +void test_thread_executor() { + concurrencpp::runtime runtime; + auto tp = runtime.thread_executor(); + std::atomic_size_t counter = 0; + auto wc = std::make_shared(); + + for (size_t i = 0; i < 4; i++) { + tp->post(thread_task, tp, std::ref(counter), wc); + } + + wc->wait(); +} + +void manual_executor_work_loop(std::shared_ptr (&executors)[16], std::atomic_size_t& counter, const size_t worker_index) { + + while (true) { + const auto c = counter.fetch_add(1, std::memory_order_relaxed); + + if (c >= 10'000'000) { + return; + } + + const auto worker_pos = ::rand() % std::size(executors); + auto& executor = executors[worker_pos]; + executor->post([] { + }); + + executors[worker_index]->loop(16); + } +} + +void test_manual_executor() { + concurrencpp::runtime runtime; + std::atomic_size_t counter = 0; + std::shared_ptr executors[16]; + std::thread threads[16]; + + for (auto& executor : executors) { + executor = runtime.make_manual_executor(); + } + + for (size_t i = 0; i < std::size(executors); i++) { + threads[i] = std::thread([&, i] { + manual_executor_work_loop(executors, std::ref(counter), i); + }); + } + + for (auto& thread : threads) { + thread.join(); + } +} diff --git a/test/source/thread_sanitizer/fibbonacci.cpp b/test/source/thread_sanitizer/fibbonacci.cpp new file mode 100644 index 00000000..0e757f47 --- /dev/null +++ b/test/source/thread_sanitizer/fibbonacci.cpp @@ -0,0 +1,54 @@ +#include "concurrencpp/concurrencpp.h" + +#include + +concurrencpp::result fibbonacci(concurrencpp::executor_tag, std::shared_ptr tpe, int curr); +int fibbonacci_sync(int i); + +int main() { + concurrencpp::runtime_options opts; + opts.max_cpu_threads = 24; + concurrencpp::runtime runtime(opts); + + auto fibb = fibbonacci(concurrencpp::executor_tag {}, runtime.thread_pool_executor(), 32).get(); + auto fibb_sync = fibbonacci_sync(32); + if (fibb != fibb_sync) { + std::cerr << "fibonnacci test failed. expected " << fibb_sync << " got " << fibb << std::endl; + std::abort(); + } + + std::cout << fibb << std::endl; +} + +using namespace concurrencpp; + +result fibbonacci_split(std::shared_ptr tpe, const int curr) { + auto fib_1 = fibbonacci(executor_tag {}, tpe, curr - 1); + auto fib_2 = fibbonacci(executor_tag {}, tpe, curr - 2); + + co_return co_await fib_1 + co_await fib_2; +} + +result fibbonacci(executor_tag, std::shared_ptr tpe, const int curr) { + if (curr == 1) { + co_return 1; + } + + if (curr == 0) { + co_return 0; + } + + co_return co_await fibbonacci_split(tpe, curr); +} + +int fibbonacci_sync(int i) { + if (i == 0) { + return 0; + } + + if (i == 1) { + return 1; + } + + return fibbonacci_sync(i - 1) + fibbonacci_sync(i - 2); +} diff --git a/test/source/thread_sanitizer/matrix_multiplication.cpp b/test/source/thread_sanitizer/matrix_multiplication.cpp new file mode 100644 index 00000000..4aa6ba68 --- /dev/null +++ b/test/source/thread_sanitizer/matrix_multiplication.cpp @@ -0,0 +1,86 @@ +#include "concurrencpp/concurrencpp.h" + +#include +#include +#include +#include + +concurrencpp::result test_matrix_multiplication(std::shared_ptr ex); + +int main() { + concurrencpp::runtime runtime; + test_matrix_multiplication(runtime.thread_pool_executor()).get(); +} + +using namespace concurrencpp; + +using matrix = std::array, 1024>; + +std::unique_ptr make_matrix() { + std::default_random_engine generator; + std::uniform_real_distribution distribution(-1000.0, 1000.0); + auto mtx_ptr = std::make_unique(); + auto& mtx = *mtx_ptr; + + for (size_t i = 0; i < 1024; i++) { + for (size_t j = 0; j < 1024; j++) { + mtx[i][j] = distribution(generator); + } + } + + return mtx_ptr; +} + +void test_matrix(const matrix& mtx0, const matrix& mtx1, const matrix& mtx2) { + for (size_t i = 0; i < 1024; i++) { + for (size_t j = 0; j < 1024; j++) { + + double res = 0.0; + + for (size_t k = 0; k < 1024; k++) { + res += mtx0[i][k] * mtx1[k][j]; + } + + if (mtx2[i][j] != res) { + std::cerr << "matrix multiplication test failed. expected " << res << " got " << mtx2[i][j] << "at matix position[" << i << "," << j << std::endl; + } + } + } +} + +result +do_multiply(executor_tag, std::shared_ptr executor, const matrix& mtx0, const matrix& mtx1, matrix& mtx2, size_t line, size_t col) { + + double res = 0.0; + for (size_t i = 0; i < 1024; i++) { + res += mtx0[line][i] * mtx1[i][col]; + } + + mtx2[line][col] = res; + co_return; +}; + +result test_matrix_multiplication(std::shared_ptr ex) { + auto mtx0_ptr = make_matrix(); + auto mtx1_ptr = make_matrix(); + auto mtx2_ptr = std::make_unique(); + + matrix& mtx0 = *mtx0_ptr; + matrix& mtx1 = *mtx1_ptr; + matrix& mtx2 = *mtx2_ptr; + + std::vector> results; + results.reserve(1024 * 1024); + + for (size_t i = 0; i < 1024; i++) { + for (size_t j = 0; j < 1024; j++) { + results.emplace_back(do_multiply({}, ex, mtx0, mtx1, mtx2, i, j)); + } + } + + for (auto& result : results) { + co_await result; + } + + test_matrix(mtx0, mtx1, mtx2); +} diff --git a/test/source/thread_sanitizer/quick_sort.cpp b/test/source/thread_sanitizer/quick_sort.cpp new file mode 100644 index 00000000..458736bb --- /dev/null +++ b/test/source/thread_sanitizer/quick_sort.cpp @@ -0,0 +1,89 @@ +#include "concurrencpp/concurrencpp.h" + +#include +#include + +concurrencpp::result quick_sort(concurrencpp::executor_tag, std::shared_ptr tp, int* a, int lo, int hi); + +int main() { + concurrencpp::runtime_options opts; + opts.max_cpu_threads = 24; + concurrencpp::runtime runtime(opts); + + ::srand(::time(nullptr)); + + std::vector array(8 * 1'000'000); + for (auto& i : array) { + i = rand() % 10 * 10'000; + } + + quick_sort({}, runtime.thread_pool_executor(), array.data(), 0, array.size() - 1).get(); + + const auto is_sorted = std::is_sorted(array.begin(), array.end()); + if (!is_sorted) { + std::cerr << "array is not sorted." << std::endl; + } +} + +using namespace concurrencpp; + +/* + +algorithm partition(A, lo, hi) is + pivot := A[(hi + lo) / 2] + i := lo - 1 + j := hi + 1 + loop forever + do + i := i + 1 + while A[i] < pivot + do + j := j - 1 + while A[j] > pivot + if i ≥ j then + return j + swap A[i] with A[j] +*/ + +int partition(int* a, int lo, int hi) { + const auto pivot = a[(hi + lo) / 2]; + auto i = lo - 1; + auto j = hi + 1; + + while (true) { + do { + ++i; + } while (a[i] < pivot); + + do { + --j; + } while (a[j] > pivot); + + if (i >= j) { + return j; + } + + std::swap(a[i], a[j]); + } +} + +/* + algorithm quicksort(A, lo, hi) is + if lo < hi then + p := partition(A, lo, hi) + quicksort(A, lo, p) + quicksort(A, p + 1, hi) + +*/ +result quick_sort(executor_tag, std::shared_ptr tp, int* a, int lo, int hi) { + if (lo >= hi) { + co_return; + } + + const auto p = partition(a, lo, hi); + auto res0 = quick_sort(executor_tag {}, tp, a, lo, p); + auto res1 = quick_sort(executor_tag {}, tp, a, p + 1, hi); + + co_await res0; + co_await res1; +} diff --git a/thread_sanitizers/result/main.cpp b/test/source/thread_sanitizer/result.cpp similarity index 65% rename from thread_sanitizers/result/main.cpp rename to test/source/thread_sanitizer/result.cpp index b64f3d1e..8b734594 100644 --- a/thread_sanitizers/result/main.cpp +++ b/test/source/thread_sanitizer/result.cpp @@ -1,4 +1,4 @@ -#include "concurrencpp.h" +#include "concurrencpp/concurrencpp.h" #include @@ -9,29 +9,29 @@ concurrencpp::result result_await(std::shared_ptr result_await_via(std::shared_ptr tp); int main() { - concurrencpp::runtime_options opts; - opts.max_cpu_threads = 24; - concurrencpp::runtime runtime(opts); - - std::cout << "result::get" << std::endl; - result_get(runtime.thread_pool_executor()); - std::cout << "================================" << std::endl; - - std::cout << "result::wait" << std::endl; - result_wait(runtime.thread_pool_executor()); - std::cout << "================================" << std::endl; - - std::cout << "result::wait_for" << std::endl; - result_wait_for(runtime.thread_pool_executor()); - std::cout << "================================" << std::endl; - - std::cout << "result::await" << std::endl; - result_await(runtime.thread_pool_executor()).get(); - std::cout << "================================" << std::endl; - - std::cout << "result::await_via" << std::endl; - result_await_via(runtime.thread_pool_executor()).get(); - std::cout << "================================" << std::endl; + concurrencpp::runtime_options opts; + opts.max_cpu_threads = 24; + concurrencpp::runtime runtime(opts); + + std::cout << "result::get" << std::endl; + result_get(runtime.thread_pool_executor()); + std::cout << "================================" << std::endl; + + std::cout << "result::wait" << std::endl; + result_wait(runtime.thread_pool_executor()); + std::cout << "================================" << std::endl; + + std::cout << "result::wait_for" << std::endl; + result_wait_for(runtime.thread_pool_executor()); + std::cout << "================================" << std::endl; + + std::cout << "result::await" << std::endl; + result_await(runtime.thread_pool_executor()).get(); + std::cout << "================================" << std::endl; + + std::cout << "result::await_via" << std::endl; + result_await_via(runtime.thread_pool_executor()).get(); + std::cout << "================================" << std::endl; } using namespace concurrencpp; @@ -43,9 +43,9 @@ void result_get(std::shared_ptr tp) { results.reserve(task_count); for (size_t i = 0; i < task_count; i++) { - results.emplace_back(tp->submit([i] { - return int(i); - })); + results.emplace_back(tp->submit([i] { + return int(i); + })); } for (size_t i = 0; i < task_count; i++) { @@ -64,13 +64,13 @@ void result_wait(std::shared_ptr tp) { results.reserve(task_count); for (size_t i = 0; i < task_count; i++) { - results.emplace_back(tp->submit([i] { - return int(i); - })); + results.emplace_back(tp->submit([i] { + return int(i); + })); } for (size_t i = 0; i < task_count; i++) { - results[i].wait(); + results[i].wait(); auto res = results[i].get(); if (res != i) { std::cerr << "submit + get, expected " << i << " and got " << res << std::endl; @@ -86,14 +86,15 @@ void result_wait_for(std::shared_ptr tp) { results.reserve(task_count); for (size_t i = 0; i < task_count; i++) { - results.emplace_back(tp->submit([i] { - return int(i); - })); + results.emplace_back(tp->submit([i] { + return int(i); + })); } for (size_t i = 0; i < task_count; i++) { - while(results[i].wait_for(std::chrono::milliseconds(5)) == result_status::idle); - + while (results[i].wait_for(std::chrono::milliseconds(5)) == result_status::idle) + ; + auto res = results[i].get(); if (res != i) { std::cerr << "submit + get, expected " << i << " and got " << res << std::endl; @@ -109,9 +110,9 @@ result result_await(std::shared_ptr tp) { results.reserve(task_count); for (size_t i = 0; i < task_count; i++) { - results.emplace_back(tp->submit([i] { - return int(i); - })); + results.emplace_back(tp->submit([i] { + return int(i); + })); } for (size_t i = 0; i < task_count; i++) { @@ -130,9 +131,9 @@ result result_await_via(std::shared_ptr tp) { results.reserve(task_count); for (size_t i = 0; i < task_count; i++) { - results.emplace_back(tp->submit([i] { - return int(i); - })); + results.emplace_back(tp->submit([i] { + return int(i); + })); } for (size_t i = 0; i < task_count; i++) { @@ -142,4 +143,4 @@ result result_await_via(std::shared_ptr tp) { std::abort(); } } -} \ No newline at end of file +} diff --git a/test/source/thread_sanitizer/when_all.cpp b/test/source/thread_sanitizer/when_all.cpp new file mode 100644 index 00000000..42cc24bb --- /dev/null +++ b/test/source/thread_sanitizer/when_all.cpp @@ -0,0 +1,65 @@ +#include "concurrencpp/concurrencpp.h" + +#include + +concurrencpp::result when_all_test(std::shared_ptr tpe); + +int main() { + concurrencpp::runtime runtime; + when_all_test(runtime.thread_executor()).get(); + return 0; +} + +using namespace concurrencpp; + +std::vector> run_loop_once(std::shared_ptr tpe) { + std::vector> results; + results.reserve(1'024); + + for (size_t i = 0; i < 1'024; i++) { + results.emplace_back(tpe->submit([] { + std::this_thread::yield(); + return 0; + })); + } + + return results; +} + +void assert_loop(result>> result_list) { + if (result_list.status() != result_status::value) { + std::abort(); + } + + auto results = result_list.get(); + + for (auto& result : results) { + if (result.get() != 0) { + std::abort(); + } + } +} + +result when_all_test(std::shared_ptr tpe) { + auto loop_0 = run_loop_once(tpe); + auto loop_1 = run_loop_once(tpe); + auto loop_2 = run_loop_once(tpe); + auto loop_3 = run_loop_once(tpe); + auto loop_4 = run_loop_once(tpe); + + auto loop_0_all = when_all(loop_0.begin(), loop_0.end()); + auto loop_1_all = when_all(loop_1.begin(), loop_1.end()); + auto loop_2_all = when_all(loop_2.begin(), loop_2.end()); + auto loop_3_all = when_all(loop_3.begin(), loop_3.end()); + auto loop_4_all = when_all(loop_4.begin(), loop_4.end()); + + auto all = when_all(std::move(loop_0_all), std::move(loop_1_all), std::move(loop_2_all), std::move(loop_3_all), std::move(loop_4_all)); + + auto all_done = co_await all; + + assert_loop(std::move(std::get<0>(all_done))); + assert_loop(std::move(std::get<1>(all_done))); + assert_loop(std::move(std::get<2>(all_done))); + assert_loop(std::move(std::get<3>(all_done))); + assert_loop(std::move(std::get<4>(all_done))); +} diff --git a/test/source/thread_sanitizer/when_any.cpp b/test/source/thread_sanitizer/when_any.cpp new file mode 100644 index 00000000..62eb0c01 --- /dev/null +++ b/test/source/thread_sanitizer/when_any.cpp @@ -0,0 +1,118 @@ +#include "concurrencpp/concurrencpp.h" + +#include +#include + +concurrencpp::result when_any_vector_test(std::shared_ptr te); +concurrencpp::result when_any_tuple_test(std::shared_ptr te); + +int main() { + concurrencpp::runtime runtime; + when_any_vector_test(runtime.thread_executor()).get(); + std::cout << "=========================================" << std::endl; + when_any_tuple_test(runtime.thread_executor()).get(); + std::cout << "=========================================" << std::endl; + return 0; +} + +using namespace concurrencpp; + +struct random_ctx { + std::random_device rd; + std::mt19937 mt; + std::uniform_int_distribution dist; + + random_ctx() : mt(rd()), dist(1, 5 * 1000) {} + + size_t operator()() noexcept { + return dist(mt); + } +}; + +std::vector> run_loop_once(std::shared_ptr te, random_ctx& r, size_t count) { + std::vector> results; + results.reserve(count); + + for (size_t i = 0; i < count; i++) { + const auto sleeping_time = r(); + results.emplace_back(te->submit([sleeping_time] { + std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time)); + return 0; + })); + } + + return results; +} + +result when_any_vector_test(std::shared_ptr te) { + random_ctx r; + + auto loop = run_loop_once(te, r, 1'024); + + while (!loop.empty()) { + auto any = co_await when_any(loop.begin(), loop.end()); + auto& done_result = any.results[any.index]; + + if (done_result.status() != result_status::value) { + std::abort(); + } + + if (done_result.get() != 0) { + std::abort(); + } + + any.results.erase(any.results.begin() + any.index); + loop = std::move(any.results); + } +} + +template +std::vector> to_vector(std::tuple& results) { + return std::apply( + [](auto&&... elems) { + std::vector> result; + result.reserve(sizeof...(elems)); + (result.emplace_back(std::move(elems)), ...); + return result; + }, + std::forward(results)); +} + +concurrencpp::result when_any_tuple_test(std::shared_ptr te) { + random_ctx r; + auto loop = run_loop_once(te, r, 10); + + for (size_t i = 0; i < 256; i++) { + auto any = co_await when_any(std::move(loop[0]), + std::move(loop[1]), + std::move(loop[2]), + std::move(loop[3]), + std::move(loop[4]), + std::move(loop[5]), + std::move(loop[6]), + std::move(loop[7]), + std::move(loop[8]), + std::move(loop[9])); + + loop = to_vector(any.results); + + const auto done_index = any.index; + auto& done_result = loop[done_index]; + + if (done_result.status() != result_status::value) { + std::abort(); + } + + if (done_result.get() != 0) { + std::abort(); + } + + loop.erase(loop.begin() + done_index); + + const auto sleeping_time = r(); + loop.emplace_back(te->submit([sleeping_time] { + std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time)); + return 0; + })); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index a78566de..00000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(tests) - -add_executable( - tests - main.cpp - helpers/assertions.cpp - helpers/object_observer.cpp - - tester/tester.cpp - - tests/all_tests.cpp - tests/runtime_tests.cpp - - tests/timer_tests/timer_tests.cpp - tests/timer_tests/timer_queue_tests.cpp - - tests/executor_tests/inline_executor_tests.cpp - tests/executor_tests/manual_executor_tests.cpp - tests/executor_tests/thread_executor_tests.cpp - tests/executor_tests/thread_pool_executor_tests.cpp - tests/executor_tests/worker_thread_executor_tests.cpp - - tests/result_tests/result_tests.cpp - tests/result_tests/result_await_tests.cpp - tests/result_tests/result_resolve_tests.cpp - tests/result_tests/result_promise_tests.cpp - tests/result_tests/make_result_tests.cpp - tests/result_tests/when_all_tests.cpp - tests/result_tests/when_any_tests.cpp - - tests/coroutine_tests/coroutine_promises_test.cpp - tests/coroutine_tests/coroutines_tests.cpp -) - -target_link_libraries(tests concurrencpp) diff --git a/tests/helpers/assertions.cpp b/tests/helpers/assertions.cpp deleted file mode 100644 index eae45df4..00000000 --- a/tests/helpers/assertions.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "assertions.h" - -#include - -#include -#include - -namespace concurrencpp::tests::details { - std::string to_string(bool value) { - return value ? "true" : "false"; - } - - std::string to_string(int value) { - return std::to_string(value); - } - - std::string to_string(long value) { - return std::to_string(value); - } - - std::string to_string(long long value) { - return std::to_string(value); - } - - std::string to_string(unsigned value) { - return std::to_string(value); - } - - std::string to_string(unsigned long value) { - return std::to_string(value); - } - - std::string to_string(unsigned long long value) { - return std::to_string(value); - } - - std::string to_string(float value) { - return std::to_string(value); - } - - std::string to_string(double value) { - return std::to_string(value); - } - - std::string to_string(long double value) { - return std::to_string(value); - } - - const std::string& to_string(const std::string& str) { - return str; - } - - std::string to_string(const char* str) { - return str; - } - - std::string to_string(const std::string_view str) { - return { str.begin(), str.end() }; - } - - std::string to_string(std::thread::id id) { - std::stringstream stream; - stream << id; - return stream.str(); - } - - std::string to_string(std::chrono::time_point time_point) { - auto time_from_epoch = time_point.time_since_epoch(); - auto seconds_from_epoch = std::chrono::duration_cast(time_from_epoch); - return std::string("time_from_epoch[") + std::to_string(seconds_from_epoch.count()) + "]"; - } - - void assert_same_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected ["; - error_msg += a; - error_msg += "] == ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - - void assert_not_same_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected: ["; - error_msg += a; - error_msg += "] =/= ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - - void assert_bigger_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected ["; - error_msg += a; - error_msg += "] > ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - - void assert_smaller_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected ["; - error_msg += a; - error_msg += "] < ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - - void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected ["; - error_msg += a; - error_msg += "] >= ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - - void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b) { - std::string error_msg = "assertion failed. "; - error_msg += "expected ["; - error_msg += a; - error_msg += "] <= ["; - error_msg += b; - error_msg += "]"; - - fprintf(stderr, "%s", error_msg.data()); - std::abort(); - } - -} - -namespace concurrencpp::tests { - void assert_true(bool condition) { - if (!condition) { - fprintf(stderr, "%s", "assertion faild. expected: [true] actual: [false]."); - std::abort(); - } - } - - void assert_false(bool condition) { - if (condition) { - fprintf(stderr, "%s", "assertion faild. expected: [false] actual: [true]."); - std::abort(); - } - } -} diff --git a/tests/helpers/assertions.h b/tests/helpers/assertions.h deleted file mode 100644 index 35f03126..00000000 --- a/tests/helpers/assertions.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef CONCURRENCPP_ASSERTIONS_H -#define CONCURRENCPP_ASSERTIONS_H - -#include -#include -#include - -namespace concurrencpp::tests::details { - std::string to_string(bool value); - std::string to_string(int value); - std::string to_string(long value); - std::string to_string(long long value); - std::string to_string(unsigned value); - std::string to_string(unsigned long value); - std::string to_string(unsigned long long value); - std::string to_string(float value); - std::string to_string(double value); - std::string to_string(long double value); - const std::string& to_string(const std::string& str); - std::string to_string(const char* str); - std::string to_string(const std::string_view str); - std::string to_string(std::thread::id); - std::string to_string(std::chrono::time_point time_point); - - template - std::string to_string(type* value) { - return std::string("pointer[") + - to_string(reinterpret_cast(value)) + - "]"; - } - - template - std::string to_string(const type&) { - return "{object}"; - } - - void assert_same_failed_impl(const std::string& a, const std::string& b); - void assert_not_same_failed_impl(const std::string& a, const std::string& b); - void assert_bigger_failed_impl(const std::string& a, const std::string& b); - void assert_smaller_failed_impl(const std::string& a, const std::string& b); - void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b); - void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b); -} - -namespace concurrencpp::tests { - void assert_true(bool condition); - void assert_false(bool condition); - - template - void assert_equal(const a_type& given_value, const b_type& expected_value) { - if (given_value == expected_value) { - return; - } - - details::assert_same_failed_impl(details::to_string(given_value), details::to_string(expected_value)); - } - - template - inline void assert_not_equal(const a_type& given_value, const b_type& expected_value) { - if (given_value != expected_value) { - return; - } - - details::assert_same_failed_impl( - details::to_string(given_value), - details::to_string(expected_value)); - } - - template - void assert_bigger(const a_type& given_value, const b_type& expected_value) { - if (given_value > expected_value) { - return; - } - - details::assert_bigger_failed_impl( - details::to_string(given_value), - details::to_string(expected_value)); - } - - template - void assert_smaller(const a_type& given_value, const b_type& expected_value) { - if (given_value < expected_value) { - return; - } - - details::assert_smaller_failed_impl( - details::to_string(given_value), - details::to_string(expected_value)); - } - - template - void assert_bigger_equal(const a_type& given_value, const b_type& expected_value) { - if (given_value >= expected_value) { - return; - } - - details::assert_bigger_equal_failed_impl( - details::to_string(given_value), - details::to_string(expected_value)); - } - - template - void assert_smaller_equal(const a_type& given_value, const b_type& expected_value) { - if (given_value <= expected_value) { - return; - } - - details::assert_smaller_equal_failed_impl( - details::to_string(given_value), - details::to_string(expected_value)); - } - - template - void assert_throws(task_type&& task) { - try { - task(); - } - catch (const exception_type&) { return; } - catch (...) {} - - assert_false(true); - } - - template - void assert_throws_with_error_message(task_type&& task, std::string_view error_msg) { - try { - task(); - } - catch (const exception_type& e) { - assert_equal(error_msg, e.what()); - return; - } - catch (...) {} - - assert_false(true); - } - -} - -#endif //CONCURRENCPP_ASSERTIONS_H \ No newline at end of file diff --git a/tests/helpers/object_observer.cpp b/tests/helpers/object_observer.cpp deleted file mode 100644 index 29210424..00000000 --- a/tests/helpers/object_observer.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "concurrencpp.h" -#include "object_observer.h" - -namespace concurrencpp::tests::details { - class object_observer_state { - - private: - mutable std::mutex m_lock; - mutable std::condition_variable m_condition; - std::unordered_map m_execution_map; - size_t m_destruction_count; - size_t m_execution_count; - - public: - object_observer_state() : - m_destruction_count(0), - m_execution_count(0){} - - size_t get_destruction_count() const noexcept { - std::unique_lock lock(m_lock); - return m_destruction_count; - } - - size_t get_execution_count() const noexcept { - std::unique_lock lock(m_lock); - return m_execution_count; - } - - std::unordered_map get_execution_map() const noexcept { - std::unique_lock lock(m_lock); - return m_execution_map; - } - - bool wait_execution_count(size_t count, std::chrono::milliseconds timeout) { - std::unique_lock lock(m_lock); - return m_condition.wait_for(lock, timeout, [count, this] { - return count == m_execution_count; - }); - } - - void on_execute() { - const auto this_id = ::concurrencpp::details::thread::get_current_virtual_id(); - - { - std::unique_lock lock(m_lock); - ++m_execution_count; - ++m_execution_map[this_id]; - } - - m_condition.notify_all(); - } - - bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { - std::unique_lock lock(m_lock); - return m_condition.wait_for(lock, timeout, [count, this] { - return count == m_destruction_count; - }); - } - - void on_destroy() { - { - std::unique_lock lock(m_lock); - ++m_destruction_count; - } - - m_condition.notify_all(); - } - }; -} - -using concurrencpp::tests::testing_stub; -using concurrencpp::tests::object_observer; -using concurrencpp::tests::value_testing_stub; - -testing_stub& testing_stub::operator = (testing_stub&& rhs) noexcept { - if (this == &rhs) { - return *this; - } - - if (static_cast(m_state)) { - m_state->on_destroy(); - } - - m_state = std::move(rhs.m_state); - return *this; -} - -void testing_stub::operator()() noexcept { - if (static_cast(m_state)) { - if (m_dummy_work_time != std::chrono::milliseconds(0)) { - std::this_thread::sleep_for(m_dummy_work_time); - } - - m_state->on_execute(); - } -} - -value_testing_stub& value_testing_stub::operator = (value_testing_stub&& rhs) noexcept { - testing_stub::operator=(std::move(rhs)); - return *this; -} - -size_t value_testing_stub::operator()() noexcept { - testing_stub::operator()(); - return m_return_value; -} - -object_observer::object_observer() : m_state(std::make_shared()) {} - -testing_stub object_observer::get_testing_stub(std::chrono::milliseconds dummy_work_time) noexcept { - return { m_state, dummy_work_time }; -} - -value_testing_stub object_observer::get_testing_stub(int value, std::chrono::milliseconds dummy_work_time) noexcept { - return { m_state, dummy_work_time, value }; -} - -value_testing_stub object_observer::get_testing_stub(size_t value, std::chrono::milliseconds dummy_work_time) noexcept { - return { m_state, dummy_work_time, static_cast(value) }; -} - -bool object_observer::wait_execution_count(size_t count, std::chrono::milliseconds timeout) { - return m_state->wait_execution_count(count, timeout); -} - -bool object_observer::wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { - return m_state->wait_destruction_count(count, timeout); -} - -size_t object_observer::get_destruction_count() const noexcept { - return m_state->get_destruction_count(); -} - -size_t object_observer::get_execution_count() const noexcept { - return m_state->get_execution_count(); -} - -std::unordered_map object_observer::get_execution_map() const noexcept { - return m_state->get_execution_map(); -} - -testing_stub::~testing_stub() noexcept { - if (static_cast(m_state)) { - m_state->on_destroy(); - } -} \ No newline at end of file diff --git a/tests/helpers/object_observer.h b/tests/helpers/object_observer.h deleted file mode 100644 index 3d02da7d..00000000 --- a/tests/helpers/object_observer.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef CONCURRENCPP_OBJECT_OBSERVER_H -#define CONCURRENCPP_OBJECT_OBSERVER_H - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace concurrencpp::tests { - - namespace details { - class object_observer_state; - } - - class testing_stub { - - protected: - std::shared_ptr m_state; - const std::chrono::milliseconds m_dummy_work_time; - - public: - testing_stub() noexcept : m_dummy_work_time(0) {} - - testing_stub( - std::shared_ptr state, - const std::chrono::milliseconds dummy_work_time) noexcept : - m_state(std::move(state)), - m_dummy_work_time(dummy_work_time) {} - - testing_stub(testing_stub&& rhs) noexcept = default; - - ~testing_stub() noexcept; - - testing_stub& operator = (testing_stub&& rhs) noexcept; - - void operator()() noexcept; - }; - - class value_testing_stub : public testing_stub { - - private: - const size_t m_return_value; - - public: - value_testing_stub(size_t return_value) noexcept : m_return_value(return_value) { } - - value_testing_stub( - std::shared_ptr state, - const std::chrono::milliseconds dummy_work_time, - int return_value) noexcept : - testing_stub(std::move(state), dummy_work_time), - m_return_value(return_value) { } - - value_testing_stub(value_testing_stub&& rhs) noexcept = default; - - value_testing_stub& operator = (value_testing_stub&& rhs) noexcept; - - size_t operator()() noexcept; - }; - - class object_observer { - - private: - const std::shared_ptr m_state; - - public: - object_observer(); - object_observer(object_observer&&) noexcept = default; - - testing_stub get_testing_stub(std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; - value_testing_stub get_testing_stub(int value, std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; - value_testing_stub get_testing_stub(size_t value, std::chrono::milliseconds dummy_work_time = std::chrono::milliseconds(0)) noexcept; - - bool wait_execution_count(size_t count, std::chrono::milliseconds timeout); - bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout); - - size_t get_destruction_count() const noexcept; - size_t get_execution_count() const noexcept; - - std::unordered_map get_execution_map() const noexcept; - }; - -} - -#endif // !CONCURRENCPP_BEHAVIOURAL_TESTERS_H diff --git a/tests/helpers/random.h b/tests/helpers/random.h deleted file mode 100644 index 3e129f72..00000000 --- a/tests/helpers/random.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CONCURRENCPP_RANDOM_H -#define CONCURRENCPP_RANDOM_H - -#include - -namespace concurrencpp::tests { - class random { - - private: - std::random_device rd; - std::mt19937 mt; - std::uniform_int_distribution dist; - - public: - random() noexcept : - mt(rd()), - dist(-1'000'000, 1'000'000) {} - - intptr_t operator()() { - return dist(mt); - } - - int64_t operator()(int64_t min, int64_t max) { - const auto r = (*this)(); - const auto upper_limit = max - min + 1; - return std::abs(r) % upper_limit + min; - } - }; -} - -#endif //CONCURRENCPP_RANDOM_H diff --git a/tests/helpers/result_generator.h b/tests/helpers/result_generator.h deleted file mode 100644 index 857528fa..00000000 --- a/tests/helpers/result_generator.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_GENERATOR_H -#define CONCURRENCPP_RESULT_GENERATOR_H - -#include "fast_randomizer.h" - -#include - -namespace concurrencpp::tests { - - template - class result_generator {}; - - template<> - class result_generator { - - private: - random m_rand; - - public: - int operator()() noexcept { return static_cast(m_rand()); } - }; - - template<> - class result_generator { - - private: - random m_rand; - - public: - std::string operator()() noexcept { return std::string("123456#") + std::to_string(m_rand()); } - }; - -} - -#endif \ No newline at end of file diff --git a/tests/helpers/thread_helpers.h b/tests/helpers/thread_helpers.h deleted file mode 100644 index 62fe4829..00000000 --- a/tests/helpers/thread_helpers.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef CONCURRENCPP_THREAD_HELPERS_H -#define CONCURRENCPP_THREAD_HELPERS_H - -#include "concurrencpp.h" - -#include -#include - -#include - -namespace concurrencpp::tests { - class test_listener { - - private: - std::atomic_size_t m_total_created; - std::atomic_size_t m_total_destroyed; - std::atomic_size_t m_total_waiting; - std::atomic_size_t m_total_resuming; - std::atomic_size_t m_total_idling; - - public: - test_listener() noexcept : - m_total_created(0), - m_total_destroyed(0), - m_total_waiting(0), - m_total_resuming(0), - m_total_idling(0) {} - - virtual ~test_listener() noexcept = default; - - size_t total_created() const noexcept { return m_total_created.load(); } - size_t total_destroyed() const noexcept { return m_total_destroyed.load(); } - size_t total_waiting() const noexcept { return m_total_waiting.load(); } - size_t total_resuming() const noexcept { return m_total_resuming.load(); } - size_t total_idling() const noexcept { return m_total_idling.load(); } - - /* - virtual void on_thread_created(std::thread::id id) override { - ++m_total_created; - } - - virtual void on_thread_waiting(std::thread::id id) override { - ++m_total_waiting; - } - - virtual void on_thread_resuming(std::thread::id id) override { - ++m_total_resuming; - } - - virtual void on_thread_idling(std::thread::id id) override { - ++m_total_idling; - } - - virtual void on_thread_destroyed(std::thread::id id) override { - ++m_total_destroyed; - } - - void reset() { - m_total_created = 0; - m_total_destroyed = 0; - m_total_waiting = 0; - m_total_resuming = 0; - m_total_idling = 0; - } - */ - }; - - inline std::shared_ptr make_test_listener() { - return std::make_shared(); - } - - class waiter { - - class waiter_impl { - - private: - std::mutex m_lock; - std::condition_variable m_cv; - bool m_unblocked; - - public: - waiter_impl() : m_unblocked(false) {} - - ~waiter_impl() { - notify_all(); - } - - void wait() { - std::unique_lock lock(m_lock); - m_cv.wait(lock, [this] { - return m_unblocked; - }); - - lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - - void notify_all() { - std::unique_lock lock(m_lock); - m_unblocked = true; - m_cv.notify_all(); - } - }; - - private: - const std::shared_ptr m_impl; - - public: - waiter() : m_impl(std::make_shared()) {} - - waiter(const waiter&) noexcept = default; - - void wait() { - m_impl->wait(); - } - - void notify_all() { - m_impl->notify_all(); - } - }; -} - -#endif \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index 26afac39..00000000 --- a/tests/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "concurrencpp.h" -#include "tests/all_tests.h" - -int main() { - concurrencpp::tests::test_all(); - return 0; -} \ No newline at end of file diff --git a/tests/tester/tester.cpp b/tests/tester/tester.cpp deleted file mode 100644 index 55907d93..00000000 --- a/tests/tester/tester.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "tester.h" - -#include - -#include - -using concurrencpp::tests::test_step; -using concurrencpp::tests::tester; -using namespace std::chrono; - -test_step::test_step(const char* step_name, std::function callable) : - m_step_name(step_name), - m_step(std::move(callable)) {} - -void test_step::launch_test_step() noexcept { - const auto test_start_time = system_clock::now(); - printf("\tTest-step started: %s\n", m_step_name); - - m_step(); - - auto elapsed_time = duration_cast(system_clock::now() - test_start_time).count(); - printf("\tTest-step ended (%lldms).\n", elapsed_time); -} - -tester::tester(const char* test_name) noexcept : m_test_name(test_name) {} - -void tester::add_step(const char* step_name, std::function callable) { - m_steps.emplace_back(step_name, std::move(callable)); -} - -void tester::launch_test() noexcept { - const auto test_start_time = system_clock::now(); - printf("Test started: %s\n", m_test_name); - - for (auto& test_step : m_steps) { - try { - test_step.launch_test_step(); - } - catch (const std::exception& ex) { - ::fprintf(stderr, "\tTest step terminated with an exception : %s\n", ex.what()); - } - } - - auto elapsed_time = duration_cast(system_clock::now() - test_start_time).count(); - printf("Test ended (%lldms).\n____________________\n", elapsed_time); -} \ No newline at end of file diff --git a/tests/tester/tester.h b/tests/tester/tester.h deleted file mode 100644 index 0f42ab8a..00000000 --- a/tests/tester/tester.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CONCURRENCPP_TESTER_H -#define CONCURRENCPP_TESTER_H - -#include -#include - -namespace concurrencpp::tests { - class test_step { - - private: - const char* m_step_name; - std::function m_step; - - public: - test_step(const char* step_name, std::function callable); - - void launch_test_step() noexcept; - }; - - class tester { - - private: - const char* m_test_name; - std::deque m_steps; - - void start_test(); - void end_test(); - - public: - tester(const char* test_name) noexcept; - - void launch_test() noexcept; - void add_step(const char* step_name, std::function callable); - }; - -} - -#endif //CONCURRENCPP_TESTER_H diff --git a/tests/tests/all_tests.cpp b/tests/tests/all_tests.cpp deleted file mode 100644 index 17ad173a..00000000 --- a/tests/tests/all_tests.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "all_tests.h" - -void concurrencpp::tests::test_all() { - test_inline_executor(); - test_thread_pool_executor(); - test_thread_executor(); - test_worker_thread_executor(); - test_manual_executor(); - - test_result(); - test_result_resolve_all(); - test_result_await_all(); - test_result_promise(); - test_make_result(); - - test_coroutine_promises(); - test_coroutines(); - - test_when_all(); - test_when_any(); - - test_timer_queue(); - test_timer(); - - test_runtime(); -} diff --git a/tests/tests/all_tests.h b/tests/tests/all_tests.h deleted file mode 100644 index d1c60c46..00000000 --- a/tests/tests/all_tests.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CONCURRENCPP_ALL_TESTS_H -#define CONCURRENCPP_ALL_TESTS_H - -namespace concurrencpp::tests { - void test_inline_executor(); - void test_thread_pool_executor(); - void test_thread_executor(); - void test_worker_thread_executor(); - void test_manual_executor(); - - void test_result_promise(); - void test_result(); - void test_result_resolve_all(); - void test_result_await_all(); - void test_result_await(); - void test_make_result(); - void test_when_all(); - void test_when_any(); - - void test_coroutine_promises(); - void test_coroutines(); - - void test_timer_queue(); - void test_timer(); - - void test_runtime(); - - void test_all(); -} - -#endif //CONCURRENCPP_ALL_TESTS_H diff --git a/tests/tests/coroutine_tests/coroutine_promises_test.cpp b/tests/tests/coroutine_tests/coroutine_promises_test.cpp deleted file mode 100644 index 36382a17..00000000 --- a/tests/tests/coroutine_tests/coroutine_promises_test.cpp +++ /dev/null @@ -1,307 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" -#include "../../helpers/object_observer.h" - -namespace concurrencpp::tests { - void test_initialy_resumed_null_result_promise(); - void test_initialy_resumed_result_promise(); - void test_initialy_rescheduled_null_result_promise(); - void test_initialy_rescheduled_result_promise(); -} - -using worker_ptr = std::shared_ptr; - -namespace concurrencpp::tests { - void init_workers(std::span span) { - for (auto& worker : span) { - worker = std::make_shared(); - } - } - - void shutdown_workers(std::span span) { - for (auto& worker : span) { - worker->shutdown(); - } - } -} - -namespace concurrencpp::tests { - null_result initialy_resumed_null_result_coro( - concurrencpp::details::wait_context& wc, - worker_ptr w0, - worker_ptr w1, - worker_ptr w2, - worker_ptr w3, - testing_stub stub) { - int i = 0; - std::string s = ""; - - co_await w0->submit([] {}); - - ++i; - s += "a"; - - co_await w1->submit([] {}); - - ++i; - s += "a"; - - co_await w2->submit([] {}); - - ++i; - s += "a"; - - co_await w3->submit([] {}); - - assert_equal(i, 3); - assert_equal(s, "aaa"); - - wc.notify(); - } -} - -void concurrencpp::tests::test_initialy_resumed_null_result_promise() { - worker_ptr workers[4]; - init_workers(workers); - - object_observer observer; - concurrencpp::details::wait_context wc; - initialy_resumed_null_result_coro( - wc, - workers[0], - workers[1], - workers[2], - workers[3], - observer.get_testing_stub()); - - wc.wait(); - assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); - - shutdown_workers(workers); -} - -namespace concurrencpp::tests { - result> initialy_resumed_result_coro( - worker_ptr w0, - worker_ptr w1, - worker_ptr w2, - worker_ptr w3, - testing_stub stub, - const bool terminate_by_exception) { - int i = 0; - std::string s = ""; - - co_await w0->submit([] {}); - - ++i; - s += "a"; - - co_await w1->submit([] {}); - - ++i; - s += "a"; - - co_await w2->submit([] {}); - - ++i; - s += "a"; - - co_await w3->submit([] {}); - - if (terminate_by_exception) { - throw costume_exception(1234); - } - - co_return std::make_pair(i, s); - } -} - -void concurrencpp::tests::test_initialy_resumed_result_promise() { - worker_ptr workers[4]; - init_workers(workers); - - object_observer observer; - auto [i, s] = initialy_resumed_result_coro( - workers[0], - workers[1], - workers[2], - workers[3], - observer.get_testing_stub(), - false).get(); - - assert_equal(i, 3); - assert_equal(s, "aaa"); - - assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); - - auto result = initialy_resumed_result_coro( - workers[0], - workers[1], - workers[2], - workers[3], - observer.get_testing_stub(), - true); - - result.wait(); - - test_ready_result_costume_exception(std::move(result), 1234); - assert_true(observer.wait_destruction_count(2, std::chrono::seconds(10))); - - shutdown_workers(workers); -} - -namespace concurrencpp::tests { - null_result initialy_rescheduled_null_result_coro( - executor_tag, - worker_ptr w0, - const std::thread::id caller_thread_id, - concurrencpp::details::wait_context& wc, - worker_ptr w1, - worker_ptr w2, - worker_ptr w3, - testing_stub stub) { - - assert_not_equal(caller_thread_id, std::this_thread::get_id()); - - int i = 0; - std::string s = ""; - - ++i; - s += "a"; - - co_await w1->submit([] {}); - - ++i; - s += "a"; - - co_await w2->submit([] {}); - - - ++i; - s += "a"; - - co_await w3->submit([] {}); - - assert_equal(i, 3); - assert_equal(s, "aaa"); - - wc.notify(); - } -} -void concurrencpp::tests::test_initialy_rescheduled_null_result_promise() { - worker_ptr workers[4]; - init_workers(workers); - - object_observer observer; - concurrencpp::details::wait_context wc; - initialy_rescheduled_null_result_coro( - {}, - workers[0], - std::this_thread::get_id(), - wc, - workers[1], - workers[2], - workers[3], - observer.get_testing_stub()); - - wc.wait(); - assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); - - shutdown_workers(workers); -} - -namespace concurrencpp::tests { - result> initialy_rescheduled_result_coro( - executor_tag, - worker_ptr w0, - const std::thread::id caller_id, - worker_ptr w1, - worker_ptr w2, - worker_ptr w3, - testing_stub stub, - const bool terminate_by_exception) { - - assert_not_equal(caller_id, std::this_thread::get_id()); - - int i = 0; - std::string s = ""; - - co_await w0->submit([] {}); - - ++i; - s += "a"; - - co_await w1->submit([] {}); - - ++i; - s += "a"; - - co_await w2->submit([] {}); - - ++i; - s += "a"; - - co_await w3->submit([] {}); - - if(terminate_by_exception) { - throw costume_exception(1234); - } - - co_return std::make_pair(i, s); - } -} - -void concurrencpp::tests::test_initialy_rescheduled_result_promise() { - worker_ptr workers[4]; - init_workers(workers); - - object_observer observer; - auto [i, s] = initialy_rescheduled_result_coro( - {}, - workers[0], - std::this_thread::get_id(), - workers[1], - workers[2], - workers[3], - observer.get_testing_stub(), - false).get(); - - assert_true(observer.wait_destruction_count(1, std::chrono::seconds(10))); - - assert_equal(i, 3); - assert_equal(s, "aaa"); - - auto result = initialy_rescheduled_result_coro( - {}, - workers[0], - std::this_thread::get_id(), - workers[1], - workers[2], - workers[3], - observer.get_testing_stub(), - true); - - result.wait(); - test_ready_result_costume_exception(std::move(result), 1234); - - assert_true(observer.wait_destruction_count(2, std::chrono::seconds(10))); - - shutdown_workers(workers); -} - -void concurrencpp::tests::test_coroutine_promises(){ - tester tester("coroutine promises test"); - - tester.add_step("initialy_resumed_null_result_promise", test_initialy_resumed_null_result_promise); - tester.add_step("initialy_resumed_result_promise", test_initialy_resumed_result_promise); - tester.add_step("initialy_rescheduled_null_result_promise", test_initialy_rescheduled_null_result_promise); - tester.add_step("initialy_rescheduled_result_promise", test_initialy_rescheduled_result_promise); - - tester.launch_test(); -} diff --git a/tests/tests/coroutine_tests/coroutines_tests.cpp b/tests/tests/coroutine_tests/coroutines_tests.cpp deleted file mode 100644 index db3a5a81..00000000 --- a/tests/tests/coroutine_tests/coroutines_tests.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "concurrencpp.h" - -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -#include - -#include "../test_utils/executor_shutdowner.h" - -namespace concurrencpp::tests { - template - result recursive_coroutine( - executor_tag, - std::shared_ptr te, - const size_t cur_depth, - const size_t max_depth, - const bool terminate_by_exception); - - template - void test_recursive_coroutines_impl(); - void test_recursive_coroutines(); - - result test_combo_coroutine_impl(std::shared_ptr te, const bool terminate_by_exception); - void test_combo_coroutine(); -} - -using concurrencpp::result; - -template -result concurrencpp::tests::recursive_coroutine( - executor_tag, - std::shared_ptr te, - const size_t cur_depth, - const size_t max_depth, - const bool terminate_by_exception) { - - if (cur_depth < max_depth) { - co_return co_await recursive_coroutine({}, te, cur_depth + 1, max_depth, terminate_by_exception); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - - if (terminate_by_exception) { - throw costume_exception(1234); - } - - co_return result_factory::get(); -} - -template -void concurrencpp::tests::test_recursive_coroutines_impl() { - //value - { - auto te = std::make_shared(); - executor_shutdowner es(te); - auto result = recursive_coroutine({}, te, 0, 20, false); - result.wait(); - test_ready_result_result(std::move(result)); - } - - //exception - { - auto te = std::make_shared(); - executor_shutdowner es(te); - auto result = recursive_coroutine({}, te, 0, 20, true); - result.wait(); - test_ready_result_costume_exception(std::move(result), 1234); - } -} - -void concurrencpp::tests::test_recursive_coroutines() { - test_recursive_coroutines_impl(); - test_recursive_coroutines_impl(); - test_recursive_coroutines_impl(); - test_recursive_coroutines_impl(); - test_recursive_coroutines_impl(); -} - -result concurrencpp::tests::test_combo_coroutine_impl(std::shared_ptr te, const bool terminate_by_exception) { - auto int_result = co_await te->submit([] { - return result_factory::get(); - }); - - auto string_result = co_await te->submit([int_result] { - return std::to_string(int_result); - }); - - assert_equal(string_result, std::to_string(result_factory::get())); - - auto& int_ref_result = co_await te->submit([]() -> int& { - return result_factory::get(); - }); - - auto& str_ref_result = co_await te->submit([]() -> std::string& { - return result_factory::get(); - }); - - assert_equal(&int_ref_result, &result_factory::get()); - assert_equal(&str_ref_result, &result_factory::get()); - - if(terminate_by_exception) { - throw costume_exception(1234); - } -} - -void concurrencpp::tests::test_combo_coroutine() { - //value - { auto te = std::make_shared(); - executor_shutdowner es(te); - auto res = test_combo_coroutine_impl(te, false); - res.wait(); - test_ready_result_result(std::move(res)); - } - - //exception - { - auto te = std::make_shared(); - executor_shutdowner es(te); - auto res = test_combo_coroutine_impl(te, true); - res.wait(); - test_ready_result_costume_exception(std::move(res), 1234); - } -} - -void concurrencpp::tests::test_coroutines() { - tester tester("coroutines test"); - - tester.add_step("recursive coroutines", test_recursive_coroutines); - tester.add_step("combo coroutine", test_combo_coroutine); - - tester.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/executor_tests/executor_test_helpers.h b/tests/tests/executor_tests/executor_test_helpers.h deleted file mode 100644 index 8c833a42..00000000 --- a/tests/tests/executor_tests/executor_test_helpers.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTOR_TEST_HELPERS_H -#define CONCURRENCPP_EXECUTOR_TEST_HELPERS_H - -#include "../../helpers/assertions.h" -#include "../../concurrencpp/src/executors/constants.h" -#include "../../concurrencpp/src/executors/executor.h" - -namespace concurrencpp::tests { - struct executor_shutdowner { - std::shared_ptr executor; - - executor_shutdowner(std::shared_ptr executor) noexcept : - executor(std::move(executor)) {} - - ~executor_shutdowner() noexcept { - executor->shutdown(); - } - }; -} - -#endif \ No newline at end of file diff --git a/tests/tests/executor_tests/inline_executor_tests.cpp b/tests/tests/executor_tests/inline_executor_tests.cpp deleted file mode 100644 index e0388847..00000000 --- a/tests/tests/executor_tests/inline_executor_tests.cpp +++ /dev/null @@ -1,311 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -#include "../../concurrencpp/src/executors/constants.h" - -namespace concurrencpp::tests { - void test_inline_executor_name(); - - void test_inline_executor_shutdown(); - - void test_inline_executor_max_concurrency_level(); - - void test_inline_executor_post_foreign(); - void test_inline_executor_post_inline(); - void test_inline_executor_post(); - - void test_inline_executor_submit_foreign(); - void test_inline_executor_submit_inline(); - void test_inline_executor_submit(); - - void test_inline_executor_bulk_post_foreign(); - void test_inline_executor_bulk_post_inline(); - void test_inline_executor_bulk_post(); - - void test_inline_executor_bulk_submit_foreign(); - void test_inline_executor_bulk_submit_inline(); - void test_inline_executor_bulk_submit(); -} - -using concurrencpp::details::thread; - -void concurrencpp::tests::test_inline_executor_name() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->name, concurrencpp::details::consts::k_inline_executor_name); -} - -void concurrencpp::tests::test_inline_executor_shutdown() { - auto executor = std::make_shared(); - assert_false(executor->shutdown_requested()); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - //it's ok to shut down an executor more than once - executor->shutdown(); - - assert_throws([executor] { - executor->enqueue(std::experimental::coroutine_handle{}); - }); - - assert_throws([executor] { - std::experimental::coroutine_handle<> array[4]; - std::span> span = array; - executor->enqueue(span); - }); -} - -void concurrencpp::tests::test_inline_executor_max_concurrency_level() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->max_concurrency_level(), - concurrencpp::details::consts::k_inline_executor_max_concurrency_level); -} - -void concurrencpp::tests::test_inline_executor_post_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first , thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_inline_executor_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - }); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_inline_executor_post() { - test_inline_executor_post_inline(); - test_inline_executor_post_foreign(); -} - -void concurrencpp::tests::test_inline_executor_submit_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), size_t(i)); - } -} - -void concurrencpp::tests::test_inline_executor_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector> results; - results.resize(task_count); - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - return results; - }); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), size_t(i)); - } -} - -void concurrencpp::tests::test_inline_executor_submit() { - test_inline_executor_submit_inline(); - test_inline_executor_submit_foreign(); -} - -void concurrencpp::tests::test_inline_executor_bulk_post_foreign(){ - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_inline_executor_bulk_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer] () mutable { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - }); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_inline_executor_bulk_post() { - test_inline_executor_bulk_post_foreign(); - test_inline_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_inline_executor_bulk_submit_foreign(){ - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_inline_executor_bulk_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - return executor->bulk_submit(stubs); - }); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_inline_executor_bulk_submit() { - test_inline_executor_bulk_submit_foreign(); - test_inline_executor_bulk_submit_inline(); -} - -void concurrencpp::tests::test_inline_executor() { - tester tester("inline_executor test"); - - tester.add_step("name", test_inline_executor_name); - tester.add_step("shutdown", test_inline_executor_shutdown); - tester.add_step("max_concurrency_level", test_inline_executor_max_concurrency_level); - tester.add_step("post", test_inline_executor_post); - tester.add_step("submit", test_inline_executor_submit); - tester.add_step("bulk_post", test_inline_executor_bulk_post); - tester.add_step("bulk_submit", test_inline_executor_bulk_submit); - - tester.launch_test(); -} diff --git a/tests/tests/executor_tests/manual_executor_tests.cpp b/tests/tests/executor_tests/manual_executor_tests.cpp deleted file mode 100644 index 4dd8b136..00000000 --- a/tests/tests/executor_tests/manual_executor_tests.cpp +++ /dev/null @@ -1,641 +0,0 @@ -#include "concurrencpp.h" - -#include "../all_tests.h" - -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -#include "../../concurrencpp/src/executors/constants.h" - -namespace concurrencpp::tests { - void test_manual_executor_name(); - - void test_manual_executor_shutdown_method_access(); - void test_manual_executor_shutdown_coro_raii(); - void test_manual_executor_shutdown_more_than_once(); - void test_manual_executor_shutdown(); - - void test_manual_executor_max_concurrency_level(); - - void test_manual_executor_post_foreign(); - void test_manual_executor_post_inline(); - void test_manual_executor_post(); - - void test_manual_executor_submit_foreign(); - void test_manual_executor_submit_inline(); - void test_manual_executor_submit(); - - void test_manual_executor_bulk_post_foreign(); - void test_manual_executor_bulk_post_inline(); - void test_manual_executor_bulk_post(); - - void test_manual_executor_bulk_submit_foreign(); - void test_manual_executor_bulk_submit_inline(); - void test_manual_executor_bulk_submit(); - - void test_manual_executor_loop_once(); - void test_manual_executor_loop_once_timed(); - - void test_manual_executor_loop(); - - void test_manual_executor_clear(); - - void test_manual_executor_wait_for_task(); - void test_manual_executor_wait_for_task_timed(); -} - -void concurrencpp::tests::test_manual_executor_name() { - auto executor = std::make_shared(); - assert_equal(executor->name, concurrencpp::details::consts::k_manual_executor_name); -} - -void concurrencpp::tests::test_manual_executor_shutdown_method_access() { - auto executor = std::make_shared(); - assert_false(executor->shutdown_requested()); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_throws([executor] { - executor->enqueue(std::experimental::coroutine_handle{}); - }); - - assert_throws([executor] { - std::experimental::coroutine_handle<> array[4]; - std::span> span = array; - executor->enqueue(span); - }); - - assert_throws([executor] { - executor->wait_for_task(); - }); - - assert_throws([executor] { - executor->wait_for_task(std::chrono::milliseconds(100)); - }); - - assert_throws([executor] { - executor->loop_once(); - }); - - assert_throws([executor] { - executor->loop(100); - }); -} - -void concurrencpp::tests::test_manual_executor_shutdown_coro_raii() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), task_count); - - for (auto& result : results) { - assert_throws([&result] { - result.get(); - }); - } -} - -void concurrencpp::tests::test_manual_executor_shutdown_more_than_once() { - const size_t task_count = 64; - auto executor = std::make_shared(); - - for (size_t i = 0 ; i < task_count ; i++) { - executor->post([] {}); - } - - for (size_t i = 0; i < 4; i++) { - executor->shutdown(); - } -} - -void concurrencpp::tests::test_manual_executor_shutdown() { - test_manual_executor_shutdown_method_access(); - test_manual_executor_shutdown_coro_raii(); - test_manual_executor_shutdown_more_than_once(); -} - -void concurrencpp::tests::test_manual_executor_max_concurrency_level() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->max_concurrency_level(), - concurrencpp::details::consts::k_manual_executor_max_concurrency_level); -} - -void concurrencpp::tests::test_manual_executor_post_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - assert_equal(executor->size(), 1 + i); - assert_false(executor->empty()); - } - - //manual executor doesn't execute the tasks automatically, hence manual. - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - executor->post([executor, &observer] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - assert_equal(executor->size(), 1 + i); - assert_false(executor->empty()); - } - }); - - assert_true(executor->loop_once()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - } - - executor->shutdown(); - - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_post() { - test_manual_executor_post_foreign(); - test_manual_executor_post_inline(); -} - -void concurrencpp::tests::test_manual_executor_submit_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0 ; i < task_count / 2 ;i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - assert_equal(results[i].get(), i); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - auto results_res = executor->submit([executor, &observer] { - std::vector> results; - results.resize(task_count); - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - return results; - }); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - assert_true(executor->loop_once()); - auto results = results_res.get(); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - assert_equal(results[i].get(), i); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_submit() { - test_manual_executor_submit_foreign(); - test_manual_executor_submit_inline(); -} - -void concurrencpp::tests::test_manual_executor_bulk_post_foreign() { - object_observer observer; - const size_t task_count = 1'000; - auto executor = std::make_shared(); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - - assert_false(executor->empty()); - assert_equal(executor->size(), task_count); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_bulk_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'000; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer]() mutable { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - executor->bulk_post(stubs); - }); - - assert_true(executor->loop_once()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_bulk_post() { - test_manual_executor_bulk_post_foreign(); - test_manual_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_manual_executor_bulk_submit_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - assert_false(executor->empty()); - assert_equal(executor->size(), task_count); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_bulk_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - return executor->bulk_submit(stubs); - }); - - assert_true(executor->loop_once()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count / 2; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - assert_equal(results[i].get(), i); - } - - executor->shutdown(); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_manual_executor_bulk_submit() { - test_manual_executor_bulk_submit_foreign(); - test_manual_executor_bulk_submit_inline(); -} - -void concurrencpp::tests::test_manual_executor_loop_once() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - for (size_t i = 0; i < 10; i++) { - assert_false(executor->loop_once()); - } - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - for (size_t i = 0; i < task_count; i++) { - assert_true(executor->loop_once()); - assert_equal(observer.get_execution_count(), i + 1); - assert_equal(executor->size(), task_count - (i + 1)); - assert_equal(results[i].get(), i); - } - - assert_equal(observer.get_destruction_count(), task_count); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, concurrencpp::details::thread::get_current_virtual_id()); - - for (size_t i = 0; i < 10; i++) { - assert_false(executor->loop_once()); - } -} - -void concurrencpp::tests::test_manual_executor_loop_once_timed() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - object_observer observer; - const auto waiting_time = 20; - - //case 1: timeout - { - for (size_t i = 0; i < 10; i++) { - const auto before = std::chrono::high_resolution_clock::now(); - assert_false(executor->loop_once(std::chrono::milliseconds(waiting_time))); - const auto after = std::chrono::high_resolution_clock::now(); - const auto ms = std::chrono::duration_cast(after - before).count(); - assert_bigger_equal(ms, waiting_time); - } - } - - //case 2: tasks already exist - { - executor->post(observer.get_testing_stub()); - const auto before = std::chrono::high_resolution_clock::now(); - assert_true(executor->loop_once(std::chrono::milliseconds(waiting_time))); - const auto after = std::chrono::high_resolution_clock::now(); - const auto ms = std::chrono::duration_cast(after - before).count(); - assert_smaller_equal(ms, 5); - assert_equal(observer.get_execution_count(), size_t(1)); - assert_equal(observer.get_destruction_count(), size_t(1)); - } - - //case 3: goes to sleep, then woken by an incoming task - { - const auto later = std::chrono::high_resolution_clock::now() + std::chrono::seconds(2); - - std::thread thread([executor, later, stub = observer.get_testing_stub()]() mutable { - std::this_thread::sleep_until(later); - executor->post(std::move(stub)); - }); - - assert_true(executor->loop_once(std::chrono::seconds(100))); - const auto now = std::chrono::high_resolution_clock::now(); - - assert_bigger_equal(now, later); - assert_smaller_equal(now, later + std::chrono::seconds(2)); - - thread.join(); - } -} - -void concurrencpp::tests::test_manual_executor_loop() { - object_observer observer; - const size_t task_count = 1'000; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->size(), size_t(0)); - assert_true(executor->empty()); - - assert_equal(executor->loop(100), size_t(0)); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(0)); - - const size_t chunk_size = 150; - const auto cycles = task_count / chunk_size; - const auto remained = task_count - (cycles * chunk_size); - - for (size_t i = 0; i < cycles; i++) { - const auto executed = executor->loop(chunk_size); - assert_equal(executed, chunk_size); - - const auto total_executed = (i + 1) * chunk_size; - assert_equal(observer.get_execution_count(), total_executed); - assert_equal(observer.get_destruction_count(), total_executed); - assert_equal(executor->size(), task_count - total_executed); - } - - //execute the remaining 100 tasks - const auto executed = executor->loop(chunk_size); - assert_equal(executed, remained); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); - - assert_true(executor->empty()); - assert_equal(executor->size(), size_t(0)); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_equal(execution_map.begin()->first, concurrencpp::details::thread::get_current_virtual_id()); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_manual_executor_clear() { - object_observer observer; - const size_t task_count = 100; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->clear(), size_t(0)); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_equal(executor->clear(), task_count); - assert_true(executor->empty()); - assert_equal(executor->size(), size_t(0)); - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), task_count); - - for (auto& result : results) { - assert_throws([&result]() mutable { - result.get(); - }); - } -} - -void concurrencpp::tests::test_manual_executor_wait_for_task() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - auto enqueuing_time = std::chrono::system_clock::now() + std::chrono::milliseconds(2'500); - - std::thread enqueuing_thread([executor, enqueuing_time]() mutable { - std::this_thread::sleep_until(enqueuing_time); - executor->post([] {}); - }); - - executor->wait_for_task(); - assert_bigger_equal(std::chrono::system_clock::now(), enqueuing_time); - - enqueuing_thread.join(); -} - -void concurrencpp::tests::test_manual_executor_wait_for_task_timed() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - const auto waiting_time = std::chrono::milliseconds(200); - - for (size_t i = 0; i < 10; i++) { - const auto before = std::chrono::system_clock::now(); - const auto task_found = executor->wait_for_task(waiting_time); - const auto after = std::chrono::system_clock::now(); - const auto time_elapsed = std::chrono::duration_cast(after - before); - - assert_false(task_found); - assert_bigger_equal(time_elapsed, waiting_time); - } - - auto enqueuing_time = std::chrono::system_clock::now() + std::chrono::milliseconds(2'500); - - std::thread enqueuing_thread([executor, enqueuing_time]() mutable { - std::this_thread::sleep_until(enqueuing_time); - executor->post([] {}); - }); - - const auto task_found = executor->wait_for_task(std::chrono::seconds(10)); - assert_true(task_found); - assert_bigger_equal(std::chrono::system_clock::now(), enqueuing_time); - - enqueuing_thread.join(); -} - -void concurrencpp::tests::test_manual_executor() { - tester tester("manual_executor test"); - - tester.add_step("name", test_manual_executor_name); - tester.add_step("shutdown", test_manual_executor_shutdown); - tester.add_step("max_concurrency_level", test_manual_executor_max_concurrency_level); - tester.add_step("post", test_manual_executor_post); - tester.add_step("submit", test_manual_executor_submit); - tester.add_step("bulk_post", test_manual_executor_bulk_post); - tester.add_step("bulk_submit", test_manual_executor_bulk_submit); - tester.add_step("loop_once", test_manual_executor_loop_once); - tester.add_step("loop_once (ms)", test_manual_executor_loop_once_timed); - tester.add_step("loop", test_manual_executor_loop); - tester.add_step("wait_for_task", test_manual_executor_wait_for_task); - tester.add_step("wait_for_task (ms)", test_manual_executor_wait_for_task_timed); - tester.add_step("clear", test_manual_executor_clear); - - tester.launch_test(); -} diff --git a/tests/tests/executor_tests/thread_executor_tests.cpp b/tests/tests/executor_tests/thread_executor_tests.cpp deleted file mode 100644 index dad17a67..00000000 --- a/tests/tests/executor_tests/thread_executor_tests.cpp +++ /dev/null @@ -1,327 +0,0 @@ -#include "concurrencpp.h" - -#include "../all_tests.h" - -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -#include "../../concurrencpp/src/executors/constants.h" - -namespace concurrencpp::tests { - void test_thread_executor_name(); - - void test_thread_executor_shutdown_join(); - void test_thread_executor_shutdown_method_access(); - void test_thread_executor_shutdown_more_than_once(); - void test_thread_executor_shutdown(); - - void test_thread_executor_max_concurrency_level(); - - void test_thread_executor_post_foreign(); - void test_thread_executor_post_inline(); - void test_thread_executor_post(); - - void test_thread_executor_submit_foreign(); - void test_thread_executor_submit_inline(); - void test_thread_executor_submit(); - - void test_thread_executor_bulk_post_foreign(); - void test_thread_executor_bulk_post_inline(); - void test_thread_executor_bulk_post(); - - void test_thread_executor_bulk_submit_foreign(); - void test_thread_executor_bulk_submit_inline(); - void test_thread_executor_bulk_submit(); - - void assert_execution_threads( - const std::unordered_map& execution_map, - const size_t expected_thread_count) { - assert_equal(execution_map.size(), expected_thread_count); - - for (const auto& thread_pair : execution_map) { - assert_not_equal(thread_pair.first, size_t(0)); - assert_equal(thread_pair.second, size_t(1)); - } - } -} - -void concurrencpp::tests::test_thread_executor_name() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->name, concurrencpp::details::consts::k_thread_executor_name); -} - -void concurrencpp::tests::test_thread_executor_shutdown_method_access() { - auto executor = std::make_shared(); - assert_false(executor->shutdown_requested()); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_throws([executor] { - executor->enqueue(std::experimental::coroutine_handle{}); - }); - - assert_throws([executor] { - std::experimental::coroutine_handle<> array[4]; - std::span> span = array; - executor->enqueue(span); - }); -} - -void concurrencpp::tests::test_thread_executor_shutdown_join() { - //the executor returns only when all tasks are done. - auto executor = std::make_shared(); - object_observer observer; - const size_t task_count = 16; - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub(std::chrono::milliseconds(100))); - } - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_equal(observer.get_execution_count(), task_count); - assert_equal(observer.get_destruction_count(), task_count); -} - -void concurrencpp::tests::test_thread_executor_shutdown_more_than_once() { - auto executor = std::make_shared(); - for (size_t i = 0; i < 4; i++) { - executor->shutdown(); - } -} - -void concurrencpp::tests::test_thread_executor_shutdown() { - test_thread_executor_shutdown_method_access(); - test_thread_executor_shutdown_join(); - test_thread_executor_shutdown_more_than_once(); -} - -void concurrencpp::tests::test_thread_executor_max_concurrency_level() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->max_concurrency_level(), - concurrencpp::details::consts::k_thread_executor_max_concurrency_level); -} - -void concurrencpp::tests::test_thread_executor_post_foreign() { - object_observer observer; - const size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); -} - -void concurrencpp::tests::test_thread_executor_post_inline() { - object_observer observer; - constexpr size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); -} - -void concurrencpp::tests::test_thread_executor_post() { - test_thread_executor_post_foreign(); - test_thread_executor_post_inline(); -} - -void concurrencpp::tests::test_thread_executor_submit_foreign() { - object_observer observer; - const size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_executor_submit_inline() { - object_observer observer; - constexpr size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector> results; - results.resize(task_count); - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - return results; - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), size_t(i)); - } -} - -void concurrencpp::tests::test_thread_executor_submit() { - test_thread_executor_submit_foreign(); - test_thread_executor_submit_inline(); -} - -void concurrencpp::tests::test_thread_executor_bulk_post_foreign() { - object_observer observer; - const size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); -} - -void concurrencpp::tests::test_thread_executor_bulk_post_inline() { - object_observer observer; - constexpr size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer]() mutable { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); -} - -void concurrencpp::tests::test_thread_executor_bulk_post() { - test_thread_executor_bulk_post_foreign(); - test_thread_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_thread_executor_bulk_submit_foreign() { - object_observer observer; - const size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_executor_bulk_submit_inline() { - object_observer observer; - constexpr size_t task_count = 128; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - return executor->bulk_submit(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - assert_execution_threads(observer.get_execution_map(), task_count); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_executor_bulk_submit() { - test_thread_executor_bulk_post_foreign(); - test_thread_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_thread_executor() { - tester tester("thread_executor test"); - - tester.add_step("shutdown", test_thread_executor_shutdown); - tester.add_step("name", test_thread_executor_name); - tester.add_step("max_concurrency_level", test_thread_executor_max_concurrency_level); - tester.add_step("post", test_thread_executor_post); - tester.add_step("submit", test_thread_executor_submit); - tester.add_step("bulk_post", test_thread_executor_bulk_post); - tester.add_step("bulk_submit", test_thread_executor_bulk_submit); - - tester.launch_test(); -} diff --git a/tests/tests/executor_tests/thread_pool_executor_tests.cpp b/tests/tests/executor_tests/thread_pool_executor_tests.cpp deleted file mode 100644 index 81c84749..00000000 --- a/tests/tests/executor_tests/thread_pool_executor_tests.cpp +++ /dev/null @@ -1,503 +0,0 @@ -#include "concurrencpp.h" - -#include "../all_tests.h" -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -namespace concurrencpp::tests { - void test_thread_pool_executor_name(); - - void test_thread_pool_executor_shutdown_coro_raii(); - void test_thread_pool_executor_shutdown_thread_join(); - void test_thread_pool_executor_shutdown_method_access(); - void test_thread_pool_executor_shutdown_method_more_than_once(); - void test_thread_pool_executor_shutdown(); - - void test_thread_pool_executor_post_foreign(); - void test_thread_pool_executor_post_inline(); - void test_thread_pool_executor_post(); - - void test_thread_pool_executor_submit_foreign(); - void test_thread_pool_executor_submit_inline(); - void test_thread_pool_executor_submit(); - - void test_thread_pool_executor_bulk_post_foreign(); - void test_thread_pool_executor_bulk_post_inline(); - void test_thread_pool_executor_bulk_post(); - - void test_thread_pool_executor_bulk_submit_foreign(); - void test_thread_pool_executor_bulk_submit_inline(); - void test_thread_pool_executor_bulk_submit(); - - void test_thread_pool_executor_enqueue_algorithm(); - void test_thread_pool_executor_dynamic_resizing(); -} - -void concurrencpp::tests::test_thread_pool_executor_name() { - const auto name = "abcde12345&*("; - auto executor = std::make_shared(name, 4, std::chrono::seconds(10)); - executor_shutdowner shutdowner(executor); - assert_equal(executor->name, name); -} - -void concurrencpp::tests::test_thread_pool_executor_shutdown_coro_raii() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared("threadpool", 1, std::chrono::seconds(4)); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - }); - - auto results = executor->bulk_submit(stubs); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), task_count); - - for (auto& result : results) { - assert_throws([&result] { - result.get(); - }); - } -} - -void concurrencpp::tests::test_thread_pool_executor_shutdown_thread_join() { - auto executor = std::make_shared("threadpool", 9, std::chrono::seconds(1)); - - for (size_t i = 0; i < 3; i++) { - executor->post([] {}); - } - - for (size_t i = 0; i < 3; i++) { - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - }); - } - - //allow threads time to start working - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - - // 1/3 of the threads are waiting, 1/3 are working, 1/3 are idle. all should be joined when tp is shut-down. - executor->shutdown(); - assert_true(executor->shutdown_requested()); -} - -void concurrencpp::tests::test_thread_pool_executor_shutdown_method_access() { - auto executor = std::make_shared("threadpool", 4, std::chrono::seconds(10)); - assert_false(executor->shutdown_requested()); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_throws([executor] { - executor->enqueue(std::experimental::coroutine_handle{}); - }); - - assert_throws([executor] { - std::experimental::coroutine_handle<> array[4]; - std::span> span = array; - executor->enqueue(span); - }); -} - -void concurrencpp::tests::test_thread_pool_executor_shutdown_method_more_than_once() { - const size_t task_count = 64; - auto executor = std::make_shared("threadpool", 4, std::chrono::seconds(10)); - - for (size_t i = 0; i < task_count; i++) { - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(18)); - }); - } - - for (size_t i = 0; i < 4; i++) { - executor->shutdown(); - } -} - -void concurrencpp::tests::test_thread_pool_executor_shutdown() { - test_thread_pool_executor_shutdown_coro_raii(); - test_thread_pool_executor_shutdown_thread_join(); - test_thread_pool_executor_shutdown_method_access(); - test_thread_pool_executor_shutdown_method_more_than_once(); -} - -void concurrencpp::tests::test_thread_pool_executor_post_foreign() { - object_observer observer; - const size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); -} - -void concurrencpp::tests::test_thread_pool_executor_post_inline() { - object_observer observer; - constexpr size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); -} - -void concurrencpp::tests::test_thread_pool_executor_post() { - test_thread_pool_executor_post_foreign(); - test_thread_pool_executor_post_inline(); -} - -void concurrencpp::tests::test_thread_pool_executor_submit_foreign() { - object_observer observer; - const size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_pool_executor_submit_inline(){ - object_observer observer; - constexpr size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector> results; - results.resize(task_count); - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - return results; - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), size_t(i)); - } -} - -void concurrencpp::tests::test_thread_pool_executor_submit() { - test_thread_pool_executor_submit_foreign(); - test_thread_pool_executor_submit_inline(); -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_post_foreign() { - object_observer observer; - const size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer]() mutable { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - executor->bulk_post(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_post() { - test_thread_pool_executor_bulk_post_foreign(); - test_thread_pool_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_submit_foreign() { - object_observer observer; - const size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_submit_inline() { - object_observer observer; - constexpr size_t task_count = 50'000; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - return executor->bulk_submit(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(2))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(2))); - - assert_equal(observer.get_execution_map().size(), worker_count); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_thread_pool_executor_bulk_submit() { - test_thread_pool_executor_bulk_submit_foreign(); - test_thread_pool_executor_bulk_submit_inline(); -} - -void concurrencpp::tests::test_thread_pool_executor_enqueue_algorithm() { - //case 1 : if an idle thread exists, enqueue it to the idle thread - { - object_observer observer; - const size_t worker_count = 6; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < worker_count; i++) { - executor->post(observer.get_testing_stub(std::chrono::milliseconds(100))); - } - - observer.wait_execution_count(worker_count, std::chrono::milliseconds(140)); - - assert_equal(observer.get_execution_map().size(), worker_count); - - //make sure each task was executed on one task - a task wasn't posted to a working thread - for (const auto& execution_thread : observer.get_execution_map()) { - assert_equal(execution_thread.second, size_t(1)); - } - } - - //case 2 : if (1) is false => if this is a thread-pool thread, enqueue to self - { - object_observer observer; - auto wc = std::make_shared(); - auto executor = std::make_shared("threadpool", 2, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - executor->post([wc] () { - wc->wait(); - }); - - constexpr size_t task_count = 1'024; - - executor->post([&observer, executor] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - }); - - observer.wait_execution_count(task_count, std::chrono::minutes(1)); - observer.wait_destruction_count(task_count, std::chrono::minutes(1)); - - assert_equal(observer.get_execution_map().size(), size_t(1)); - - wc->notify(); - } - - //case 3 : if (2) is false, choose a worker using round robin - { - const size_t task_count = 1'024; - const size_t worker_count = 2; - object_observer observer; - auto wc = std::make_shared(); - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(10)); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < worker_count; i++) { - executor->post([wc]() { - wc->wait(); - }); - } - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - - wc->notify(); - - observer.wait_execution_count(task_count, std::chrono::minutes(1)); - observer.wait_destruction_count(task_count, std::chrono::minutes(1)); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(2)); - - auto worker_it = execution_map.begin(); - assert_bigger(worker_it->second, task_count / 10); - - ++worker_it; - assert_bigger(worker_it->second, task_count / 10); - } -} - -void concurrencpp::tests::test_thread_pool_executor_dynamic_resizing() { - //if the workers are only waiting - notify them - { - const size_t worker_count = 4; - const size_t iterations = 4; - const size_t task_count = 1'024; - object_observer observer; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(5)); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < iterations; i++) { - for (size_t j = 0; j < task_count; j++) { - executor->post(observer.get_testing_stub()); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(350)); - - //in between, threads are waiting for an event (abort/task) - } - - observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); - observer.wait_destruction_count(task_count * iterations, std::chrono::minutes(1)); - - //if all the tasks were launched by <> workers, then no new workers were injected - assert_equal(observer.get_execution_map().size(), worker_count); - } - - //case 2 : if max_idle_time reached, idle threads exit. new threads are injeced when new tasks arrive - { - const size_t iterations = 4; - const size_t worker_count = 4; - const size_t task_count = 4'000; - object_observer observer; - auto executor = std::make_shared("threadpool", worker_count, std::chrono::seconds(1)); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < iterations; i++) { - for (size_t j = 0; j < task_count; j++) { - executor->post(observer.get_testing_stub()); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(1250)); - - //in between, threads are idling - } - - observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); - observer.wait_execution_count(task_count * iterations, std::chrono::minutes(1)); - - /* - If all the tasks were executed by <> * iterations workers, then - in every iteration a new set of threads was injected, meaning that the previous set of threads - had exited. - */ - assert_equal(observer.get_execution_map().size(), worker_count * iterations); - } -} - -void concurrencpp::tests::test_thread_pool_executor() { - tester tester("thread_pool_executor test"); - - tester.add_step("name", test_thread_pool_executor_name); - tester.add_step("shutdown", test_thread_pool_executor_shutdown); - - tester.add_step("post", test_thread_pool_executor_post); - tester.add_step("submit", test_thread_pool_executor_submit); - tester.add_step("bulk_post", test_thread_pool_executor_bulk_post); - tester.add_step("bulk_submit", test_thread_pool_executor_bulk_submit); - tester.add_step("enqueuing algorithm", test_thread_pool_executor_enqueue_algorithm); - tester.add_step("dynamic resizing", test_thread_pool_executor_dynamic_resizing); - - tester.launch_test(); -} diff --git a/tests/tests/executor_tests/worker_thread_executor_tests.cpp b/tests/tests/executor_tests/worker_thread_executor_tests.cpp deleted file mode 100644 index e464728f..00000000 --- a/tests/tests/executor_tests/worker_thread_executor_tests.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include "concurrencpp.h" - -#include "../all_tests.h" - -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" - -#include "../../concurrencpp/src/executors/constants.h" - -namespace concurrencpp::tests { - void test_worker_thread_executor_name(); - - void test_worker_thread_executor_shutdown_method_access(); - void test_worker_thread_executor_shutdown_thread_join(); - void test_worker_thread_executor_shutdown_coro_raii(); - void test_worker_thread_executor_shutdown_coro_more_than_once(); - void test_worker_thread_executor_shutdown(); - - void test_worker_thread_executor_max_concurrency_level(); - - void test_worker_thread_executor_post_foreign(); - void test_worker_thread_executor_post_inline(); - void test_worker_thread_executor_post(); - - void test_worker_thread_executor_submit_foreign(); - void test_worker_thread_executor_submit_inline(); - void test_worker_thread_executor_submit(); - - void test_worker_thread_executor_bulk_post_foreign(); - void test_worker_thread_executor_bulk_post_inline(); - void test_worker_thread_executor_bulk_post(); - - void test_worker_thread_executor_bulk_submit_foreign(); - void test_worker_thread_executor_bulk_submit_inline(); - void test_worker_thread_executor_bulk_submit(); -} - -using concurrencpp::details::thread; - -void concurrencpp::tests::test_worker_thread_executor_name() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->name, concurrencpp::details::consts::k_worker_thread_executor_name); -} - -void concurrencpp::tests::test_worker_thread_executor_shutdown_method_access() { - auto executor = std::make_shared(); - assert_false(executor->shutdown_requested()); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_throws([executor] { - executor->enqueue(std::experimental::coroutine_handle{}); - }); - - assert_throws([executor] { - std::experimental::coroutine_handle<> array[4]; - std::span> span = array; - executor->enqueue(span); - }); -} - -void concurrencpp::tests::test_worker_thread_executor_shutdown_thread_join() { - auto executor = std::make_shared(); - - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - }); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); -} - -void concurrencpp::tests::test_worker_thread_executor_shutdown_coro_raii() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - }); - - auto results = executor->bulk_submit(stubs); - - executor->shutdown(); - assert_true(executor->shutdown_requested()); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), task_count); - - for (auto& result : results) { - assert_throws([&result] { - result.get(); - }); - } -} - -void concurrencpp::tests::test_worker_thread_executor_shutdown_coro_more_than_once() { - const size_t task_count = 64; - auto executor = std::make_shared(); - - for (size_t i = 0; i < task_count; i++) { - executor->post([] { - std::this_thread::sleep_for(std::chrono::milliseconds(18)); - }); - } - - for (size_t i = 0; i < 4; i++) { - executor->shutdown(); - } -} - -void concurrencpp::tests::test_worker_thread_executor_shutdown() { - test_worker_thread_executor_shutdown_method_access(); - test_worker_thread_executor_shutdown_coro_raii(); - test_worker_thread_executor_shutdown_thread_join(); - test_worker_thread_executor_shutdown_coro_more_than_once(); -} - -void concurrencpp::tests::test_worker_thread_executor_max_concurrency_level() { - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - assert_equal(executor->max_concurrency_level(), 1); -} - -void concurrencpp::tests::test_worker_thread_executor_post_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_worker_thread_executor_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer] { - for (size_t i = 0; i < task_count; i++) { - executor->post(observer.get_testing_stub()); - } - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_worker_thread_executor_post() { - test_worker_thread_executor_post_foreign(); - test_worker_thread_executor_post_inline(); -} - -void concurrencpp::tests::test_worker_thread_executor_submit_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector> results; - results.resize(task_count); - - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_worker_thread_executor_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector> results; - results.resize(task_count); - for (size_t i = 0; i < task_count; i++) { - results[i] = executor->submit(observer.get_testing_stub(i)); - } - - return results; - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), size_t(i)); - } -} - -void concurrencpp::tests::test_worker_thread_executor_submit() { - test_worker_thread_executor_submit_foreign(); - test_worker_thread_executor_submit_inline(); -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_post_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_post_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - executor->post([executor, &observer]() mutable { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub()); - } - - executor->bulk_post(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_post() { - test_worker_thread_executor_bulk_post_foreign(); - test_worker_thread_executor_bulk_post_inline(); -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_submit_foreign() { - object_observer observer; - const size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - auto results = executor->bulk_submit(stubs); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_submit_inline() { - object_observer observer; - constexpr size_t task_count = 1'024; - auto executor = std::make_shared(); - executor_shutdowner shutdown(executor); - - auto results_res = executor->submit([executor, &observer] { - std::vector stubs; - stubs.reserve(task_count); - - for (size_t i = 0; i < task_count; i++) { - stubs.emplace_back(observer.get_testing_stub(i)); - } - - return executor->bulk_submit(stubs); - }); - - assert_true(observer.wait_execution_count(task_count, std::chrono::minutes(1))); - assert_true(observer.wait_destruction_count(task_count, std::chrono::minutes(1))); - - const auto& execution_map = observer.get_execution_map(); - - assert_equal(execution_map.size(), size_t(1)); - assert_not_equal(execution_map.begin()->first, thread::get_current_virtual_id()); - - auto results = results_res.get(); - for (size_t i = 0; i < task_count; i++) { - assert_equal(results[i].get(), i); - } -} - -void concurrencpp::tests::test_worker_thread_executor_bulk_submit() { - test_worker_thread_executor_bulk_submit_foreign(); - test_worker_thread_executor_bulk_submit_inline(); -} - -void concurrencpp::tests::test_worker_thread_executor() { - tester tester("worker_thread_executor test"); - - tester.add_step("name", test_worker_thread_executor_name); - tester.add_step("shutdown", test_worker_thread_executor_shutdown); - tester.add_step("max_concurrency_level", test_worker_thread_executor_max_concurrency_level); - tester.add_step("post", test_worker_thread_executor_post); - tester.add_step("submit", test_worker_thread_executor_submit); - tester.add_step("bulk_post", test_worker_thread_executor_bulk_post); - tester.add_step("bulk_submit", test_worker_thread_executor_bulk_submit); - - tester.launch_test(); -} diff --git a/tests/tests/result_tests/make_result_tests.cpp b/tests/tests/result_tests/make_result_tests.cpp deleted file mode 100644 index 30509aaa..00000000 --- a/tests/tests/result_tests/make_result_tests.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" -#include "../../helpers/object_observer.h" - -namespace concurrencpp::tests { - template - void test_make_ready_result_impl(); - void test_make_ready_result(); - - void test_make_exceptional_result(); -} - -template -void concurrencpp::tests::test_make_ready_result_impl() { - result result; - - if constexpr (std::is_same_v) { - result = concurrencpp::make_ready_result(); - } - else { - result = make_ready_result(result_factory::get()); - } - - test_ready_result_result(std::move(result)); -} - -void concurrencpp::tests::test_make_ready_result() { - test_make_ready_result_impl(); - test_make_ready_result_impl(); - test_make_ready_result_impl(); - test_make_ready_result_impl(); - test_make_ready_result_impl(); -} - -void concurrencpp::tests::test_make_exceptional_result() { - assert_throws_with_error_message([] { - make_exceptional_result({}); - }, concurrencpp::details::consts::k_make_exceptional_result_exception_null_error_msg); - - auto assert_ok = [](result& result) { - assert_equal(result.status(), result_status::exception); - try - { - result.get(); - } - catch (const std::runtime_error& re) { - assert_equal(std::string("error"), re.what()); - return; - } - catch (...) {} - assert_false(true); - }; - - result res = make_exceptional_result(std::runtime_error("error")); - assert_ok(res); - - res = make_exceptional_result(std::make_exception_ptr(std::runtime_error("error"))); - assert_ok(res); -} - -void concurrencpp::tests::test_make_result() { - tester tester("make_result test"); - - tester.add_step("make_ready_result", test_make_ready_result); - tester.add_step("make_exceptional_result", test_make_exceptional_result); - - tester.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/result_tests/result_await_tests.cpp b/tests/tests/result_tests/result_await_tests.cpp deleted file mode 100644 index 1d977114..00000000 --- a/tests/tests/result_tests/result_await_tests.cpp +++ /dev/null @@ -1,464 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/result_factory.h" -#include "../test_utils/test_ready_result.h" -#include "../test_utils/test_executors.h" -#include "../test_utils/executor_shutdowner.h" -#include "../test_utils/proxy_coro.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" - -namespace concurrencpp::tests { - template - result test_result_await_ready_val(); - - template - result test_result_await_ready_err(); - - template - result test_result_await_not_ready_val(std::shared_ptr executor); - - template - result test_result_await_not_ready_err(std::shared_ptr executor); - - template - void test_result_await_impl(); - void test_result_await(); - - template - result test_result_await_via_ready_val(std::shared_ptr executor); - - template - result test_result_await_via_ready_err(std::shared_ptr executor); - - template - result test_result_await_via_ready_val_force_rescheduling(std::shared_ptr executor); - - template - result test_result_await_via_ready_err_force_rescheduling(std::shared_ptr executor); - - template - result test_result_await_via_ready_val_force_rescheduling_executor_threw(); - - template - result test_result_await_via_ready_err_force_rescheduling_executor_threw(); - - template - result test_result_await_via_not_ready_val(std::shared_ptr executor); - template - result test_result_await_via_not_ready_err(std::shared_ptr executor); - - template - result test_result_await_via_not_ready_val_executor_threw(std::shared_ptr executor); - template - result test_result_await_via_not_ready_err_executor_threw(std::shared_ptr executor); - - template - void test_result_await_via_impl(); - void test_result_await_via(); -} - -using concurrencpp::result; -using concurrencpp::result_promise; -using namespace std::chrono; - -/* - At this point, we know result::resolve(_via) works perfectly so we wrap co_await operator in another - resolving-coroutine and we continue testing it as a resolve test. co_await will do its thing - and it'll forward the result/exception to a resolving coroutine. - since result::resolve(_via) works with no bugs, every failure is caused by result::co_await -*/ - -template -result concurrencpp::tests::test_result_await_ready_val() { - auto result = result_factory::make_ready(); - const auto thread_id_0 = std::this_thread::get_id(); - - auto result_proxy = [result = std::move(result)]() mutable->concurrencpp::result{ - co_return co_await result; - }; - - auto done_result = co_await result_proxy().resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_await_ready_err() { - random randomizer; - const auto id = randomizer(); - auto result = make_exceptional_result(costume_exception(id)); - - const auto thread_id_0 = std::this_thread::get_id(); - - auto result_proxy = [result = std::move(result)]() mutable->concurrencpp::result{ - co_return co_await result; - }; - - auto done_result = co_await result_proxy().resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_await_not_ready_val(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - executor->set_rp_value(std::move(rp)); - - auto result_proxy = [result = std::move(result)]() mutable -> concurrencpp::result{ - co_return co_await result; - }; - - auto done_result = co_await result_proxy().resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //the thread that set the value is the thread that resumes the coroutine. - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_await_not_ready_err(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - const auto id = executor->set_rp_err(std::move(rp)); - - auto result_proxy = [result = std::move(result)]() mutable->concurrencpp::result{ - co_return co_await result; - }; - - auto done_result = co_await result_proxy().resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //the thread that set the value is the thread that resumes the coroutine. - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -void concurrencpp::tests::test_result_await_impl() { - //empty result throws - { - assert_throws([] { - result result; - result.operator co_await(); - }); - } - - //ready result resumes immediately in the awaiting thread with no rescheduling - test_result_await_ready_val().get(); - - //ready result resumes immediately in the awaiting thread with no rescheduling - test_result_await_ready_err().get(); - - //if value or exception are not available - suspend and resume in the setting thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_not_ready_val(te).get(); - } - - //if value or exception are not available - suspend and resume in the setting thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_not_ready_err(te).get(); - } -} - -void concurrencpp::tests::test_result_await() { - test_result_await_impl(); - test_result_await_impl(); - test_result_await_impl(); - test_result_await_impl(); - test_result_await_impl(); -} - -template -result concurrencpp::tests::test_result_await_via_ready_val(std::shared_ptr executor) { - auto result = result_factory::make_ready(); - - const auto thread_id_0 = std::this_thread::get_id(); - - proxy_coro coro(std::move(result), executor, false); - auto done_result = co_await coro().resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - assert_false(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_await_via_ready_err(std::shared_ptr executor) { - random randomizer; - const auto id = randomizer(); - auto result = make_exceptional_result(costume_exception(id)); - - const auto thread_id_0 = std::this_thread::get_id(); - - proxy_coro coro(std::move(result), executor, false); - auto done_result = co_await coro().resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - assert_false(executor->scheduled_async()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_await_via_ready_val_force_rescheduling(std::shared_ptr executor) { - auto result = result_factory::make_ready(); - - proxy_coro coro(std::move(result), executor, true); - auto done_result = co_await coro().resolve(); - - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_await_via_ready_err_force_rescheduling(std::shared_ptr executor) { - random randomizer; - auto id = static_cast(randomizer()); - auto result = make_exceptional_result(costume_exception(id)); - - proxy_coro coro(std::move(result), executor, true); - auto done_result = co_await coro().resolve(); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_await_via_ready_val_force_rescheduling_executor_threw() { - auto result = result_factory::make_ready(); - auto te = std::make_shared(); - - auto thread_id_before = std::this_thread::get_id(); - - - try { - proxy_coro coro(std::move(result), te, true); - auto done_result = co_await coro().resolve(); - co_await done_result; - } - catch (const executor_enqueue_exception&) { - //do nothing - } - catch (...) { - assert_false(true); - } - - auto thread_id_after = std::this_thread::get_id(); - - assert_equal(thread_id_before, thread_id_after); - assert_false(static_cast(result)); -} - -template -result concurrencpp::tests::test_result_await_via_ready_err_force_rescheduling_executor_threw() { - auto result = result_factory::make_exceptional(); - auto te = std::make_shared(); - - auto thread_id_before = std::this_thread::get_id(); - - try { - proxy_coro coro(std::move(result), te, true); - auto done_result = co_await coro().resolve(); - assert_true(static_cast(done_result)); - - co_await done_result; - } - catch (const executor_enqueue_exception&) { - //do nothing - } - catch (...) { - assert_false(true); - } - - auto thread_id_after = std::this_thread::get_id(); - - assert_equal(thread_id_before, thread_id_after); - assert_false(static_cast(result)); -} - -template -result concurrencpp::tests::test_result_await_via_not_ready_val(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - executor->set_rp_value(std::move(rp)); - - proxy_coro coro(std::move(result), executor, true); - auto done_result = co_await coro().resolve(); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_await_via_not_ready_err(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - const auto id = executor->set_rp_err(std::move(rp)); - - proxy_coro coro(std::move(result), executor, true); - auto done_result = co_await coro().resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_async()); - assert_false(executor->scheduled_inline()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_await_via_not_ready_val_executor_threw(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - auto te = std::make_shared(); - - executor->set_rp_value(std::move(rp)); - - proxy_coro coro(std::move(result), te, true); - auto done_result = co_await coro().resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //since te threw, execution is resumed in ex::m_setting_thread - test_executor_error_thrown(std::move(done_result), te); -} - -template -result concurrencpp::tests::test_result_await_via_not_ready_err_executor_threw(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - auto te = std::make_shared(); - - const auto id = executor->set_rp_err(std::move(rp)); - (void)id; - - proxy_coro coro(std::move(result), te, true); - auto done_result = co_await coro().resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //since te threw, execution is resumed in ex::m_setting_thread - test_executor_error_thrown(std::move(done_result), te); -} - -template -void concurrencpp::tests::test_result_await_via_impl() { - //empty result throws - assert_throws([] { - result result; - auto executor = make_test_executor(); - result.await_via(executor); - }); - - assert_throws_with_error_message([] { - auto result = result_factory::make_ready(); - result.await_via({}, true); - }, - concurrencpp::details::consts::k_result_await_via_executor_null_error_msg); - - //if the result is ready by value, and force_rescheduling = false, resume in the calling thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_ready_val(te).get(); - } - - //if the result is ready by exception, and force_rescheduling = false, resume in the calling thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_ready_err(te).get(); - } - - //if the result is ready by value, and force_rescheduling = true, forcefully resume execution through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_ready_val_force_rescheduling(te).get(); - } - - //if the result is ready by exception, and force_rescheduling = true, forcefully resume execution through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_ready_err_force_rescheduling(te).get(); - } - - //if execution is rescheduled by a throwing executor, reschdule inline and throw executor_exception - test_result_await_via_ready_val_force_rescheduling_executor_threw().get(); - - //if execution is rescheduled by a throwing executor, reschdule inline and throw executor_exception - test_result_await_via_ready_err_force_rescheduling_executor_threw().get(); - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_not_ready_val(te).get(); - } - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_not_ready_err(te).get(); - } - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_not_ready_val_executor_threw(te).get(); - } - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_await_via_not_ready_err_executor_threw(te).get(); - } -} - -void concurrencpp::tests::test_result_await_via() { - test_result_await_via_impl(); - test_result_await_via_impl(); - test_result_await_via_impl(); - test_result_await_via_impl(); - test_result_await_via_impl(); -} - -void concurrencpp::tests::test_result_await_all() { - tester tester("result::await, result::await_via test"); - - tester.add_step("await", test_result_await); - tester.add_step("await_via", test_result_await_via); - - tester.launch_test(); -} diff --git a/tests/tests/result_tests/result_resolve_tests.cpp b/tests/tests/result_tests/result_resolve_tests.cpp deleted file mode 100644 index 6b9e1ee8..00000000 --- a/tests/tests/result_tests/result_resolve_tests.cpp +++ /dev/null @@ -1,432 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/result_factory.h" -#include "../test_utils/test_ready_result.h" -#include "../test_utils/test_executors.h" -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" - -namespace concurrencpp::tests { - template - result test_result_resolve_ready_val(); - - template - result test_result_resolve_ready_err(); - - template - result test_result_resolve_not_ready_val(std::shared_ptr executor); - - template - result test_result_resolve_not_ready_err(std::shared_ptr executor); - - template - void test_result_resolve_impl(); - void test_result_resolve(); - - template - result test_result_resolve_via_ready_val(std::shared_ptr executor); - - template - result test_result_resolve_via_ready_err(std::shared_ptr executor); - - template - result test_result_resolve_via_ready_val_force_rescheduling(std::shared_ptr executor); - - template - result test_result_resolve_via_ready_err_force_rescheduling(std::shared_ptr executor); - - template - result test_result_resolve_via_ready_val_force_rescheduling_executor_threw(); - - template - result test_result_resolve_via_ready_err_force_rescheduling_executor_threw(); - - template - result test_result_resolve_via_not_ready_val(std::shared_ptr executor); - template - result test_result_resolve_via_not_ready_err(std::shared_ptr executor); - - template - result test_result_resolve_via_not_ready_val_executor_threw(std::shared_ptr executor); - template - result test_result_resolve_via_not_ready_err_executor_threw(std::shared_ptr executor); - - template - void test_result_resolve_via_impl(); - void test_result_resolve_via(); - -} - -using concurrencpp::result; -using concurrencpp::result_promise; -using namespace std::chrono; - -template -result concurrencpp::tests::test_result_resolve_ready_val() { - auto result = result_factory::make_ready(); - - const auto thread_id_0 = std::this_thread::get_id(); - - auto done_result = co_await result.resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_resolve_ready_err() { - random randomizer; - const auto id = randomizer(); - auto result = make_exceptional_result(costume_exception(id)); - - const auto thread_id_0 = std::this_thread::get_id(); - - auto done_result = co_await result.resolve(); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_resolve_not_ready_val(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - executor->set_rp_value(std::move(rp)); - - auto done_result = co_await result.resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //the thread that set the value is the thread that resumes the coroutine. - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_resolve_not_ready_err(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - const auto id = executor->set_rp_err(std::move(rp)); - - auto done_result = co_await result.resolve(); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_inline()); //the thread that set the err is the thread that resumes the coroutine. - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -void concurrencpp::tests::test_result_resolve_impl() { - //empty result throws - { - assert_throws([] { - result result; - result.resolve(); - }); - } - - //ready result resumes immediately in the awaiting thread with no rescheduling - test_result_resolve_ready_val().get(); - - //ready result resumes immediately in the awaiting thread with no rescheduling - test_result_resolve_ready_err().get(); - - //if value or exception are not available - suspend and resume in the setting thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_not_ready_val(te).get(); - } - - //if value or exception are not available - suspend and resume in the setting thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_not_ready_err(te).get(); - } -} - -void concurrencpp::tests::test_result_resolve() { - test_result_resolve_impl(); - test_result_resolve_impl(); - test_result_resolve_impl(); - test_result_resolve_impl(); - test_result_resolve_impl(); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_val(std::shared_ptr executor) { - auto result = result_factory::make_ready(); - - const auto thread_id_0 = std::this_thread::get_id(); - - auto done_result = co_await result.resolve_via(executor, false); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - assert_false(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_err(std::shared_ptr executor) { - random randomizer; - const auto id = randomizer(); - auto result = make_exceptional_result(costume_exception(id)); - - const auto thread_id_0 = std::this_thread::get_id(); - - auto done_result = co_await result.resolve_via(executor, false); - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_false(static_cast(result)); - assert_equal(thread_id_0, thread_id_1); - assert_false(executor->scheduled_async()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_val_force_rescheduling(std::shared_ptr executor) { - auto result = result_factory::make_ready(); - - auto done_result = co_await result.resolve_via(executor, true); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_err_force_rescheduling(std::shared_ptr executor) { - random randomizer; - auto id = static_cast(randomizer()); - auto result = make_exceptional_result(costume_exception(id)); - - auto done_result = co_await result.resolve_via(executor, true); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_val_force_rescheduling_executor_threw() { - auto result = result_factory::make_ready(); - auto te = std::make_shared(); - - const auto thread_id_0 = std::this_thread::get_id(); - - try { - auto done_result = co_await result.resolve_via(te, true); - } - catch (const executor_enqueue_exception&) { - //do nothing - } - catch (...) { - assert_false(true); - } - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_equal(thread_id_0, thread_id_1); - assert_false(static_cast(result)); -} - -template -result concurrencpp::tests::test_result_resolve_via_ready_err_force_rescheduling_executor_threw() { - auto result = result_factory::make_exceptional(); - auto te = std::make_shared(); - - const auto thread_id_0 = std::this_thread::get_id(); - - try { - auto done_result = co_await result.resolve_via(te, true); - } - catch (const executor_enqueue_exception&) { - //do nothing - } - catch (...) { - assert_false(true); - } - - const auto thread_id_1 = std::this_thread::get_id(); - - assert_equal(thread_id_0, thread_id_1); - assert_false(static_cast(result)); -} - -template -result concurrencpp::tests::test_result_resolve_via_not_ready_val(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - executor->set_rp_value(std::move(rp)); - - auto done_result = co_await result.resolve_via(executor, false); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_inline()); - assert_true(executor->scheduled_async()); - test_ready_result_result(std::move(done_result)); -} - -template -result concurrencpp::tests::test_result_resolve_via_not_ready_err(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - const auto id = executor->set_rp_err(std::move(rp)); - - auto done_result = co_await result.resolve_via(executor, false); - - assert_false(static_cast(result)); - assert_true(executor->scheduled_async()); - assert_false(executor->scheduled_inline()); - test_ready_result_costume_exception(std::move(done_result), id); -} - -template -result concurrencpp::tests::test_result_resolve_via_not_ready_val_executor_threw(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - auto te = std::make_shared(); - - executor->set_rp_value(std::move(rp)); - - auto done_result = co_await result.resolve_via(te, false); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_async()); - assert_true(executor->scheduled_inline()); //since te threw, execution is resumed in ex::m_setting_thread - test_executor_error_thrown(std::move(done_result), te); -} - -template -result concurrencpp::tests::test_result_resolve_via_not_ready_err_executor_threw(std::shared_ptr executor) { - result_promise rp; - auto result = rp.get_result(); - - auto te = std::make_shared(); - - const auto id = executor->set_rp_err(std::move(rp)); - (void)id; - - auto done_result = co_await result.resolve_via(te, false); - - assert_false(static_cast(result)); - assert_false(executor->scheduled_async()); - assert_true(executor->scheduled_inline()); //since te threw, execution is resumed in ex::m_setting_thread - test_executor_error_thrown(std::move(done_result), te); -} - -template -void concurrencpp::tests::test_result_resolve_via_impl() { - //empty result throws - assert_throws([] { - result result; - auto executor = make_test_executor(); - result.resolve_via(executor); - }); - - - assert_throws_with_error_message([] { - auto result = result_factory::make_ready(); - result.resolve_via({}, true); - }, - concurrencpp::details::consts::k_result_resolve_via_executor_null_error_msg); - - //if the result is ready by value, and force_rescheduling = false, resume in the calling thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_ready_val(te).get(); - } - - //if the result is ready by exception, and force_rescheduling = false, resume in the calling thread - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_ready_err(te).get(); - } - - //if the result is ready by value, and force_rescheduling = true, forcefully resume execution through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_ready_val_force_rescheduling(te).get(); - } - - //if the result is ready by exception, and force_rescheduling = true, forcefully resume execution through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_ready_err_force_rescheduling(te).get(); - } - - //if execution is rescheduled by a throwing executor, reschedule inline and throw executor_exception - test_result_resolve_via_ready_val_force_rescheduling_executor_threw().get(); - - //if execution is rescheduled by a throwing executor, reschedule inline and throw executor_exception - test_result_resolve_via_ready_err_force_rescheduling_executor_threw().get(); - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_not_ready_val(te).get(); - } - - //if result is not ready - the execution resumes through the executor - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_not_ready_err(te).get(); - } - - //if result is not ready & executor threw - resume inline and throw concurrencpp::executor_exception - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_not_ready_val_executor_threw(te).get(); - } - - //if result is not ready & executor threw - resume inline and throw concurrencpp::executor_exception - { - auto te = make_test_executor(); - executor_shutdowner es(te); - test_result_resolve_via_not_ready_err_executor_threw(te).get(); - } -} - -void concurrencpp::tests::test_result_resolve_via() { - test_result_resolve_via_impl(); - test_result_resolve_via_impl(); - test_result_resolve_via_impl(); - test_result_resolve_via_impl(); - test_result_resolve_via_impl(); -} - -void concurrencpp::tests::test_result_resolve_all() { - tester tester("result::resolve, result::resolve_via test"); - - tester.add_step("reslove", test_result_resolve); - tester.add_step("reslove_via", test_result_resolve_via); - - tester.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/result_tests/result_tests.cpp b/tests/tests/result_tests/result_tests.cpp deleted file mode 100644 index 10b179a2..00000000 --- a/tests/tests/result_tests/result_tests.cpp +++ /dev/null @@ -1,679 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" -#include "../test_utils/result_factory.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" - -namespace concurrencpp::tests { - template - void test_result_constructor_impl(); - void test_result_constructor(); - - template - void test_result_status_impl(); - void test_result_status(); - - template - void test_result_get_impl(); - void test_result_get(); - - template - void test_result_wait_impl(); - void test_result_wait(); - - template - void test_result_wait_for_impl(); - void test_result_wait_for(); - - template - void test_result_wait_until_impl(); - void test_result_wait_until(); - - template - void test_result_assignment_operator_empty_to_empty(); - template - void test_result_assignment_operator_non_empty_to_non_empty(); - template - void test_result_assignment_operator_empty_to_non_empty(); - template - void test_result_assignment_operator_non_empty_to_empty(); - template - void test_result_assignment_operator_assign_to_self(); - template - void test_result_assignment_operator_impl(); - void test_result_assignment_operator(); -} - -using concurrencpp::result; -using concurrencpp::result_promise; -using namespace std::chrono; -using namespace concurrencpp::tests; - - -template -void concurrencpp::tests::test_result_constructor_impl() { - result default_constructed_result; - assert_false(static_cast(default_constructed_result)); - - result_promise rp; - auto rp_result = rp.get_result(); - assert_true(static_cast(rp_result)); - assert_equal(rp_result.status(), result_status::idle); - - auto new_result = std::move(rp_result); - assert_false(static_cast(rp_result)); - assert_true(static_cast(new_result)); - assert_equal(new_result.status(), result_status::idle); -} - -void concurrencpp::tests::test_result_constructor() { - test_result_constructor_impl(); - test_result_constructor_impl(); - test_result_constructor_impl(); - test_result_constructor_impl(); - test_result_constructor_impl(); -} - -template -void concurrencpp::tests::test_result_status_impl() { - //empty result throws - { - result result; - assert_throws([&result] { result.status(); }); - } - - //idle result - { - result_promise rp; - auto result = rp.get_result(); - assert_equal(result.status(), result_status::idle); - } - - //ready by value - { - result_promise rp; - auto result = rp.get_result(); - rp.set_from_function(result_factory::get); - assert_equal(result.status(), result_status::value); - } - - //exception result - { - result_promise rp; - auto result = rp.get_result(); - rp.set_from_function(result_factory::throw_ex); - assert_equal(result.status(), result_status::exception); - } - - //multiple calls of status are ok - { - result_promise rp; - auto result = rp.get_result(); - rp.set_from_function(result_factory::get); - - for (size_t i = 0; i < 10; i++) { - assert_equal(result.status(), result_status::value); - } - } -} - -void concurrencpp::tests::test_result_status() { - test_result_status_impl(); - test_result_status_impl(); - test_result_status_impl(); - test_result_status_impl(); - test_result_status_impl(); -} - -template -void concurrencpp::tests::test_result_get_impl() { - //empty result throws - { - result result; - assert_throws([&result] { result.get(); }); - } - - //get blocks until value is present and empties the result - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable { - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - result.get(); - const auto now = high_resolution_clock::now(); - - assert_false(static_cast(result)); - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - thread.join(); - } - - //get blocks until exception is present and empties the result - { - random randomizer; - result_promise rp; - auto result = rp.get_result(); - const auto id = randomizer(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), id, unblocking_time]() mutable { - std::this_thread::sleep_until(unblocking_time); - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - }); - - try { - result.get(); - } - catch (costume_exception e) { - assert_equal(e.id, id); - } - - const auto now = high_resolution_clock::now(); - - assert_false(static_cast(result)); - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - thread.join(); - } -} - -void concurrencpp::tests::test_result_get() { - test_result_get_impl(); - test_result_get_impl(); - test_result_get_impl(); - test_result_get_impl(); - test_result_get_impl(); -} - -template -void concurrencpp::tests::test_result_wait_impl() { - //empty result throws - { - result result; - assert_throws([&result] { result.wait(); }); - } - - //wait blocks until value is present - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable { - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - result.wait(); - const auto now = high_resolution_clock::now(); - - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - - test_ready_result_result(std::move(result)); - thread.join(); - } - - //wait blocks until exception is present - { - random randomizer; - result_promise rp; - auto result = rp.get_result(); - const auto id = randomizer(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), id, unblocking_time]() mutable { - std::this_thread::sleep_until(unblocking_time); - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - }); - - result.wait(); - const auto now = high_resolution_clock::now(); - - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - - test_ready_result_costume_exception(std::move(result), id); - thread.join(); - } - - //if result is ready with value, wait returns immediately - { - result_promise rp; - auto result = rp.get_result(); - rp.set_from_function(result_factory::get); - - const auto time_before = high_resolution_clock::now(); - result.wait(); - const auto time_after = high_resolution_clock::now(); - const auto total_blocking_time = duration_cast(time_after - time_before).count(); - - assert_smaller_equal(total_blocking_time, 3); - test_ready_result_result(std::move(result)); - } - - //if result is ready with exception, wait returns immediately - { - random randomizer; - result_promise rp; - auto result = rp.get_result(); - const auto id = randomizer(); - - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - - const auto time_before = high_resolution_clock::now(); - result.wait(); - const auto time_after = high_resolution_clock::now(); - const auto total_blocking_time = duration_cast(time_after - time_before).count(); - - assert_smaller_equal(total_blocking_time, 3); - test_ready_result_costume_exception(std::move(result), id); - } - - //multiple calls to wait are ok - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable { - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - for (size_t i = 0; i < 10; i++) { - result.wait(); - } - - test_ready_result_result(std::move(result)); - thread.join(); - } -} - -void concurrencpp::tests::test_result_wait() { - test_result_wait_impl(); - test_result_wait_impl(); - test_result_wait_impl(); - test_result_wait_impl(); - test_result_wait_impl(); -} - -template -void concurrencpp::tests::test_result_wait_for_impl() { - //empty result throws - { - concurrencpp::result result; - assert_throws([&result] { - result.wait_for(seconds(1)); - }); - } - - //if the result is ready by value, don't block and return status::value - { - result_promise rp; - auto result = rp.get_result(); - - rp.set_from_function(result_factory::get); - - const auto before = high_resolution_clock::now(); - const auto status = result.wait_for(seconds(3)); - const auto after = high_resolution_clock::now(); - const auto time = duration_cast(after - before).count(); - - assert_smaller_equal(time, 3); - assert_equal(status, result_status::value); - } - - //if the result is ready by exception, don't block and return status::exception - { - result_promise rp; - auto result = rp.get_result(); - - rp.set_from_function(result_factory::throw_ex); - - const auto before = high_resolution_clock::now(); - const auto status = result.wait_for(seconds(3)); - const auto after = high_resolution_clock::now(); - const auto time = duration_cast(after - before).count(); - - assert_smaller_equal(time, 3); - assert_equal(status, result_status::exception); - } - - //if timeout reaches and no value/exception - return status::idle - { - result_promise rp; - auto result = rp.get_result(); - - const auto before = high_resolution_clock::now(); - const auto status = result.wait_for(milliseconds(500)); - const auto after = high_resolution_clock::now(); - const auto time = duration_cast(after - before); - - assert_equal(status, result_status::idle); - assert_bigger_equal(time, milliseconds(500)); - } - - //if result is set before timeout, unblock, and return status::value - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - result.wait_for(seconds(100)); - const auto now = high_resolution_clock::now(); - - test_ready_result_result(std::move(result)); - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - thread.join(); - } - - //if exception is set before timeout, unblock, and return status::exception - { - random randomizer; - result_promise rp; - auto result = rp.get_result(); - const auto id = randomizer(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time, id]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - }); - - result.wait_for(seconds(100)); - const auto now = high_resolution_clock::now(); - - test_ready_result_costume_exception(std::move(result), id); - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(1)); - - thread.join(); - } - - //multiple calls of wait_for are ok - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - for (size_t i = 0; i < 10; i++) { - result.wait_for(seconds(10)); - } - - test_ready_result_result(std::move(result)); - thread.join(); - } -} - -void concurrencpp::tests::test_result_wait_for() { - test_result_wait_for_impl(); - test_result_wait_for_impl(); - test_result_wait_for_impl(); - test_result_wait_for_impl(); - test_result_wait_for_impl(); -} - -template -void concurrencpp::tests::test_result_wait_until_impl() { - //empty result throws - { - concurrencpp::result result; - assert_throws([&result] { - const auto later = high_resolution_clock::now() + seconds(10); - result.wait_until(later); - }); - } - - //if time_point <= now, the function is equivalent to result::status - { - result_promise rp_idle, rp_val, rp_err; - result idle_result = rp_idle.get_result(), - value_result = rp_val.get_result(), - err_result = rp_err.get_result(); - - rp_val.set_from_function(result_factory::get); - rp_err.set_from_function(result_factory::throw_ex); - - const auto now = high_resolution_clock::now(); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - assert_equal(idle_result.wait_until(now), concurrencpp::result_status::idle); - assert_equal(value_result.wait_until(now), concurrencpp::result_status::value); - assert_equal(err_result.wait_until(now), concurrencpp::result_status::exception); - } - - //if the result is ready by value, don't block and return status::value - { - result_promise rp; - auto result = rp.get_result(); - - rp.set_from_function(result_factory::get); - - const auto later = high_resolution_clock::now() + seconds(2); - const auto before = high_resolution_clock::now(); - const auto status = result.wait_until(later); - const auto after = high_resolution_clock::now(); - const auto time = duration_cast(after - before).count(); - - assert_smaller_equal(time, 3); - assert_equal(status, result_status::value); - } - - //if the result is ready by exception, don't block and return status::exception - { - result_promise rp; - auto result = rp.get_result(); - - rp.set_from_function(result_factory::throw_ex); - - const auto later = high_resolution_clock::now() + seconds(2); - const auto before = high_resolution_clock::now(); - const auto status = result.wait_until(later); - const auto after = high_resolution_clock::now(); - const auto time = duration_cast(after - before).count(); - - assert_smaller_equal(time, 3); - assert_equal(status, result_status::exception); - } - - //if timeout reaches and no value/exception - return status::idle - { - result_promise rp; - auto result = rp.get_result(); - - const auto later = high_resolution_clock::now() + seconds(1); - const auto status = result.wait_until(later); - const auto now = high_resolution_clock::now(); - - assert_equal(status, result_status::idle); - assert_bigger_equal(now, later); - } - - //if result is set before timeout, unblock, and return status::value - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - const auto later = high_resolution_clock::now() + seconds(10); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - result.wait_until(later); - const auto now = high_resolution_clock::now(); - - test_ready_result_result(std::move(result)); - assert_bigger_equal(now, unblocking_time); - assert_smaller(now, unblocking_time + seconds(2)); - thread.join(); - } - - //if exception is set before timeout, unblock, and return status::exception - { - random randomizer; - result_promise rp; - auto result = rp.get_result(); - const auto id = randomizer(); - - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - const auto later = high_resolution_clock::now() + seconds(10); - - std::thread thread([rp = std::move(rp), unblocking_time, id]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - }); - - result.wait_until(later); - const auto now = high_resolution_clock::now(); - - test_ready_result_costume_exception(std::move(result), id); - assert_bigger_equal(now, unblocking_time); - assert_smaller_equal(now, unblocking_time + seconds(2)); - thread.join(); - } - - //multiple calls to wait_until are ok - { - result_promise rp; - auto result = rp.get_result(); - const auto unblocking_time = high_resolution_clock::now() + seconds(1); - - std::thread thread([rp = std::move(rp), unblocking_time]() mutable{ - std::this_thread::sleep_until(unblocking_time); - rp.set_from_function(result_factory::get); - }); - - for (size_t i = 0; i < 10; i++) { - const auto later = high_resolution_clock::now() + seconds(1); - result.wait_until(later); - } - - thread.join(); - } -} - -void concurrencpp::tests::test_result_wait_until() { - test_result_wait_until_impl(); - test_result_wait_until_impl(); - test_result_wait_until_impl(); - test_result_wait_until_impl(); - test_result_wait_until_impl(); -} - - -template -void concurrencpp::tests::test_result_assignment_operator_empty_to_empty() { - result result_0, result_1; - result_0 = std::move(result_1); - assert_false(static_cast(result_0)); - assert_false(static_cast(result_1)); -} - -template -void concurrencpp::tests::test_result_assignment_operator_non_empty_to_non_empty() { - result_promise rp_0, rp_1; - result result_0 = rp_0.get_result(), result_1 = rp_1.get_result(); - result_0 = std::move(result_1); - assert_true(static_cast(result_0)); - assert_false(static_cast(result_1)); - - rp_0.set_from_function(result_factory::get); - assert_false(static_cast(result_1)); - assert_equal(result_0.status(), result_status::idle); - - rp_1.set_from_function(result_factory::get); - assert_false(static_cast(result_1)); - test_ready_result_result(std::move(result_0)); -} - -template -void concurrencpp::tests::test_result_assignment_operator_empty_to_non_empty() { - result_promise rp_0; - result result_0 = rp_0.get_result(), result_1; - result_0 = std::move(result_1); - assert_false(static_cast(result_0)); - assert_false(static_cast(result_1)); - -} - -template -void concurrencpp::tests::test_result_assignment_operator_non_empty_to_empty() { - result_promise rp_1; - result result_0, result_1 = rp_1.get_result(); - result_0 = std::move(result_1); - assert_true(static_cast(result_0)); - assert_false(static_cast(result_1)); - - rp_1.set_from_function(result_factory::get); - test_ready_result_result(std::move(result_0)); - -} - -template -void concurrencpp::tests::test_result_assignment_operator_assign_to_self() { - result res0; - - res0 = std::move(res0); - assert_false(static_cast(res0)); - - result_promise rp_1; - auto res1 = rp_1.get_result(); - - res1 = std::move(res1); - assert_true(static_cast(res1)); -} - - -template -void concurrencpp::tests::test_result_assignment_operator_impl() { - test_result_assignment_operator_empty_to_empty(); - test_result_assignment_operator_non_empty_to_empty(); - test_result_assignment_operator_empty_to_non_empty(); - test_result_assignment_operator_non_empty_to_non_empty(); - test_result_assignment_operator_assign_to_self(); -} - -void concurrencpp::tests::test_result_assignment_operator() { - test_result_assignment_operator_impl(); - test_result_assignment_operator_impl(); - test_result_assignment_operator_impl(); - test_result_assignment_operator_impl(); - test_result_assignment_operator_impl(); -} - -void concurrencpp::tests::test_result() { - tester tester("result test"); - - tester.add_step("constructor", test_result_constructor); - tester.add_step("status", test_result_status); - tester.add_step("get", test_result_get); - tester.add_step("wait", test_result_wait); - tester.add_step("wait_for", test_result_wait_for); - tester.add_step("wait_until", test_result_wait_until); - tester.add_step("operator =", test_result_assignment_operator); - - tester.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/result_tests/when_all_tests.cpp b/tests/tests/result_tests/when_all_tests.cpp deleted file mode 100644 index fde31e3d..00000000 --- a/tests/tests/result_tests/when_all_tests.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" -#include "../../helpers/object_observer.h" - -namespace concurrencpp::tests { - template - void test_when_all_vector_empty_result(); - - template - void test_when_all_vector_empty_range(); - - template - result test_when_all_vector_valid(std::shared_ptr ex); - - template - void test_when_all_vector_impl(); - void test_when_all_vector(); - - void test_when_all_tuple_empty_result(); - - void test_when_all_tuple_empty_range(); - - result test_when_all_tuple_valid(std::shared_ptr ex); - - void test_when_all_tuple(); -} - -template -void concurrencpp::tests::test_when_all_vector_empty_result() { - const size_t task_count = 63; - std::vector> result_promises(task_count); - std::vector> results; - - for(auto& rp : result_promises) { - results.emplace_back(rp.get_result()); - } - - results.emplace_back(); - - assert_throws_with_error_message( - [&] { - concurrencpp::when_all(results.begin(), results.end()); - }, - concurrencpp::details::consts::k_when_all_empty_result_error_msg); - - const auto all_valid = std::all_of( - results.begin(), - results.begin() + task_count, - [](const auto& result) { - return static_cast(result); - }); - - assert_true(all_valid); -} - -template -void concurrencpp::tests::test_when_all_vector_empty_range() { - std::vector> empty_range; - auto all = concurrencpp::when_all(empty_range.begin(), empty_range.end()); - assert_equal(all.status(), result_status::value); - assert_equal(all.get(), std::vector>{}); -} - -template -concurrencpp::result concurrencpp::tests::test_when_all_vector_valid(std::shared_ptr ex) { - const size_t task_count = 1'024; - auto values = result_factory::get_many(task_count); - std::atomic_size_t counter = 0; - - std::vector> results; - results.reserve(1'024); - - for (size_t i = 0; i < task_count; i++) { - results.emplace_back(ex->submit([&values, &counter, i]() -> type { - (void)values; - counter.fetch_add(1, std::memory_order_relaxed); - - if (i % 4 == 0) { - throw costume_exception(i); - } - - if constexpr (!std::is_same_v) { - return values[i]; - } - })); - } - - auto all = concurrencpp::when_all(results.begin(), results.end()); - - const auto all_empty = std::all_of( - results.begin(), - results.end(), - [](const auto& res) { - return !static_cast(res); - }); - - assert_true(all_empty); - - auto done_results = co_await all; - - assert_equal(counter.load(std::memory_order_relaxed), results.size()); - - const auto all_done = std::all_of( - done_results.begin(), - done_results.end(), - [](const auto& res) { - return static_cast(res) && (res.status() != result_status::idle); - }); - - assert_true(all_done); - - for (size_t i = 0; i < task_count; i++) { - if (i % 4 == 0) { - test_ready_result_costume_exception(std::move(done_results[i]), i); - } - else { - if constexpr (!std::is_same_v) { - test_ready_result_result(std::move(done_results[i]), values[i]); - } - else { - test_ready_result_result(std::move(done_results[i])); - } - } - } -} - -template -void concurrencpp::tests::test_when_all_vector_impl() { - test_when_all_vector_empty_result(); - test_when_all_vector_empty_range(); - - { - auto ex = std::make_shared(); - executor_shutdowner shutdown(ex); - test_when_all_vector_valid(ex).get(); - } -} - -void concurrencpp::tests::test_when_all_vector() { - test_when_all_vector_impl(); - test_when_all_vector_impl(); - test_when_all_vector_impl(); - test_when_all_vector_impl(); - test_when_all_vector_impl(); -} - -void concurrencpp::tests::test_when_all_tuple_empty_result() { - result_promise rp_int; - auto int_res = rp_int.get_result(); - - result_promise rp_s; - auto s_res = rp_s.get_result(); - - result_promise rp_void; - auto void_res = rp_void.get_result(); - - result_promise rp_int_ref; - auto int_ref_res = rp_int_ref.get_result(); - - result s_ref_res; - - assert_throws_with_error_message( - [&] { - when_all( - std::move(int_res), - std::move(s_res), - std::move(void_res), - std::move(int_ref_res), - std::move(s_ref_res)); - }, - concurrencpp::details::consts::k_when_all_empty_result_error_msg); - - assert_true(static_cast(int_res)); - assert_true(static_cast(s_res)); - assert_true(static_cast(void_res)); - assert_true(static_cast(int_res)); -} - -void concurrencpp::tests::test_when_all_tuple_empty_range() { - auto all = when_all(); - assert_equal(all.status(), result_status::value); - assert_equal(all.get(), std::tuple<>{}); -} - -concurrencpp::result concurrencpp::tests::test_when_all_tuple_valid(std::shared_ptr ex) { - std::atomic_size_t counter = 0; - - auto int_res_val = ex->submit([&]() ->int { - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - auto int_res_ex = ex->submit([&]() ->int { - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(0); - return result_factory::get(); - }); - - auto s_res_val = ex->submit([&]() -> std::string { - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - auto s_res_ex = ex->submit([&]() -> std::string { - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(1); - return result_factory::get(); - }); - - auto void_res_val = ex->submit([&]{ - counter.fetch_add(1, std::memory_order_relaxed); - }); - - auto void_res_ex = ex->submit([&]{ - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(2); - }); - - auto int_ref_res_val = ex->submit([&]() ->int& { - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - auto int_ref_res_ex = ex->submit([&]() ->int& { - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(3); - return result_factory::get(); - }); - - auto s_ref_res_val = ex->submit([&]() -> std::string& { - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - auto s_ref_res_ex = ex->submit([&]() -> std::string& { - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(4); - return result_factory::get(); - }); - - auto all = when_all( - std::move(int_res_val), - std::move(int_res_ex), - std::move(s_res_val), - std::move(s_res_ex), - std::move(void_res_val), - std::move(void_res_ex), - std::move(int_ref_res_val), - std::move(int_ref_res_ex), - std::move(s_ref_res_val), - std::move(s_ref_res_ex)); - - assert_false(static_cast(int_res_val)); - assert_false(static_cast(int_res_ex)); - assert_false(static_cast(s_res_val)); - assert_false(static_cast(s_res_ex)); - assert_false(static_cast(void_res_val)); - assert_false(static_cast(void_res_ex)); - assert_false(static_cast(int_ref_res_val)); - assert_false(static_cast(int_ref_res_ex)); - assert_false(static_cast(s_ref_res_val)); - assert_false(static_cast(s_ref_res_ex)); - - auto done_results_tuple = co_await all; - - assert_equal(counter.load(std::memory_order_relaxed), size_t(10)); - - test_ready_result_result(std::move(std::get<0>(done_results_tuple)), result_factory::get()); - test_ready_result_result(std::move(std::get<2>(done_results_tuple)), result_factory::get()); - test_ready_result_result(std::move(std::get<4>(done_results_tuple))); - test_ready_result_result(std::move(std::get<6>(done_results_tuple)), result_factory::get()); - test_ready_result_result(std::move(std::get<8>(done_results_tuple)), result_factory::get()); - - test_ready_result_costume_exception(std::move(std::get<1>(done_results_tuple)), 0); - test_ready_result_costume_exception(std::move(std::get<3>(done_results_tuple)), 1); - test_ready_result_costume_exception(std::move(std::get<5>(done_results_tuple)), 2); - test_ready_result_costume_exception(std::move(std::get<7>(done_results_tuple)), 3); - test_ready_result_costume_exception(std::move(std::get<9>(done_results_tuple)), 4); -} - -void concurrencpp::tests::test_when_all_tuple() { - test_when_all_tuple_empty_result(); - test_when_all_tuple_empty_range(); - - { - auto ex = std::make_shared(); - executor_shutdowner shutdown(ex); - test_when_all_tuple_valid(ex); - } -} - -void concurrencpp::tests::test_when_all() { - tester test("when_all test"); - - test.add_step("when_all(begin, end)", test_when_all_vector); - test.add_step("when_all(result_types&& ... results)", test_when_all_tuple); - - test.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/result_tests/when_any_tests.cpp b/tests/tests/result_tests/when_any_tests.cpp deleted file mode 100644 index 4151c549..00000000 --- a/tests/tests/result_tests/when_any_tests.cpp +++ /dev/null @@ -1,358 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../test_utils/test_ready_result.h" -#include "../test_utils/executor_shutdowner.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/random.h" -#include "../../helpers/object_observer.h" - -namespace concurrencpp::tests { - template - void test_when_any_vector_empty_result(); - - template - void test_when_any_vector_empty_range(); - - template - result test_when_any_vector_valid(std::shared_ptr ex); - - template - void test_when_any_vector_impl(); - - void test_when_any_vector(); - - void test_when_any_tuple_empty_result(); - - result test_when_any_tuple_impl(std::shared_ptr ex); - - void test_when_any_tuple(); -} - -template -void concurrencpp::tests::test_when_any_vector_empty_result() { - const size_t task_count = 63; - std::vector> result_promises(task_count); - std::vector> results; - - for (auto& rp : result_promises) { - results.emplace_back(rp.get_result()); - } - - results.emplace_back(); - - assert_throws_with_error_message( - [&] { - concurrencpp::when_any(results.begin(), results.end()); - }, - concurrencpp::details::consts::k_when_any_empty_result_error_msg); - - const auto all_valid = std::all_of( - results.begin(), - results.begin() + task_count, - [](const auto& result) { - return static_cast(result); - }); - - assert_true(all_valid); -} - -template -void concurrencpp::tests::test_when_any_vector_empty_range() { - std::vector> empty_range; - - assert_throws_with_error_message([&] { - when_any(empty_range.begin(), empty_range.end()); - }, concurrencpp::details::consts::k_when_any_empty_range_error_msg); -} - -template -concurrencpp::result -concurrencpp::tests::test_when_any_vector_valid(std::shared_ptr ex) { - const size_t task_count = 64; - auto values = result_factory::get_many(task_count); - std::vector> results; - random randomizer; - - for (size_t i = 0; i < task_count; i++) { - const auto time_to_sleep = randomizer(10, 100); - results.emplace_back(ex->submit([i, time_to_sleep, &values]() -> type { - (void)values; - std::this_thread::sleep_for(std::chrono::milliseconds(time_to_sleep)); - - if (i % 4 == 0) { - throw costume_exception(i); - } - - if constexpr (!std::is_same_v) { - return values[i]; - } - })); - } - - auto any_done = co_await when_any(results.begin(), results.end()); - - auto& done_result = any_done.results[any_done.index]; - - const auto all_valid = std::all_of( - any_done.results.begin(), - any_done.results.end(), - [](const auto& result) { - return static_cast(result); - }); - - assert_true(all_valid); - - if (any_done.index % 4 == 0) { - test_ready_result_costume_exception(std::move(done_result), any_done.index); - } - else { - if constexpr (std::is_same_v) { - test_ready_result_result(std::move(done_result)); - } - else { - test_ready_result_result(std::move(done_result), values[any_done.index]); - } - } - - //the value vector is a local variable, tasks may outlive it. join them. - for (auto& result : any_done.results) { - if (!static_cast(result)) { - continue; - } - - co_await result.resolve(); - } -} - -template -void concurrencpp::tests::test_when_any_vector_impl() { - test_when_any_vector_empty_result(); - test_when_any_vector_empty_range(); - - { - auto thread_executor = std::make_shared(); - executor_shutdowner es(thread_executor); - test_when_any_vector_valid(thread_executor).get(); - } -} - -void concurrencpp::tests::test_when_any_vector() { - test_when_any_vector_impl(); - test_when_any_vector_impl(); - test_when_any_vector_impl(); - test_when_any_vector_impl(); - test_when_any_vector_impl(); -} - -void concurrencpp::tests::test_when_any_tuple_empty_result() { - result_promise rp_int; - auto int_res = rp_int.get_result(); - - result_promise rp_s; - auto s_res = rp_s.get_result(); - - result_promise rp_void; - auto void_res = rp_void.get_result(); - - result_promise rp_int_ref; - auto int_ref_res = rp_int_ref.get_result(); - - result s_ref_res; - - assert_throws_with_error_message( - [&] { - when_any( - std::move(int_res), - std::move(s_res), - std::move(void_res), - std::move(int_ref_res), - std::move(s_ref_res)); - }, - concurrencpp::details::consts::k_when_any_empty_result_error_msg); - - //all pre-operation results are still valid - assert_true(static_cast(int_res)); - assert_true(static_cast(s_res)); - assert_true(static_cast(void_res)); - assert_true(static_cast(int_res)); -} - -concurrencpp::result -concurrencpp::tests::test_when_any_tuple_impl(std::shared_ptr ex) { - std::atomic_size_t counter = 0; - random randomizer; - - auto tts = randomizer(10,100); - auto int_res_val = ex->submit([&counter, tts] { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto int_res_ex = ex->submit([&counter, tts] { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(0); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto s_res_val = ex->submit([&counter, tts]() -> std::string { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto s_res_ex = ex->submit([&counter, tts]() -> std::string { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(1); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto void_res_val = ex->submit([&counter, tts] { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - }); - - tts = randomizer(10, 100); - auto void_res_ex = ex->submit([&counter, tts] { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(2); - }); - - tts = randomizer(10, 100); - auto int_ref_res_val = ex->submit([&counter, tts]() ->int& { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto int_ref_res_ex = ex->submit([&counter, tts]() ->int& { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(3); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto s_ref_res_val = ex->submit([&counter, tts]() -> std::string& { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - return result_factory::get(); - }); - - tts = randomizer(10, 100); - auto s_ref_res_ex = ex->submit([&counter, tts]() -> std::string& { - std::this_thread::sleep_for(std::chrono::milliseconds(tts)); - counter.fetch_add(1, std::memory_order_relaxed); - throw costume_exception(4); - return result_factory::get(); - }); - - auto any_done = co_await when_any( - std::move(int_res_val), - std::move(int_res_ex), - std::move(s_res_val), - std::move(s_res_ex), - std::move(void_res_val), - std::move(void_res_ex), - std::move(int_ref_res_val), - std::move(int_ref_res_ex), - std::move(s_ref_res_val), - std::move(s_ref_res_ex)); - - assert_bigger_equal(counter.load(std::memory_order_relaxed), size_t(1)); - - switch (any_done.index) { - case 0: - { - test_ready_result_result(std::move(std::get<0>(any_done.results)), result_factory::get()); - break; - } - case 1: - { - test_ready_result_costume_exception(std::move(std::get<1>(any_done.results)), 0); - break; - } - case 2: - { - test_ready_result_result(std::move(std::get<2>(any_done.results)), result_factory::get()); - break; - } - case 3: - { - test_ready_result_costume_exception(std::move(std::get<3>(any_done.results)), 1); - break; - } - case 4: - { - test_ready_result_result(std::move(std::get<4>(any_done.results))); - break; - } - case 5: - { - test_ready_result_costume_exception(std::move(std::get<5>(any_done.results)), 2); - break; - } - case 6: - { - test_ready_result_result(std::move(std::get<6>(any_done.results)), result_factory::get()); - break; - } - case 7: - { - test_ready_result_costume_exception(std::move(std::get<7>(any_done.results)), 3); - break; - } - case 8: - { - test_ready_result_result(std::move(std::get<8>(any_done.results)), result_factory::get()); - break; - } - case 9: - { - test_ready_result_costume_exception(std::move(std::get<9>(any_done.results)), 4); - break; - } - default: - { - assert_false(true); - } - } - - auto wait = [](auto& result) { - if (static_cast(result)) { - result.wait(); - } - }; - - std::apply([wait](auto&... results) {(wait(results), ...); }, any_done.results); -} - -void concurrencpp::tests::test_when_any_tuple() { - test_when_any_tuple_empty_result(); - - { - auto thread_executor = std::make_shared(); - executor_shutdowner es(thread_executor); - test_when_any_tuple_impl(thread_executor).get(); - } -} - -void concurrencpp::tests::test_when_any() { - tester test("when_any test"); - - test.add_step("when_any(begin, end)", test_when_any_vector); - test.add_step("when_any(result_types&& ... results)", test_when_any_tuple); - - test.launch_test(); -} diff --git a/tests/tests/runtime_tests.cpp b/tests/tests/runtime_tests.cpp deleted file mode 100644 index 1cb9ce87..00000000 --- a/tests/tests/runtime_tests.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "concurrencpp.h" -#include "all_tests.h" - -#include "../tester/tester.h" -#include "../helpers/assertions.h" - -namespace concurrencpp::tests { - void test_runtime_destructor(); - void test_runtime_version(); -} - -namespace concurrencpp::tests { - struct dummy_executor : public concurrencpp::executor { - - bool shutdown_requested_flag = false; - - dummy_executor(const char* name, int, float) : executor(name) {} - - void enqueue(std::experimental::coroutine_handle<>) override {} - void enqueue(std::span>) override {} - - int max_concurrency_level() const noexcept override { return 0; } - - bool shutdown_requested() const noexcept override { return shutdown_requested_flag; }; - void shutdown() noexcept override { shutdown_requested_flag = true; }; - }; -} - -void concurrencpp::tests::test_runtime_destructor() { - std::shared_ptr executors[7]; - - { - concurrencpp::runtime runtime; - executors[0] = runtime.inline_executor(); - executors[1] = runtime.thread_pool_executor(); - executors[2] = runtime.background_executor(); - executors[3] = runtime.thread_executor(); - executors[4] = runtime.make_worker_thread_executor(); - executors[5] = runtime.make_manual_executor(); - executors[6] = runtime.template make_executor("dummy_executor", 1, 4.4f); - - for (auto& executor : executors) { - assert_true(static_cast(executor)); - assert_false(executor->shutdown_requested()); - } - } - - for (auto& executor : executors) { - assert_true(executor->shutdown_requested()); - } -} - -void concurrencpp::tests::test_runtime_version() { - concurrencpp::runtime runtime; - - auto version = runtime.version(); - assert_equal(std::get<0>(version), concurrencpp::details::consts::k_concurrencpp_version_major); - assert_equal(std::get<1>(version), concurrencpp::details::consts::k_concurrencpp_version_minor); - assert_equal(std::get<2>(version), concurrencpp::details::consts::k_concurrencpp_version_revision); -} - -void concurrencpp::tests::test_runtime() { - tester tester("runtime test"); - - tester.add_step("~runtime", test_runtime_destructor); - tester.add_step("version", test_runtime_version); - - tester.launch_test(); -} \ No newline at end of file diff --git a/tests/tests/test_utils/executor_shutdowner.h b/tests/tests/test_utils/executor_shutdowner.h deleted file mode 100644 index 2b38ab2a..00000000 --- a/tests/tests/test_utils/executor_shutdowner.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef CONCURRENCPP_EXECUTOR_TEST_HELPERS_H -#define CONCURRENCPP_EXECUTOR_TEST_HELPERS_H - -#include "../../concurrencpp/src/executors/executor.h" - -namespace concurrencpp::tests { - struct executor_shutdowner { - std::shared_ptr executor; - - executor_shutdowner(std::shared_ptr executor) noexcept : - executor(std::move(executor)) {} - - ~executor_shutdowner() noexcept { - executor->shutdown(); - } - }; -} - -#endif \ No newline at end of file diff --git a/tests/tests/test_utils/proxy_coro.h b/tests/tests/test_utils/proxy_coro.h deleted file mode 100644 index c24dc046..00000000 --- a/tests/tests/test_utils/proxy_coro.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef CONCURRENCPP_PROXY_CORO_H -#define CONCURRENCPP_PROXY_CORO_H - -#include "concurrencpp.h" - -namespace concurrencpp::tests { - template - class proxy_coro { - - private: - result m_result; - std::shared_ptr m_test_executor; - bool m_force_rescheduling; - - public: - proxy_coro( - result result, - std::shared_ptr test_executor, - bool force_rescheduling) noexcept : - m_result(std::move(result)), - m_test_executor(std::move(test_executor)), - m_force_rescheduling(force_rescheduling) {} - - result operator() () { - co_return co_await m_result.await_via(std::move(m_test_executor), m_force_rescheduling); - } - }; -} - -#endif \ No newline at end of file diff --git a/tests/tests/test_utils/result_factory.h b/tests/tests/test_utils/result_factory.h deleted file mode 100644 index 9098f00c..00000000 --- a/tests/tests/test_utils/result_factory.h +++ /dev/null @@ -1,173 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_FACTORY_H -#define CONCURRENCPP_RESULT_FACTORY_H - -#include -#include -#include -#include - -namespace concurrencpp::tests { - template - struct result_factory { - static type get() { - return type(); - } - - static type throw_ex() { - throw std::underflow_error(""); - return get(); - } - - static result make_ready() { - return make_ready_result(get()); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; - - template<> - struct result_factory { - static int get() { - return 123456789; - } - - static std::vector get_many(size_t count) { - std::vector res(count); - std::iota(res.begin(), res.end(), 0); - return res; - } - - static int throw_ex() { - throw std::underflow_error(""); - return get(); - } - - static result make_ready() { - return make_ready_result(get()); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; - - template<> - struct result_factory { - static std::string get() { - return "abcdefghijklmnopqrstuvwxyz123456789!@#$%^&*()"; - } - - static std::vector get_many(size_t count) { - std::vector res; - res.reserve(count); - - for (size_t i = 0; i < count; i++) { - res.emplace_back(std::to_string(i)); - } - - return res; - } - - static std::string throw_ex() { - throw std::underflow_error(""); - return get(); - } - - static result make_ready() { - return make_ready_result(get()); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; - - template<> - struct result_factory { - static void get() {} - - static void throw_ex() { - throw std::underflow_error(""); - } - - static std::vector get_many(size_t count) { - return std::vector{ count }; - } - - static result make_ready() { - return make_ready_result(); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; - - template<> - struct result_factory { - static int& get() { - static int i = 0; - return i; - } - - static std::vector> get_many(size_t count) { - static std::vector s_res(64); - std::vector> res; - res.reserve(count); - for (size_t i = 0; i < count; i++) { - res.emplace_back(s_res[i % 64]); - } - - return res; - } - - static int& throw_ex() { - throw std::underflow_error(""); - return get(); - } - - static result make_ready() { - return make_ready_result(get()); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; - - template<> - struct result_factory { - static std::string& get() { - static std::string str; - return str; - } - - static std::vector> get_many(size_t count) { - static std::vector s_res(64); - std::vector> res; - res.reserve(count); - for (size_t i = 0; i < count; i++) { - res.emplace_back(s_res[i % 64]); - } - - return res; - } - - static std::string& throw_ex() { - throw std::underflow_error(""); - return get(); - } - - static result make_ready() { - return make_ready_result(get()); - } - - static result make_exceptional() { - return make_exceptional_result(std::underflow_error("")); - } - }; -} - -#endif \ No newline at end of file diff --git a/tests/tests/test_utils/test_executors.h b/tests/tests/test_utils/test_executors.h deleted file mode 100644 index a5702c7e..00000000 --- a/tests/tests/test_utils/test_executors.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef CONCURRENCPP_RESULT_TEST_EXECUTORS_H -#define CONCURRENCPP_RESULT_TEST_EXECUTORS_H - -#include "concurrencpp.h" -#include "result_factory.h" -#include "test_ready_result.h" - -#include -#include - -#include "../../helpers/random.h" - -namespace concurrencpp::tests { - class test_executor : public ::concurrencpp::executor { - - private: - mutable std::mutex m_lock; - std::thread m_execution_thread; - std::thread m_setting_thread; - bool m_abort; - - public: - test_executor() noexcept : executor("test_executor"), m_abort(false) {} - - ~test_executor() noexcept { - shutdown(); - } - - int max_concurrency_level() const noexcept override { - return 1; - } - - bool shutdown_requested() const noexcept override { - std::unique_lock lock(m_lock); - return m_abort; - } - - void shutdown() noexcept override { - std::unique_lock lock(m_lock); - if (m_abort) { - return; - } - - m_abort = true; - - if (m_execution_thread.joinable()) { - m_execution_thread.join(); - } - - if (m_setting_thread.joinable()) { - m_setting_thread.join(); - } - } - - bool scheduled_async() const noexcept { - std::unique_lock lock(m_lock); - return std::this_thread::get_id() == m_execution_thread.get_id(); - } - - bool scheduled_inline() const noexcept { - std::unique_lock lock(m_lock); - return std::this_thread::get_id() == m_setting_thread.get_id(); - } - - void enqueue(std::experimental::coroutine_handle<> task) override { - std::unique_lock lock(m_lock); - assert(!m_execution_thread.joinable()); - m_execution_thread = std::thread([task]() mutable{ - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - task(); - }); - } - - void enqueue(std::span> span) override { - (void)span; - std::abort(); //not neeeded. - } - - template - void set_rp_value(result_promise rp) { - std::unique_lock lock(m_lock); - assert(!m_setting_thread.joinable()); - m_setting_thread = std::thread([rp = std::move(rp)]() mutable { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - rp.set_from_function(result_factory::get); - }); - } - - template - size_t set_rp_err(result_promise rp) { - random randomizer; - const auto id = static_cast(randomizer()); - - std::unique_lock lock(m_lock); - assert(!m_setting_thread.joinable()); - m_setting_thread = std::thread([id, rp = std::move(rp)]() mutable { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - rp.set_exception(std::make_exception_ptr(costume_exception(id))); - }); - return id; - } - }; - - inline std::shared_ptr make_test_executor() { - return std::make_shared(); - } -} - -namespace concurrencpp::tests { - struct executor_enqueue_exception {}; - - struct throwing_executor : public concurrencpp::executor { - throwing_executor() : executor("throwing_executor") {} - - void enqueue(std::experimental::coroutine_handle<>) override { - throw executor_enqueue_exception(); - } - - void enqueue(std::span>) override { - throw executor_enqueue_exception(); - } - - int max_concurrency_level() const noexcept override { - return 0; - } - - bool shutdown_requested() const noexcept override { - return false; - } - - void shutdown() noexcept override { - //do nothing - } - }; - - template - void test_executor_error_thrown( - concurrencpp::result result, - std::shared_ptr throwing_executor) { - assert_equal(result.status(), concurrencpp::result_status::exception); - try { - result.get(); - } - catch (concurrencpp::errors::executor_exception ex) { - assert_equal(ex.throwing_executor.get(), throwing_executor.get()); - - try { - std::rethrow_exception(ex.thrown_exception); - } - catch (executor_enqueue_exception) { - //OK - } - catch (...) { - assert_false(true); - } - } - catch (...) { - assert_true(false); - } - } -} - -#endif //CONCURRENCPP_RESULT_HELPERS_H diff --git a/tests/tests/test_utils/test_ready_result.h b/tests/tests/test_utils/test_ready_result.h deleted file mode 100644 index 558ee573..00000000 --- a/tests/tests/test_utils/test_ready_result.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef CONCURRENCPP_TEST_READY_RESULT_H -#define CONCURRENCPP_TEST_READY_RESULT_H - -#include "concurrencpp.h" - -#include "result_factory.h" - -#include "../../helpers/assertions.h" - -#include - -namespace concurrencpp::tests { - struct costume_exception : public std::exception { - const intptr_t id; - - costume_exception(intptr_t id) noexcept : id(id) {} - }; -} - -namespace concurrencpp::tests { - template - void test_ready_result_result(::concurrencpp::result result, const type& o) { - assert_true(static_cast(result)); - assert_equal(result.status(), concurrencpp::result_status::value); - - try - { - assert_equal(result.get(), o); - } - catch (...) { - assert_true(false); - } - } - - template - void test_ready_result_result( - ::concurrencpp::result result, - std::reference_wrapper> ref) { - assert_true(static_cast(result)); - assert_equal(result.status(), concurrencpp::result_status::value); - - try - { - assert_equal(&result.get(), &ref.get()); - } - catch (...) { - assert_true(false); - } - } - - template - void test_ready_result_result(::concurrencpp::result result) { - test_ready_result_result(std::move(result), result_factory::get()); - } - - template<> - inline void test_ready_result_result(::concurrencpp::result result) { - assert_true(static_cast(result)); - assert_equal(result.status(), concurrencpp::result_status::value); - try - { - result.get(); //just make sure no exception is thrown. - } - catch (...) { - assert_true(false); - } - } - - template - void test_ready_result_costume_exception(concurrencpp::result result, const intptr_t id) { - assert_true(static_cast(result)); - assert_equal(result.status(), concurrencpp::result_status::exception); - - try { - result.get(); - } - catch (costume_exception e) { - return assert_equal(e.id, id); - } - catch (...) {} - - assert_true(false); - } -} - -#endif \ No newline at end of file diff --git a/tests/tests/timer_tests/timer_queue_tests.cpp b/tests/tests/timer_tests/timer_queue_tests.cpp deleted file mode 100644 index c79eb4f0..00000000 --- a/tests/tests/timer_tests/timer_queue_tests.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" -#include "../../helpers/random.h" - -#include - -using namespace std::chrono_literals; - -namespace concurrencpp::tests { - void test_timer_queue_make_timer(); - void test_timer_queue_make_oneshot_timer(); - void test_timer_queue_make_delay_object(); -} - -void concurrencpp::tests::test_timer_queue_make_timer() { - auto timer_queue = std::make_shared(); - assert_false(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - timer_queue->make_timer(100ms, 100ms, {}, [] {}); - }, - concurrencpp::details::consts::k_timer_queue_make_timer_executor_null_err_msg); - - timer_queue->shutdown(); - assert_true(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - auto inline_executor = std::make_shared(); - timer_queue->make_timer(100ms, 100ms, inline_executor, [] {}); - }, - concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); -} - -void concurrencpp::tests::test_timer_queue_make_oneshot_timer() { - auto timer_queue = std::make_shared(); - assert_false(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - timer_queue->make_one_shot_timer(100ms, {}, [] {}); - }, - concurrencpp::details::consts::k_timer_queue_make_oneshot_timer_executor_null_err_msg); - - timer_queue->shutdown(); - assert_true(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - auto inline_executor = std::make_shared(); - timer_queue->make_one_shot_timer(100ms, inline_executor, [] {}); - }, - concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); -} - -void concurrencpp::tests::test_timer_queue_make_delay_object() { - auto timer_queue = std::make_shared(); - assert_false(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - timer_queue->make_delay_object(100ms, {}); - }, - concurrencpp::details::consts::k_timer_queue_make_delay_object_executor_null_err_msg); - - timer_queue->shutdown(); - assert_true(timer_queue->shutdown_requested()); - - assert_throws_with_error_message([timer_queue] { - auto inline_executor = std::make_shared(); - timer_queue->make_delay_object(100ms, inline_executor); - }, - concurrencpp::details::consts::k_timer_queue_shutdown_err_msg); -} - -void concurrencpp::tests::test_timer_queue() { - tester test("timer_queue test"); - - test.add_step("make_timer", test_timer_queue_make_timer); - test.add_step("make_oneshot_timer", test_timer_queue_make_timer); - test.add_step("make_delay_object", test_timer_queue_make_delay_object); - - test.launch_test(); -} diff --git a/tests/tests/timer_tests/timer_tests.cpp b/tests/tests/timer_tests/timer_tests.cpp deleted file mode 100644 index 5b2a14e2..00000000 --- a/tests/tests/timer_tests/timer_tests.cpp +++ /dev/null @@ -1,728 +0,0 @@ -#include "concurrencpp.h" -#include "../all_tests.h" - -#include "../../tester/tester.h" -#include "../../helpers/assertions.h" -#include "../../helpers/object_observer.h" -#include "../../helpers/random.h" - -#include - -using namespace std::chrono_literals; - -namespace concurrencpp::tests { - void test_one_timer(); - void test_many_timers(); - void test_timer_constructor(); - - void test_timer_destructor_empty_timer(); - void test_timer_destructor_dead_timer_queue(); - void test_timer_destructor_functionality(); - void test_timer_destructor_RAII(); - void test_timer_destructor(); - - void test_timer_cancel_empty_timer(); - void test_timer_cancel_dead_timer_queue(); - void test_timer_cancel_before_due_time(); - void test_timer_cancel_after_due_time_before_beat(); - void test_timer_cancel_after_due_time_after_beat(); - void test_timer_cancel_RAII(); - void test_timer_cancel(); - - void test_timer_operator_bool(); - - void test_timer_set_frequency_before_due_time(); - void test_timer_set_frequency_after_due_time(); - void test_timer_set_frequency(); - - void test_timer_oneshot_timer(); - void test_timer_delay_object(); - - void test_timer_assignment_operator_empty_to_empty(); - void test_timer_assignment_operator_non_empty_to_non_empty(); - void test_timer_assignment_operator_empty_to_non_empty(); - void test_timer_assignment_operator_non_empty_to_empty(); - void test_timer_assignment_operator_assign_to_self(); - void test_timer_assignment_operator(); -} - -using concurrencpp::timer; - -using namespace std::chrono; -using time_point = std::chrono::time_point; - -namespace concurrencpp::tests { - auto empty_callback = []() noexcept {}; - - class counter_executor : public concurrencpp::executor { - - private: - std::atomic_size_t m_invocation_count; - - public: - counter_executor() : executor("timer_counter_executor"), m_invocation_count(0) {} - - void enqueue(std::experimental::coroutine_handle<> task) override { - ++m_invocation_count; - task(); - } - - void enqueue(std::span>) override { - //do nothing - } - - int max_concurrency_level() const noexcept override { - return 0; - } - - bool shutdown_requested() const noexcept override { - return false; - } - - void shutdown() noexcept override { - //do nothing - } - - size_t invocation_count() const noexcept { - return m_invocation_count.load(); - } - }; - - class recording_executor : public concurrencpp::executor { - - public: - mutable std::mutex m_lock; - std::vector<::time_point> m_time_points; - ::time_point m_start_time; - - public: - recording_executor() : executor("timer_recording_executor") { - m_time_points.reserve(64); - }; - - virtual void enqueue(std::experimental::coroutine_handle<> task) override { - { - std::unique_lock lock(m_lock); - m_time_points.emplace_back(std::chrono::high_resolution_clock::now()); - } - - task(); - } - - virtual void enqueue(std::span>) override { - //do nothing - } - - virtual int max_concurrency_level() const noexcept override { - return 0; - } - - virtual bool shutdown_requested() const noexcept override { - return false; - } - - virtual void shutdown() noexcept override { - //do nothing - } - - void start_test() { - std::unique_lock lock(m_lock); - m_start_time = std::chrono::high_resolution_clock::now(); - - } - - ::time_point get_start_time() { - std::unique_lock lock(m_lock); - return m_start_time; - } - - ::time_point get_first_fire_time() { - std::unique_lock lock(m_lock); - assert(!m_time_points.empty()); - return m_time_points[0]; - } - - std::vector<::time_point> flush_time_points() { - std::unique_lock lock(m_lock); - return std::move(m_time_points); - } - - void reset() { - std::unique_lock lock(m_lock); - m_time_points.clear(); - m_start_time = std::chrono::high_resolution_clock::now(); - } - }; - - class timer_tester { - - private: - const std::chrono::milliseconds m_due_time; - std::chrono::milliseconds m_frequency; - const std::shared_ptr m_executor; - const std::shared_ptr m_timer_queue; - concurrencpp::timer m_timer; - - void assert_timer_stats() noexcept { - assert_equal(m_timer.get_due_time(), m_due_time); - assert_equal(m_timer.get_frequency(), m_frequency); - assert_equal(m_timer.get_executor().get(), m_executor.get()); - assert_equal(m_timer.get_timer_queue().lock(), m_timer_queue); - } - - size_t calculate_due_time() noexcept { - const auto start_time = m_executor->get_start_time(); - const auto first_fire = m_executor->get_first_fire_time(); - const auto due_time = duration_cast(first_fire - start_time).count(); - return static_cast(due_time); - } - - std::vector calculate_frequencies() { - auto fire_times = m_executor->flush_time_points(); - - std::vector intervals; - intervals.reserve(fire_times.size()); - - for (size_t i = 0; i < fire_times.size() - 1; i++) { - const auto period = fire_times[i + 1] - fire_times[i]; - const auto interval = duration_cast(period).count(); - intervals.emplace_back(static_cast(interval)); - } - - return intervals; - } - - void assert_correct_time_points() { - const auto tested_due_time = calculate_due_time(); - interval_ok(tested_due_time, m_due_time.count()); - - const auto intervals = calculate_frequencies(); - for (auto tested_frequency : intervals) { - interval_ok(tested_frequency, m_frequency.count()); - } - } - - public: - timer_tester( - const std::chrono::milliseconds due_time, - const std::chrono::milliseconds frequency, - std::shared_ptr timer_queue) : - m_due_time(due_time), - m_frequency(frequency), - m_executor(std::make_shared()), - m_timer_queue(std::move(timer_queue)) {} - - void start_timer_test() { - m_timer = m_timer_queue->make_timer( - m_due_time, - m_frequency, - m_executor, - [] {}); - - m_executor->start_test(); - } - - void start_once_timer_test() { - m_timer = m_timer_queue->make_one_shot_timer( - m_due_time, - m_executor, - [] {}); - - m_executor->start_test(); - } - - void reset(std::chrono::milliseconds new_frequency) noexcept { - m_timer.set_frequency(new_frequency); - m_executor->reset(); - m_frequency = new_frequency; - } - - void test_frequencies(size_t frequency) { - const auto frequencies = calculate_frequencies(); - for (auto tested_frequency : frequencies) { - interval_ok(tested_frequency, frequency); - } - } - - void test() { - assert_timer_stats(); - assert_correct_time_points(); - - m_timer.cancel(); - } - - void test_oneshot_timer() { - assert_timer_stats(); - interval_ok(calculate_due_time(), m_due_time.count()); - - auto frequencies = calculate_frequencies(); - assert_true(frequencies.empty()); - - m_timer.cancel(); - } - - static void interval_ok(size_t interval, size_t expected) noexcept { - assert_bigger_equal(interval, expected - 30); - assert_smaller_equal(interval, expected + 100); - } - }; -} - -void concurrencpp::tests::test_one_timer() { - const auto due_time = 1270ms; - const auto frequency = 3230ms; - auto tq = std::make_shared(); - - timer_tester tester(due_time, frequency, tq); - tester.start_timer_test(); - - std::this_thread::sleep_for(30s); - - tester.test(); -} - -void concurrencpp::tests::test_many_timers() { - random randomizer; - auto timer_queue = std::make_shared(); - std::list testers; - - const size_t due_time_min = 100; - const size_t due_time_max = 2'000; - const size_t frequency_min = 100; - const size_t frequency_max = 3'000; - const size_t timer_count = 1'024 * 4; - - auto round_down = [](size_t num) { - return num - num % 50; - }; - - for (size_t i = 0; i < timer_count; i++) { - const auto due_time = round_down(randomizer(due_time_min, due_time_max)); - const auto frequency = round_down(randomizer(frequency_min, frequency_max)); - - testers - .emplace_front( - std::chrono::milliseconds(due_time), - std::chrono::milliseconds(frequency), - timer_queue) - .start_timer_test(); - } - - std::this_thread::sleep_for(5min); - - for (auto& tester : testers) { - tester.test(); - } -} - -void concurrencpp::tests::test_timer_constructor() { - test_one_timer(); - test_many_timers(); -} - -void concurrencpp::tests::test_timer_destructor_empty_timer() { - timer timer; -} - -void concurrencpp::tests::test_timer_destructor_dead_timer_queue() { - timer timer; - auto executor = std::make_shared(); - - { - auto timer_queue = std::make_shared(); - - timer = timer_queue->make_timer( - 10 * 1000ms, - 10 * 1000ms, - executor, - empty_callback); - } - - //nothing strange should happen here -} - -void concurrencpp::tests::test_timer_destructor_functionality() { - auto executor = std::make_shared(); - auto timer_queue = std::make_shared(); - - size_t invocation_count_before = 0; - - { - auto timer = timer_queue->make_timer(500ms, 500ms, executor, [] {}); - - std::this_thread::sleep_for(4150ms); - - invocation_count_before = executor->invocation_count(); - } - - std::this_thread::sleep_for(4s); - - const auto invocation_count_after = executor->invocation_count(); - assert_equal(invocation_count_before, invocation_count_after); -} - -void concurrencpp::tests::test_timer_destructor_RAII() { - const size_t timer_count = 1'024; - object_observer observer; - auto timer_queue = std::make_shared(); - auto inline_executor = std::make_shared(); - - std::vector timers; - timers.reserve(timer_count); - - for (size_t i = 0; i< timer_count; i++) { - timers.emplace_back(timer_queue->make_timer( - 1'000ms, - 1'000ms, - inline_executor, - observer.get_testing_stub())); - } - - timers.clear(); - timer_queue.reset(); - - assert_equal(observer.get_destruction_count(), timer_count); -} - -void concurrencpp::tests::test_timer_destructor() { - test_timer_destructor_empty_timer(); - test_timer_destructor_dead_timer_queue(); - test_timer_destructor_functionality(); - test_timer_destructor_RAII(); -} - -void concurrencpp::tests::test_timer_cancel_empty_timer() { - timer timer; - timer.cancel(); -} - -void concurrencpp::tests::test_timer_cancel_dead_timer_queue() { - timer timer; - - { - auto timer_queue = std::make_shared(); - auto executor = std::make_shared(); - - timer = timer_queue->make_timer( - 10 * 1000ms, - 10 * 1000ms, - executor, - empty_callback); - } - - timer.cancel(); - assert_false(timer); -} - -void concurrencpp::tests::test_timer_cancel_before_due_time() { - object_observer observer; - auto timer_queue = std::make_shared(); - auto wt_executor = std::make_shared(); - - auto timer = timer_queue->make_timer( - 1'000ms, - 500ms, - wt_executor, - observer.get_testing_stub()); - - timer.cancel(); - assert_false(timer); - - std::this_thread::sleep_for(4s); - - assert_equal(observer.get_execution_count(), size_t(0)); - assert_equal(observer.get_destruction_count(), size_t(1)); - - wt_executor->shutdown(); -} - -void concurrencpp::tests::test_timer_cancel_after_due_time_before_beat() { - object_observer observer; - auto timer_queue = std::make_shared(); - auto wt_executor = std::make_shared(); - - auto timer = timer_queue->make_timer( - 500ms, - 500ms, - wt_executor, - observer.get_testing_stub()); - - std::this_thread::sleep_for(std::chrono::milliseconds(600)); - - timer.cancel(); - assert_false(timer); - - std::this_thread::sleep_for(std::chrono::seconds(3)); - - assert_equal(observer.get_execution_count(), size_t(1)); - assert_equal(observer.get_destruction_count(), size_t(1)); - - wt_executor->shutdown(); -} - -void concurrencpp::tests::test_timer_cancel_after_due_time_after_beat() { - object_observer observer; - auto timer_queue = std::make_shared(); - auto wt_executor = std::make_shared(); - - auto timer = timer_queue->make_timer( - 1'000ms, - 1'000ms, - wt_executor, - observer.get_testing_stub()); - - std::this_thread::sleep_for(std::chrono::milliseconds(1'000 * 4 + 220)); - - const auto execution_count_0 = observer.get_execution_count(); - - timer.cancel(); - assert_false(timer); - - std::this_thread::sleep_for(std::chrono::seconds(4)); - - const auto execution_count_1 = observer.get_execution_count(); - - assert_equal(execution_count_0, execution_count_1); - assert_equal(observer.get_destruction_count(), size_t(1)); - - wt_executor->shutdown(); -} - -void concurrencpp::tests::test_timer_cancel_RAII() { - const size_t timer_count = 1'024; - object_observer observer; - auto timer_queue = std::make_shared(); - auto inline_executor = std::make_shared(); - - std::vector timers; - timers.reserve(timer_count); - - for (size_t i = 0; i < timer_count; i++) { - timers.emplace_back(timer_queue->make_timer( - 1'000ms, - 1'000ms, - inline_executor, - observer.get_testing_stub())); - } - - timer_queue.reset(); - - for(auto& timer : timers) { - timer.cancel(); - } - - assert_true(observer.wait_destruction_count(timer_count, 1min)); -} - -void concurrencpp::tests::test_timer_cancel() { - test_timer_cancel_empty_timer(); - test_timer_cancel_dead_timer_queue(); - test_timer_cancel_before_due_time(); - test_timer_cancel_after_due_time_before_beat(); - test_timer_cancel_after_due_time_after_beat(); - test_timer_cancel_RAII(); -} - -void concurrencpp::tests::test_timer_operator_bool() { - auto timer_queue = std::make_shared(); - auto inline_executor = std::make_shared(); - - auto timer_1 = timer_queue->make_timer( - 10 * 1000ms, - 10 * 1000ms, - inline_executor, - empty_callback); - - assert_true(static_cast(timer_1)); - - auto timer_2 = std::move(timer_1); - assert_false(static_cast(timer_1)); - assert_true(static_cast(timer_2)); - - timer_2.cancel(); - assert_false(static_cast(timer_2)); -} - -void concurrencpp::tests::test_timer_set_frequency_before_due_time() { - auto timer_queue = std::make_shared(); - timer_tester tester(1'000ms, 1'000ms, timer_queue); - tester.start_timer_test(); - - tester.reset(500ms); - - std::this_thread::sleep_for(std::chrono::seconds(5)); - - tester.test_frequencies(500); -} - -void concurrencpp::tests::test_timer_set_frequency_after_due_time() { - auto timer_queue = std::make_shared(); - timer_tester tester(1'000ms, 1'000ms, timer_queue); - tester.start_timer_test(); - - std::this_thread::sleep_for(1200ms); - - tester.reset(500ms); - - std::this_thread::sleep_for(5s); - - tester.test_frequencies(500); -} - -void concurrencpp::tests::test_timer_set_frequency() { - assert_throws([] { - timer timer; - timer.set_frequency(200ms); - }); - - test_timer_set_frequency_before_due_time(); - test_timer_set_frequency_after_due_time(); -} - -void concurrencpp::tests::test_timer_oneshot_timer() { - auto timer_queue = std::make_shared(); - timer_tester tester(350ms, 0ms, timer_queue); - tester.start_once_timer_test(); - - std::this_thread::sleep_for(5s); - - tester.test_oneshot_timer(); -} - -void concurrencpp::tests::test_timer_delay_object() { - auto timer_queue = std::make_shared(); - auto wt_executor = std::make_shared(); - const auto expected_interval = 150ms; - - for (size_t i = 0; i < 15; i++) { - const auto before = std::chrono::high_resolution_clock::now(); - auto delay_object = timer_queue->make_delay_object(expected_interval, wt_executor); - assert_true(static_cast(delay_object)); - delay_object.get(); - const auto after = std::chrono::high_resolution_clock::now(); - const auto interval_ms = duration_cast(after - before); - timer_tester::interval_ok(interval_ms.count(), expected_interval.count()); - } - - wt_executor->shutdown(); -} - -void concurrencpp::tests::test_timer_assignment_operator_empty_to_empty() { - concurrencpp::timer timer1, timer2; - assert_false(timer1); - assert_false(timer2); - - timer1 = std::move(timer2); - - assert_false(timer1); - assert_false(timer2); -} - -void concurrencpp::tests::test_timer_assignment_operator_non_empty_to_non_empty() { - auto timer_queue = std::make_shared(); - - auto executor0 = std::make_shared(); - auto executor1 = std::make_shared(); - - object_observer observer0, observer1; - - auto timer0 = timer_queue->make_timer(10 * 1'000ms, 10 * 1'000ms, executor0, observer0.get_testing_stub()); - auto timer1 = timer_queue->make_timer(20 * 1'000ms, 20 * 1'000ms, executor1, observer1.get_testing_stub()); - - timer0 = std::move(timer1); - - assert_true(timer0); - assert_false(timer1); - - assert_true(observer0.wait_destruction_count(1, 20s)); - assert_equal(observer1.get_destruction_count(), size_t(0)); - - assert_equal(timer0.get_due_time(), 20'000ms); - assert_equal(timer0.get_frequency(), 20'000ms); - assert_equal(timer0.get_executor(), executor1); -} - -void concurrencpp::tests::test_timer_assignment_operator_empty_to_non_empty() { - auto timer_queue = std::make_shared(); - auto executor = std::make_shared(); - object_observer observer; - - auto timer0 = timer_queue->make_timer( - 10 * 1'000ms, - 10 * 1'000ms, - executor, - observer.get_testing_stub()); - concurrencpp::timer timer1; - - timer0 = std::move(timer1); - - assert_false(timer0); - assert_false(timer1); - - assert_true(observer.wait_destruction_count(1, 20s)); -} - -void concurrencpp::tests::test_timer_assignment_operator_non_empty_to_empty() { - auto timer_queue = std::make_shared(); - auto executor = std::make_shared(); - object_observer observer; - - concurrencpp::timer timer0; - auto timer1 = timer_queue->make_timer( - 10 * 1'000ms, - 10 * 1'000ms, - executor, - observer.get_testing_stub()); - - timer0 = std::move(timer1); - - assert_true(timer0); - assert_false(timer1); - - assert_equal(timer0.get_due_time(), 10'000ms); - assert_equal(timer0.get_frequency(), 10'000ms); - assert_equal(timer0.get_executor(), executor); -} - -void concurrencpp::tests::test_timer_assignment_operator_assign_to_self() { - auto timer_queue = std::make_shared(); - auto executor = std::make_shared(); - object_observer observer; - - auto timer = timer_queue->make_timer( - 1'000ms, - 1'000ms, - executor, - observer.get_testing_stub()); - - timer = std::move(timer); - - assert_true(timer); - assert_equal(timer.get_due_time(), 1'000ms); - assert_equal(timer.get_frequency(), 1'000ms); - assert_equal(timer.get_executor(), executor); -} - -void concurrencpp::tests::test_timer_assignment_operator() { - test_timer_assignment_operator_empty_to_empty(); - test_timer_assignment_operator_non_empty_to_non_empty(); - test_timer_assignment_operator_empty_to_non_empty(); - test_timer_assignment_operator_non_empty_to_empty(); - test_timer_assignment_operator_assign_to_self(); -} - -void concurrencpp::tests::test_timer() { - tester test("timer test"); - - test.add_step("constructor", test_timer_constructor); - test.add_step("destructor", test_timer_destructor); - test.add_step("cancel", test_timer_cancel); - test.add_step("operator bool", test_timer_operator_bool); - test.add_step("set_frequency", test_timer_set_frequency); - test.add_step("oneshot_timer", test_timer_oneshot_timer); - test.add_step("delay_object", test_timer_delay_object); - test.add_step("operator =", test_timer_assignment_operator); - - test.launch_test(); -} - diff --git a/thread_sanitizers/CMakeLists.txt b/thread_sanitizers/CMakeLists.txt deleted file mode 100644 index 96acd94c..00000000 --- a/thread_sanitizers/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(thread_sanitizers) - -if (NOT MSVC) - add_compile_options(-fsanitize=thread -g) - - add_subdirectory(executors) - add_subdirectory(result) - add_subdirectory(fibbonacci) - add_subdirectory(quick_sort) - add_subdirectory(matrix_multiplication) - add_subdirectory(when_all) - add_subdirectory(when_any) -endif() \ No newline at end of file diff --git a/thread_sanitizers/executors/CMakeLists.txt b/thread_sanitizers/executors/CMakeLists.txt deleted file mode 100644 index 514ee3c1..00000000 --- a/thread_sanitizers/executors/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(executor_test) - -add_executable( - executor_test - main.cpp - ) - -target_link_libraries(executor_test concurrencpp) diff --git a/thread_sanitizers/executors/main.cpp b/thread_sanitizers/executors/main.cpp deleted file mode 100644 index 4a541116..00000000 --- a/thread_sanitizers/executors/main.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "concurrencpp.h" - -#include - -void test_worker_thread_executor(); -void test_thread_pool_executor(); -void test_thread_executor(); -void test_manual_executor(); - -int main() { - std::cout << "concurrencpp::worker_thread_executor" << std::endl; - test_worker_thread_executor(); - std::cout << "====================================" << std::endl; - - std::cout << "concurrencpp::thread_pool_executor" << std::endl; - test_thread_pool_executor(); - std::cout << "====================================" << std::endl; - - std::cout << "concurrencpp::thread_executor" << std::endl; - test_thread_executor(); - std::cout << "====================================" << std::endl; - - std::cout << "concurrencpp::manual_executor" << std::endl; - test_manual_executor(); - std::cout << "====================================" << std::endl; -} - -using namespace concurrencpp; - -void worker_thread_task( - std::shared_ptr (&executors)[16], - std::atomic_size_t& counter, - std::shared_ptr wc){ - const auto c = counter.fetch_add(1, std::memory_order_relaxed); - - if (c >= 10'000'000){ - if (c == 10'000'000){ - wc->notify(); - } - - return; - } - - const auto worker_pos = ::rand() % std::size(executors); - auto& executor = executors[worker_pos]; - executor->post(worker_thread_task, std::ref(executors), std::ref(counter), wc); -} - -void test_worker_thread_executor(){ - concurrencpp::runtime runtime; - - std::srand(::time(nullptr)); - std::shared_ptr executors[16]; - std::atomic_size_t counter = 0; - auto wc = std::make_shared(); - - for (auto& executor : executors) { - executor = runtime.make_worker_thread_executor(); - } - - for (size_t i = 0 ;i < 16; i++) { - executors[i]->post(worker_thread_task, std::ref(executors), std::ref(counter), wc); - } - - wc->wait(); -} - -void thread_pool_task( - std::shared_ptr tpe, - std::atomic_size_t& counter, - std::shared_ptr wc){ - const auto c = counter.fetch_add(1, std::memory_order_relaxed); - - if (c >= 10'000'000){ - if (c == 10'000'000){ - wc->notify(); - } - - return; - } - - tpe->post(thread_pool_task, tpe, std::ref(counter), wc); -} - -void test_thread_pool_executor(){ - concurrencpp::runtime runtime; - auto tpe = runtime.thread_pool_executor(); - std::atomic_size_t counter = 0; - auto wc = std::make_shared(); - const auto max_concurrency_level = tpe->max_concurrency_level(); - - for (size_t i = 0 ;i < max_concurrency_level; i++) { - tpe->post(thread_pool_task, tpe, std::ref(counter), wc); - } - - wc->wait(); -} - -void thread_task( - std::shared_ptr tp, - std::atomic_size_t& counter, - std::shared_ptr wc){ - const auto c = counter.fetch_add(1, std::memory_order_relaxed); - if (c >= 1'024 * 4){ - if (c == 1'024 * 4){ - wc->notify(); - } - - return; - } - - tp->post(thread_task, tp, std::ref(counter), wc); -} - -void test_thread_executor(){ - concurrencpp::runtime runtime; - auto tp = runtime.thread_executor(); - std::atomic_size_t counter = 0; - auto wc = std::make_shared(); - - for (size_t i = 0 ;i < 4; i++) { - tp->post(thread_task, tp, std::ref(counter), wc); - } - - wc->wait(); -} - -void manual_executor_work_loop( - std::shared_ptr (&executors)[16], - std::atomic_size_t& counter, - const size_t worker_index){ - - while(true){ - const auto c = counter.fetch_add(1, std::memory_order_relaxed); - - if (c >= 10'000'000){ - return; - } - - const auto worker_pos = ::rand() % std::size(executors); - auto& executor = executors[worker_pos]; - executor->post([]{}); - - executors[worker_index]->loop(16); - } -} - -void test_manual_executor() { - concurrencpp::runtime runtime; - std::atomic_size_t counter = 0; - std::shared_ptr executors[16]; - std::thread threads[16]; - - for(auto& executor : executors){ - executor = runtime.make_manual_executor(); - } - - for(size_t i = 0; i < std::size(executors); i++){ - threads[i] = std::thread([&, i]{ - manual_executor_work_loop(executors, std::ref(counter), i); - }); - } - - for(auto& thread : threads){ - thread.join(); - } -} diff --git a/thread_sanitizers/fibbonacci/CMakeLists.txt b/thread_sanitizers/fibbonacci/CMakeLists.txt deleted file mode 100644 index a77c1bde..00000000 --- a/thread_sanitizers/fibbonacci/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(fibbonacci_test) - -add_executable( - fibbonacci_test - main.cpp - ) - -target_link_libraries(fibbonacci_test concurrencpp) diff --git a/thread_sanitizers/fibbonacci/main.cpp b/thread_sanitizers/fibbonacci/main.cpp deleted file mode 100644 index 215679ab..00000000 --- a/thread_sanitizers/fibbonacci/main.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "concurrencpp.h" - -#include - -concurrencpp::result fibbonacci(concurrencpp::executor_tag, std::shared_ptr tpe, int curr); -int fibbonacci_sync(int i); - -int main() { - concurrencpp::runtime_options opts; - opts.max_cpu_threads = 24; - concurrencpp::runtime runtime(opts); - - auto fibb = fibbonacci(concurrencpp::executor_tag{}, runtime.thread_pool_executor(), 32).get(); - auto fibb_sync = fibbonacci_sync(32); - if (fibb != fibb_sync) { - std::cerr << "fibonnacci test failed. expected " << fibb_sync << " got " << fibb << std::endl; - std::abort(); - } - - std::cout << fibb << std::endl; -} - -using namespace concurrencpp; - -result fibbonacci_split(std::shared_ptr tpe, const int curr) { - auto fib_1 = fibbonacci(executor_tag{}, tpe, curr - 1); - auto fib_2 = fibbonacci(executor_tag{}, tpe, curr - 2); - - co_return co_await fib_1 + co_await fib_2; -} - -result fibbonacci(executor_tag, std::shared_ptr tpe, const int curr) { - if (curr == 1) { - co_return 1; - } - - if (curr == 0) { - co_return 0; - } - - co_return co_await fibbonacci_split(tpe, curr); -} - -int fibbonacci_sync(int i) { - if (i == 0) { - return 0; - } - - if (i == 1) { - return 1; - } - - return fibbonacci_sync(i - 1) + fibbonacci_sync(i - 2); -} diff --git a/thread_sanitizers/matrix_multiplication/CMakeLists.txt b/thread_sanitizers/matrix_multiplication/CMakeLists.txt deleted file mode 100644 index 77863fca..00000000 --- a/thread_sanitizers/matrix_multiplication/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(matrix_multiplication_test) - -add_executable( - matrix_multiplication_test - main.cpp - ) - -target_link_libraries(matrix_multiplication_test concurrencpp) diff --git a/thread_sanitizers/matrix_multiplication/main.cpp b/thread_sanitizers/matrix_multiplication/main.cpp deleted file mode 100644 index 1249602d..00000000 --- a/thread_sanitizers/matrix_multiplication/main.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "concurrencpp.h" - -#include -#include -#include -#include - -concurrencpp::result test_matrix_multiplication(std::shared_ptr ex); - -int main() { - concurrencpp::runtime runtime; - test_matrix_multiplication(runtime.thread_pool_executor()).get(); -} - -using namespace concurrencpp; - -using matrix = std::array, 1024>; - -std::unique_ptr make_matrix() { - std::default_random_engine generator; - std::uniform_real_distribution distribution(-1000.0, 1000.0); - auto mtx_ptr = std::make_unique(); - auto& mtx = *mtx_ptr; - - for (size_t i = 0; i < 1024; i++) { - for (size_t j = 0; j < 1024; j++) { - mtx[i][j] = distribution(generator); - } - } - - return mtx_ptr; -} - -void test_matrix(const matrix& mtx0, const matrix& mtx1, const matrix& mtx2) { - for (size_t i = 0; i < 1024; i++) { - for (size_t j = 0; j < 1024; j++) { - - double res = 0.0; - - for (size_t k = 0; k < 1024; k++) { - res += mtx0[i][k] * mtx1[k][j]; - } - - if (mtx2[i][j] != res) { - std::cerr - << "matrix multiplication test failed. expected " - << res << " got " << mtx2[i][j] << - "at matix position[" << i << "," << j << std::endl; - } - } - } -} - -result do_multiply( - executor_tag, - std::shared_ptr executor, - const matrix& mtx0, - const matrix& mtx1, - matrix& mtx2, - size_t line, - size_t col){ - - double res = 0.0; - for (size_t i = 0; i < 1024; i++) { - res += mtx0[line][i] * mtx1[i][col]; - } - - mtx2[line][col] = res; - co_return; -}; - -result test_matrix_multiplication(std::shared_ptr ex){ - auto mtx0_ptr = make_matrix(); - auto mtx1_ptr = make_matrix(); - auto mtx2_ptr = std::make_unique(); - - matrix& mtx0 = *mtx0_ptr; - matrix& mtx1 = *mtx1_ptr; - matrix& mtx2 = *mtx2_ptr; - - std::vector> results; - results.reserve(1024 * 1024); - - for (size_t i = 0; i < 1024; i++) { - for (size_t j = 0; j < 1024; j++) { - results.emplace_back(do_multiply({}, ex, mtx0, mtx1, mtx2, i, j)); - } - } - - for (auto& result : results) { - co_await result; - } - - test_matrix(mtx0, mtx1, mtx2); -} \ No newline at end of file diff --git a/thread_sanitizers/quick_sort/CMakeLists.txt b/thread_sanitizers/quick_sort/CMakeLists.txt deleted file mode 100644 index 9aafbf10..00000000 --- a/thread_sanitizers/quick_sort/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(quick_sort_test) - -add_executable( - quick_sort_test - main.cpp - ) - -target_link_libraries(quick_sort_test concurrencpp) diff --git a/thread_sanitizers/quick_sort/main.cpp b/thread_sanitizers/quick_sort/main.cpp deleted file mode 100644 index f39aa623..00000000 --- a/thread_sanitizers/quick_sort/main.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "concurrencpp.h" - -#include - -concurrencpp::result quick_sort( - concurrencpp::executor_tag, - std::shared_ptr tp, - int* a, - int lo, - int hi); - -int main() { - concurrencpp::runtime_options opts; - opts.max_cpu_threads = 24; - concurrencpp::runtime runtime(opts); - - ::srand(::time(nullptr)); - - std::vector array(8 * 1'000'000); - for (auto& i : array) { - i = rand() % 10 * 10'000; - } - - quick_sort({}, runtime.thread_pool_executor(), array.data(), 0, array.size() - 1).get(); - - const auto is_sorted = std::is_sorted(array.begin(), array.end()); - if (!is_sorted) { - std::cerr << "array is not sorted." << std::endl; - } -} - -using namespace concurrencpp; - -/* - -algorithm partition(A, lo, hi) is - pivot := A[(hi + lo) / 2] - i := lo - 1 - j := hi + 1 - loop forever - do - i := i + 1 - while A[i] < pivot - do - j := j - 1 - while A[j] > pivot - if i ≥ j then - return j - swap A[i] with A[j] -*/ - -int partition(int* a, int lo, int hi) { - const auto pivot = a[(hi + lo) / 2]; - auto i = lo - 1; - auto j = hi + 1; - - while (true) { - do { - ++i; - } while (a[i] < pivot); - - do { - --j; - } while (a[j] > pivot); - - if (i >= j) { - return j; - } - - std::swap(a[i], a[j]); - } -} - -/* - algorithm quicksort(A, lo, hi) is - if lo < hi then - p := partition(A, lo, hi) - quicksort(A, lo, p) - quicksort(A, p + 1, hi) - -*/ -result quick_sort(executor_tag, std::shared_ptr tp, int* a, int lo, int hi) { - if (lo >= hi) { - co_return; - } - - const auto p = partition(a, lo, hi); - auto res0 = quick_sort(executor_tag{}, tp, a, lo, p); - auto res1 = quick_sort(executor_tag{}, tp, a, p + 1, hi); - - co_await res0; - co_await res1; -} diff --git a/thread_sanitizers/result/CMakeLists.txt b/thread_sanitizers/result/CMakeLists.txt deleted file mode 100644 index a8ad0388..00000000 --- a/thread_sanitizers/result/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(result_test) - -add_executable( - result_test - main.cpp - ) - -target_link_libraries(result_test concurrencpp) diff --git a/thread_sanitizers/when_all/CMakeLists.txt b/thread_sanitizers/when_all/CMakeLists.txt deleted file mode 100644 index b1901ed3..00000000 --- a/thread_sanitizers/when_all/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(when_all_test) - -add_executable( - when_all_test - main.cpp - ) - -target_link_libraries(when_all_test concurrencpp) diff --git a/thread_sanitizers/when_all/main.cpp b/thread_sanitizers/when_all/main.cpp deleted file mode 100644 index 1686256b..00000000 --- a/thread_sanitizers/when_all/main.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "concurrencpp.h" - -#include - -concurrencpp::result when_all_test(std::shared_ptr tpe); - -int main() { - concurrencpp::runtime runtime; - when_all_test(runtime.thread_executor()).get(); - return 0; -} - -using namespace concurrencpp; - -std::vector> run_loop_once(std::shared_ptr tpe){ - std::vector> results; - results.reserve(1'024); - - for (size_t i = 0; i< 1'024; i++){ - results.emplace_back(tpe->submit([]{ - std::this_thread::yield(); - return 0; - })); - } - - return results; -} - -void assert_loop(result>> result_list){ - if (result_list.status() != result_status::value){ - std::abort(); - } - - auto results = result_list.get(); - - for (auto& result : results){ - if (result.get() != 0){ - std::abort(); - } - } -} - -result when_all_test(std::shared_ptr tpe){ - auto loop_0 = run_loop_once(tpe); - auto loop_1 = run_loop_once(tpe); - auto loop_2 = run_loop_once(tpe); - auto loop_3 = run_loop_once(tpe); - auto loop_4 = run_loop_once(tpe); - - auto loop_0_all = when_all(loop_0.begin(), loop_0.end()); - auto loop_1_all = when_all(loop_1.begin(), loop_1.end()); - auto loop_2_all = when_all(loop_2.begin(), loop_2.end()); - auto loop_3_all = when_all(loop_3.begin(), loop_3.end()); - auto loop_4_all = when_all(loop_4.begin(), loop_4.end()); - - auto all = when_all( - std::move(loop_0_all), - std::move(loop_1_all), - std::move(loop_2_all), - std::move(loop_3_all), - std::move(loop_4_all)); - - auto all_done = co_await all; - - assert_loop(std::move(std::get<0>(all_done))); - assert_loop(std::move(std::get<1>(all_done))); - assert_loop(std::move(std::get<2>(all_done))); - assert_loop(std::move(std::get<3>(all_done))); - assert_loop(std::move(std::get<4>(all_done))); -} diff --git a/thread_sanitizers/when_any/CMakeLists.txt b/thread_sanitizers/when_any/CMakeLists.txt deleted file mode 100644 index 72daea0d..00000000 --- a/thread_sanitizers/when_any/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(when_any_test) - -add_executable( - when_any_test - main.cpp - ) - -target_link_libraries(when_any_test concurrencpp) diff --git a/thread_sanitizers/when_any/main.cpp b/thread_sanitizers/when_any/main.cpp deleted file mode 100644 index 7e6941f4..00000000 --- a/thread_sanitizers/when_any/main.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "concurrencpp.h" - -#include -#include - -concurrencpp::result when_any_vector_test(std::shared_ptr te); -concurrencpp::result when_any_tuple_test(std::shared_ptr te); - -int main() { - concurrencpp::runtime runtime; - when_any_vector_test(runtime.thread_executor()).get(); - std::cout << "=========================================" << std::endl; - when_any_tuple_test(runtime.thread_executor()).get(); - std::cout << "=========================================" << std::endl; - return 0; -} - -using namespace concurrencpp; - -struct random_ctx { - std::random_device rd; - std::mt19937 mt; - std::uniform_int_distribution dist; - - random_ctx(): mt(rd()), dist(1, 5 * 1000) { } - - size_t operator() () noexcept { - return dist(mt); - } -}; - -std::vector> run_loop_once(std::shared_ptr te, random_ctx& r, size_t count){ - std::vector> results; - results.reserve(count); - - for (size_t i = 0; i< count; i++){ - const auto sleeping_time = r(); - results.emplace_back(te->submit([sleeping_time]{ - std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time)); - return 0; - })); - } - - return results; -} - -result when_any_vector_test(std::shared_ptr te){ - random_ctx r; - - auto loop = run_loop_once(te, r, 1'024); - - while(!loop.empty()){ - auto any = co_await when_any(loop.begin(), loop.end()); - auto& done_result = any.results[any.index]; - - if (done_result.status() != result_status::value){ - std::abort(); - } - - if (done_result.get() != 0){ - std::abort(); - } - - any.results.erase(any.results.begin() + any.index); - loop = std::move(any.results); - } -} - -template -std::vector> to_vector(std::tuple& results){ - return std::apply([](auto&&... elems) { - std::vector> result; - result.reserve(sizeof...(elems)); - (result.emplace_back(std::move(elems)), ...); - return result; - }, std::forward(results)); -} - -concurrencpp::result when_any_tuple_test(std::shared_ptr te){ - random_ctx r; - auto loop = run_loop_once(te, r, 10); - - for(size_t i = 0; i < 256; i++) { - auto any = co_await when_any( - std::move(loop[0]), - std::move(loop[1]), - std::move(loop[2]), - std::move(loop[3]), - std::move(loop[4]), - std::move(loop[5]), - std::move(loop[6]), - std::move(loop[7]), - std::move(loop[8]), - std::move(loop[9])); - - loop = to_vector(any.results); - - const auto done_index = any.index; - auto& done_result = loop[done_index]; - - if (done_result.status() != result_status::value){ - std::abort(); - } - - if (done_result.get() != 0){ - std::abort(); - } - - loop.erase(loop.begin() + done_index); - - const auto sleeping_time = r(); - loop.emplace_back(te->submit([sleeping_time]{ - std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time)); - return 0; - })); - } -}