From d1263aebd74ce9e30d543abc749418a43b988d10 Mon Sep 17 00:00:00 2001 From: Nils Date: Wed, 28 Jul 2021 11:43:57 +0200 Subject: [PATCH] Removed non-STL components --- .gitmodules | 3 - CMakeLists.txt | 38 - README.md | 676 -- examples/CMakeLists.txt | 64 - examples/coro_event.cpp | 26 - examples/coro_generator.cpp | 31 - examples/coro_io_scheduler.cpp | 144 - examples/coro_latch.cpp | 54 - examples/coro_mutex.cpp | 38 - examples/coro_ring_buffer.cpp | 74 - examples/coro_semaphore.cpp | 29 - examples/coro_shared_mutex.cpp | 55 - examples/coro_task.cpp | 76 - examples/coro_task_container.cpp | 82 - examples/coro_thread_pool.cpp | 78 - test/CMakeLists.txt | 42 - test/bench.cpp | 735 -- test/catch.hpp | 17615 ----------------------------- test/main.cpp | 32 - test/net/test_dns_resolver.cpp | 34 - test/net/test_ip_address.cpp | 73 - test/net/test_tcp_server.cpp | 199 - test/net/test_udp_peers.cpp | 118 - test/test_event.cpp | 264 - test/test_generator.cpp | 40 - test/test_io_scheduler.cpp | 732 -- test/test_latch.cpp | 110 - test/test_mutex.cpp | 113 - test/test_ring_buffer.cpp | 113 - test/test_semaphore.cpp | 226 - test/test_shared_mutex.cpp | 160 - test/test_sync_wait.cpp | 58 - test/test_task.cpp | 243 - test/test_thread_pool.cpp | 193 - test/test_when_all.cpp | 109 - vendor/c-ares/c-ares | 1 - 36 files changed, 22678 deletions(-) delete mode 100644 examples/CMakeLists.txt delete mode 100644 examples/coro_event.cpp delete mode 100644 examples/coro_generator.cpp delete mode 100644 examples/coro_io_scheduler.cpp delete mode 100644 examples/coro_latch.cpp delete mode 100644 examples/coro_mutex.cpp delete mode 100644 examples/coro_ring_buffer.cpp delete mode 100644 examples/coro_semaphore.cpp delete mode 100644 examples/coro_shared_mutex.cpp delete mode 100644 examples/coro_task.cpp delete mode 100644 examples/coro_task_container.cpp delete mode 100644 examples/coro_thread_pool.cpp delete mode 100644 test/CMakeLists.txt delete mode 100644 test/bench.cpp delete mode 100644 test/catch.hpp delete mode 100644 test/main.cpp delete mode 100644 test/net/test_dns_resolver.cpp delete mode 100644 test/net/test_ip_address.cpp delete mode 100644 test/net/test_tcp_server.cpp delete mode 100644 test/net/test_udp_peers.cpp delete mode 100644 test/test_event.cpp delete mode 100644 test/test_generator.cpp delete mode 100644 test/test_io_scheduler.cpp delete mode 100644 test/test_latch.cpp delete mode 100644 test/test_mutex.cpp delete mode 100644 test/test_ring_buffer.cpp delete mode 100644 test/test_semaphore.cpp delete mode 100644 test/test_shared_mutex.cpp delete mode 100644 test/test_sync_wait.cpp delete mode 100644 test/test_task.cpp delete mode 100644 test/test_thread_pool.cpp delete mode 100644 test/test_when_all.cpp delete mode 160000 vendor/c-ares/c-ares diff --git a/.gitmodules b/.gitmodules index 0357437..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "vendor/c-ares/c-ares"] - path = vendor/c-ares/c-ares - url = https://github.com/c-ares/c-ares.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ff4fd39..9525bd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,20 +8,10 @@ execute_process( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) -option(LIBCORO_BUILD_TESTS "Build the tests, Default=ON." ON) -option(LIBCORO_CODE_COVERAGE "Enable code coverage, tests must also be enabled, Default=OFF" OFF) -option(LIBCORO_BUILD_EXAMPLES "Build the examples, Default=ON." ON) - -message("${PROJECT_NAME} LIBCORO_BUILD_TESTS = ${LIBCORO_BUILD_TESTS}") -message("${PROJECT_NAME} LIBCORO_CODE_COVERAGE = ${LIBCORO_CODE_COVERAGE}") -message("${PROJECT_NAME} LIBCORO_BUILD_EXAMPLES = ${LIBCORO_BUILD_EXAMPLES}") - set(CARES_STATIC ON CACHE INTERNAL "") set(CARES_SHARED OFF CACHE INTERNAL "") set(CARES_INSTALL OFF CACHE INTERNAL "") -add_subdirectory(vendor/c-ares/c-ares) - set(LIBCORO_SOURCE_FILES inc/coro/concepts/awaitable.hpp inc/coro/concepts/buffer.hpp @@ -32,24 +22,10 @@ set(LIBCORO_SOURCE_FILES inc/coro/detail/poll_info.hpp inc/coro/detail/void_value.hpp - inc/coro/net/connect.hpp src/net/connect.cpp - inc/coro/net/dns_resolver.hpp src/net/dns_resolver.cpp - inc/coro/net/hostname.hpp - inc/coro/net/ip_address.hpp src/net/ip_address.cpp - inc/coro/net/recv_status.hpp src/net/recv_status.cpp - inc/coro/net/send_status.hpp src/net/send_status.cpp - inc/coro/net/socket.hpp src/net/socket.cpp - inc/coro/net/ssl_context.hpp src/net/ssl_context.cpp - inc/coro/net/ssl_handshake_status.hpp - inc/coro/net/tcp_client.hpp src/net/tcp_client.cpp - inc/coro/net/tcp_server.hpp src/net/tcp_server.cpp - inc/coro/net/udp_peer.hpp src/net/udp_peer.cpp - inc/coro/coro.hpp inc/coro/event.hpp src/event.cpp inc/coro/fd.hpp inc/coro/generator.hpp - inc/coro/io_scheduler.hpp src/io_scheduler.cpp inc/coro/latch.hpp inc/coro/mutex.hpp src/mutex.cpp inc/coro/poll.hpp @@ -79,17 +55,3 @@ if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") message(FATAL_ERROR "Clang is currently not supported.") endif() - -if(LIBCORO_BUILD_TESTS) - if(LIBCORO_CODE_COVERAGE) - target_compile_options(${PROJECT_NAME} PRIVATE --coverage) - target_link_libraries(${PROJECT_NAME} PRIVATE gcov) - endif() - - enable_testing() - add_subdirectory(test) -endif() - -if(LIBCORO_BUILD_EXAMPLES) - add_subdirectory(examples) -endif() \ No newline at end of file diff --git a/README.md b/README.md index fb6cee4..fe6a622 100644 --- a/README.md +++ b/README.md @@ -50,82 +50,7 @@ The `coro::task` is the main coroutine building block within `libcoro`. Use ```C++ -#include -#include -int main() -{ - // Task that takes a value and doubles it. - auto double_task = [](uint64_t x) -> coro::task { co_return x* x; }; - - // Create a task that awaits the doubling of its given value and - // then returns the result after adding 5. - auto double_and_add_5_task = [&](uint64_t input) -> coro::task { - auto doubled = co_await double_task(input); - co_return doubled + 5; - }; - - auto output = coro::sync_wait(double_and_add_5_task(2)); - std::cout << "Task1 output = " << output << "\n"; - - struct expensive_struct - { - std::string id{}; - std::vector records{}; - - expensive_struct() = default; - ~expensive_struct() = default; - - // Explicitly delete copy constructor and copy assign, force only moves! - // While the default move constructors will work for this struct the example - // inserts explicit print statements to show the task is moving the value - // out correctly. - expensive_struct(const expensive_struct&) = delete; - auto operator=(const expensive_struct&) -> expensive_struct& = delete; - - expensive_struct(expensive_struct&& other) : id(std::move(other.id)), records(std::move(other.records)) - { - std::cout << "expensive_struct() move constructor called\n"; - } - auto operator=(expensive_struct&& other) -> expensive_struct& - { - if (std::addressof(other) != this) - { - id = std::move(other.id); - records = std::move(other.records); - } - std::cout << "expensive_struct() move assignment called\n"; - return *this; - } - }; - - // Create a very large object and return it by moving the value so the - // contents do not have to be copied out. - auto move_output_task = []() -> coro::task { - expensive_struct data{}; - data.id = "12345678-1234-5678-9012-123456781234"; - for (size_t i = 10'000; i < 100'000; ++i) - { - data.records.emplace_back(std::to_string(i)); - } - - // Because the struct only has move contructors it will be forced to use - // them, no need to explicitly std::move(data). - co_return data; - }; - - auto data = coro::sync_wait(move_output_task()); - std::cout << data.id << " has " << data.records.size() << " records.\n"; - - // std::unique_ptr can also be used to return a larger object. - auto unique_ptr_task = []() -> coro::task> { co_return std::make_unique(42); }; - - auto answer_to_everything = coro::sync_wait(unique_ptr_task()); - if (answer_to_everything != nullptr) - { - std::cout << "Answer to everything = " << *answer_to_everything << "\n"; - } -} ``` Expected output: @@ -143,37 +68,7 @@ Answer to everything = 42 The `coro::generator` construct is a coroutine which can generate one or more values. ```C++ -#include -#include -int main() -{ - auto task = [](uint64_t count_to) -> coro::task { - // Create a generator function that will yield and incrementing - // number each time its called. - auto gen = []() -> coro::generator { - uint64_t i = 0; - while (true) - { - co_yield i++; - } - }; - - // Generate the next number until its greater than count to. - for (auto val : gen()) - { - std::cout << val << ", "; - - if (val >= count_to) - { - break; - } - } - co_return; - }; - - coro::sync_wait(task(100)); -} ``` Expected output: @@ -186,32 +81,7 @@ $ ./examples/coro_generator The `coro::event` is a thread safe async tool to have 1 or more waiters suspend for an event to be set before proceeding. The implementation of event currently will resume execution of all waiters on the thread that sets the event. If the event is already set when a waiter goes to wait on the thread they will simply continue executing with no suspend or wait time incurred. ```C++ -#include -#include -int main() -{ - coro::event e; - - // These tasks will wait until the given event has been set before advancing. - auto make_wait_task = [](const coro::event& e, uint64_t i) -> coro::task { - std::cout << "task " << i << " is waiting on the event...\n"; - co_await e; - std::cout << "task " << i << " event triggered, now resuming.\n"; - co_return; - }; - - // This task will trigger the event allowing all waiting tasks to proceed. - auto make_set_task = [](coro::event& e) -> coro::task { - std::cout << "set task is triggering the event\n"; - e.set(); - co_return; - }; - - // Given more than a single task to synchronously wait on, use when_all() to execute all the - // tasks concurrently on this thread and then sync_wait() for them all to complete. - coro::sync_wait(coro::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); -} ``` Expected output: @@ -230,60 +100,7 @@ task 1 event triggered, now resuming. The `coro::latch` is a thread safe async tool to have 1 waiter suspend until all outstanding events have completed before proceeding. ```C++ -#include -#include -int main() -{ - // Complete worker tasks faster on a thread pool, using the io_scheduler version so the worker - // tasks can yield for a specific amount of time to mimic difficult work. The pool is only - // setup with a single thread to showcase yield_for(). - coro::io_scheduler tp{coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}}; - - // This task will wait until the given latch setters have completed. - auto make_latch_task = [](coro::latch& l) -> coro::task { - // It seems like the dependent worker tasks could be created here, but in that case it would - // be superior to simply do: `co_await coro::when_all(tasks);` - // It is also important to note that the last dependent task will resume the waiting latch - // task prior to actually completing -- thus the dependent task's frame could be destroyed - // by the latch task completing before it gets a chance to finish after calling resume() on - // the latch task! - - std::cout << "latch task is now waiting on all children tasks...\n"; - co_await l; - std::cout << "latch task dependency tasks completed, resuming.\n"; - co_return; - }; - - // This task does 'work' and counts down on the latch when completed. The final child task to - // complete will end up resuming the latch task when the latch's count reaches zero. - auto make_worker_task = [](coro::io_scheduler& tp, coro::latch& l, int64_t i) -> coro::task { - // Schedule the worker task onto the thread pool. - co_await tp.schedule(); - std::cout << "worker task " << i << " is working...\n"; - // Do some expensive calculations, yield to mimic work...! Its also important to never use - // std::this_thread::sleep_for() within the context of coroutines, it will block the thread - // and other tasks that are ready to execute will be blocked. - co_await tp.yield_for(std::chrono::milliseconds{i * 20}); - std::cout << "worker task " << i << " is done, counting down on the latch\n"; - l.count_down(); - co_return; - }; - - const int64_t num_tasks{5}; - coro::latch l{num_tasks}; - std::vector> tasks{}; - - // Make the latch task first so it correctly waits for all worker tasks to count down. - tasks.emplace_back(make_latch_task(l)); - for (int64_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_worker_task(tp, l, i)); - } - - // Wait for all tasks to complete. - coro::sync_wait(coro::when_all(std::move(tasks))); -} ``` Expected output: @@ -311,44 +128,7 @@ Its important to note that upon releasing the mutex that thread unlocking the mu The suspend waiter queue is LIFO, however the worker that current holds the mutex will periodically 'acquire' the current LIFO waiter list to process those waiters when its internal list becomes empty. This effectively resets the suspended waiter list to empty and the worker holding the mutex will work through the newly acquired LIFO queue of waiters. It would be possible to reverse this list to be as fair as possible, however not reversing the list should result is better throughput at possibly the cost of some latency for the first suspended waiters on the 'current' LIFO queue. Reversing the list, however, would introduce latency for all queue waiters since its done everytime the LIFO queue is swapped. ```C++ -#include -#include -int main() -{ - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 4}}; - std::vector output{}; - coro::mutex mutex; - - auto make_critical_section_task = [&](uint64_t i) -> coro::task { - co_await tp.schedule(); - // To acquire a mutex lock co_await its lock() function. Upon acquiring the lock the - // lock() function returns a coro::scoped_lock that holds the mutex and automatically - // unlocks the mutex upon destruction. This behaves just like std::scoped_lock. - { - auto scoped_lock = co_await mutex.lock(); - output.emplace_back(i); - } // <-- scoped lock unlocks the mutex here. - co_return; - }; - - const size_t num_tasks{100}; - std::vector> tasks{}; - tasks.reserve(num_tasks); - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_critical_section_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); - - // The output will be variable per run depending on how the tasks are picked up on the - // thread pool workers. - for (const auto& value : output) - { - std::cout << value << ", "; - } -} ``` Expected output, note that the output will vary from run to run based on how the thread pool workers @@ -367,61 +147,7 @@ The `coro::shared_mutex` requires a `executor_type` when constructed to be able ```C++ -#include -#include -int main() -{ - // Shared mutexes require an excutor type to be able to wake up multiple shared waiters when - // there is an exclusive lock holder releasing the lock. This example uses a single thread - // to also show the interleaving of coroutines acquiring the shared lock in shared and - // exclusive mode as they resume and suspend in a linear manner. Ideally the thread pool - // executor would have more than 1 thread to resume all shared waiters in parallel. - auto tp = std::make_shared(coro::thread_pool::options{.thread_count = 1}); - coro::shared_mutex mutex{tp}; - - auto make_shared_task = [&](uint64_t i) -> coro::task { - co_await tp->schedule(); - { - std::cerr << "shared task " << i << " lock_shared()\n"; - auto scoped_lock = co_await mutex.lock_shared(); - std::cerr << "shared task " << i << " lock_shared() acquired\n"; - /// Immediately yield so the other shared tasks also acquire in shared state - /// while this task currently holds the mutex in shared state. - co_await tp->yield(); - std::cerr << "shared task " << i << " unlock_shared()\n"; - } - co_return; - }; - - auto make_exclusive_task = [&]() -> coro::task { - co_await tp->schedule(); - - std::cerr << "exclusive task lock()\n"; - auto scoped_lock = co_await mutex.lock(); - std::cerr << "exclusive task lock() acquired\n"; - // Do the exclusive work.. - std::cerr << "exclusive task unlock()\n"; - co_return; - }; - - // Create 3 shared tasks that will acquire the mutex in a shared state. - const size_t num_tasks{3}; - std::vector> tasks{}; - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_shared_task(i)); - } - // Create an exclusive task. - tasks.emplace_back(make_exclusive_task()); - // Create 3 more shared tasks that will be blocked until the exclusive task completes. - for (size_t i = num_tasks + 1; i <= num_tasks * 2; ++i) - { - tasks.emplace_back(make_shared_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); -} ``` Example output, notice how the (4,5,6) shared tasks attempt to acquire the lock in a shared state but are blocked behind the exclusive waiter until it completes: @@ -455,35 +181,7 @@ shared task 6 unlock_shared() The `coro::semaphore` is a thread safe async tool to protect a limited number of resources by only allowing so many consumers to acquire the resources a single time. The `coro::semaphore` also has a maximum number of resources denoted by its constructor. This means if a resource is produced or released when the semaphore is at its maximum resource availability then the release operation will await for space to become available. This is useful for a ringbuffer type situation where the resources are produced and then consumed, but will have no effect on a semaphores usage if there is a set known quantity of resources to start with and are acquired and then released back. ```C++ -#include -#include -int main() -{ - // Have more threads/tasks than the semaphore will allow for at any given point in time. - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 8}}; - coro::semaphore semaphore{2}; - - auto make_rate_limited_task = [&](uint64_t task_num) -> coro::task { - co_await tp.schedule(); - - // This will only allow 2 tasks through at any given point in time, all other tasks will - // await the resource to be available before proceeding. - co_await semaphore.acquire(); - std::cout << task_num << ", "; - semaphore.release(); - co_return; - }; - - const size_t num_tasks{100}; - std::vector> tasks{}; - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_rate_limited_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); -} ``` Expected output, note that there is no lock around the `std::cout` so some of the output isn't perfect. @@ -498,80 +196,7 @@ The `coro::ring_buffer` is thread safe async multi-produc The `coro::ring_buffer` also works with `coro::stop_signal` in that if the ring buffers `stop_signal_notify_waiters()` function is called then any producers or consumers that are suspended and waiting will be awoken by throwing a `coro::stop_signal`. This can be useful to write code that will always suspend if data cannot be produced or consumed for long running daemons but will need to break out of the suspend unpon shutdown. ```C++ -#include -#include -int main() -{ - const size_t iterations = 100; - const size_t consumers = 4; - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 4}}; - coro::ring_buffer rb{}; - coro::mutex m{}; - - std::vector> tasks{}; - - auto make_producer_task = [&]() -> coro::task { - co_await tp.schedule(); - - for (size_t i = 1; i <= iterations; ++i) - { - co_await rb.produce(i); - } - - // Wait for the ring buffer to clear all items so its a clean stop. - while (!rb.empty()) - { - co_await tp.yield(); - } - - // Now that the ring buffer is empty signal to all the consumers its time to stop. Note that - // the stop signal works on producers as well, but this example only uses 1 producer. - { - auto scoped_lock = co_await m.lock(); - std::cerr << "\nproducer is sending stop signal"; - } - rb.stop_signal_notify_waiters(); - co_return; - }; - - auto make_consumer_task = [&](size_t id) -> coro::task { - co_await tp.schedule(); - - try - { - while (true) - { - auto value = co_await rb.consume(); - { - auto scoped_lock = co_await m.lock(); - std::cout << "(id=" << id << ", v=" << value << "), "; - } - - // Mimic doing some work on the consumed value. - co_await tp.yield(); - } - } - catch (const coro::stop_signal&) - { - auto scoped_lock = co_await m.lock(); - std::cerr << "\nconsumer " << id << " shutting down, stop signal received"; - } - - co_return; - }; - - // Create N consumers - for (size_t i = 0; i < consumers; ++i) - { - tasks.emplace_back(make_consumer_task(i)); - } - // Create 1 producer. - tasks.emplace_back(make_producer_task()); - - // Wait for all the values to be produced and consumed through the ring buffer. - coro::sync_wait(coro::when_all(std::move(tasks))); -} ``` Expected output: @@ -589,84 +214,7 @@ consumer 3 shutting down, stop signal received `coro::thread_pool` is a statically sized pool of worker threads to execute scheduled coroutines from a FIFO queue. To schedule a coroutine on a thread pool the pool's `schedule()` function should be `co_awaited` to transfer the execution from the current thread to a thread pool worker thread. Its important to note that scheduling will first place the coroutine into the FIFO queue and will be picked up by the first available thread in the pool, e.g. there could be a delay if there is a lot of work queued up. ```C++ -#include -#include -#include -int main() -{ - coro::thread_pool tp{coro::thread_pool::options{ - // By default all thread pools will create its thread count with the - // std::thread::hardware_concurrency() as the number of worker threads in the pool, - // but this can be changed via this thread_count option. This example will use 4. - .thread_count = 4, - // Upon starting each worker thread an optional lambda callback with the worker's - // index can be called to make thread changes, perhaps priority or change the thread's - // name. - .on_thread_start_functor = [](std::size_t worker_idx) -> void { - std::cout << "thread pool worker " << worker_idx << " is starting up.\n"; - }, - // Upon stopping each worker thread an optional lambda callback with the worker's - // index can b called. - .on_thread_stop_functor = [](std::size_t worker_idx) -> void { - std::cout << "thread pool worker " << worker_idx << " is shutting down.\n"; - }}}; - - auto offload_task = [&](uint64_t child_idx) -> coro::task { - // Start by scheduling this offload worker task onto the thread pool. - co_await tp.schedule(); - // Now any code below this schedule() line will be executed on one of the thread pools - // worker threads. - - // Mimic some expensive task that should be run on a background thread... - std::random_device rd; - std::mt19937 gen{rd()}; - std::uniform_int_distribution<> d{0, 1}; - - size_t calculation{0}; - for (size_t i = 0; i < 1'000'000; ++i) - { - calculation += d(gen); - - // Lets be nice and yield() to let other coroutines on the thread pool have some cpu - // time. This isn't necessary but is illustrated to show how tasks can cooperatively - // yield control at certain points of execution. Its important to never call the - // std::this_thread::sleep_for() within the context of a coroutine, that will block - // and other coroutines which are ready for execution from starting, always use yield() - // or within the context of a coro::io_scheduler you can use yield_for(amount). - if (i == 500'000) - { - std::cout << "Task " << child_idx << " is yielding()\n"; - co_await tp.yield(); - } - } - co_return calculation; - }; - - auto primary_task = [&]() -> coro::task { - const size_t num_children{10}; - std::vector> child_tasks{}; - child_tasks.reserve(num_children); - for (size_t i = 0; i < num_children; ++i) - { - child_tasks.emplace_back(offload_task(i)); - } - - // Wait for the thread pool workers to process all child tasks. - auto results = co_await coro::when_all(std::move(child_tasks)); - - // Sum up the results of the completed child tasks. - size_t calculation{0}; - for (const auto& task : results) - { - calculation += task.return_value(); - } - co_return calculation; - }; - - auto result = coro::sync_wait(primary_task()); - std::cout << "calculated thread pool result = " << result << "\n"; -} ``` Example output (will vary based on threads): @@ -710,150 +258,7 @@ Before getting to an example there are two methods of scheduling work onto an i/ The example provided here shows an i/o scheduler that spins up a basic `coro::net::tcp_server` and a `coro::net::tcp_client` that will connect to each other and then send a request and a response. ```C++ -#include -#include -int main() -{ - auto scheduler = std::make_shared(coro::io_scheduler::options{ - // The scheduler will spawn a dedicated event processing thread. This is the default, but - // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. - .thread_strategy = coro::io_scheduler::thread_strategy_t::spawn, - // If the scheduler is in spawn mode this functor is called upon starting the dedicated - // event processor thread. - .on_io_thread_start_functor = [] { std::cout << "io_scheduler::process event thread start\n"; }, - // If the scheduler is in spawn mode this functor is called upon stopping the dedicated - // event process thread. - .on_io_thread_stop_functor = [] { std::cout << "io_scheduler::process event thread stop\n"; }, - // The io scheduler uses a coro::thread_pool to process the events or tasks it is given. - // The tasks are not processed inline on the dedicated event processor thread so events can - // be received and handled as soon as a worker thread is available. See the coro::thread_pool - // for the available options and their descriptions. - .pool = - coro::thread_pool::options{ - .thread_count = 2, - .on_thread_start_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " starting\n"; }, - .on_thread_stop_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " stopping\n"; }}}); - - auto make_server_task = [&]() -> coro::task { - // Start by creating a tcp server, we'll do this before putting it into the scheduler so - // it is immediately available for the client to connect since this will create a socket, - // bind the socket and start listening on that socket. See tcp_server for more details on - // how to specify the local address and port to bind to as well as enabling SSL/TLS. - coro::net::tcp_server server{scheduler}; - - // Now scheduler this task onto the scheduler. - co_await scheduler->schedule(); - - // Wait for an incoming connection and accept it. - auto poll_status = co_await server.poll(); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error, see poll_status for detailed error states. - } - - // Accept the incoming client connection. - auto client = server.accept(); - - // Verify the incoming connection was accepted correctly. - if (!client.socket().is_valid()) - { - co_return; // Handle error. - } - - // Now wait for the client message, this message is small enough it should always arrive - // with a single recv() call. - poll_status = co_await client.poll(coro::poll_op::read); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error. - } - - // Prepare a buffer and recv() the client's message. This function returns the recv() status - // as well as a span that overlaps the given buffer for the bytes that were read. This - // can be used to resize the buffer or work with the bytes without modifying the buffer at all. - std::string request(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status != coro::net::recv_status::ok) - { - co_return; // Handle error, see net::recv_status for detailed error states. - } - - request.resize(recv_bytes.size()); - std::cout << "server: " << request << "\n"; - - // Make sure the client socket can be written to. - poll_status = co_await client.poll(coro::poll_op::write); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error. - } - - // Send the server response to the client. - // This message is small enough that it will be sent in a single send() call, but to demonstrate - // how to use the 'remaining' portion of the send() result this is wrapped in a loop until - // all the bytes are sent. - std::string response = "Hello from server."; - std::span remaining = response; - do - { - // Optimistically send() prior to polling. - auto [send_status, r] = client.send(remaining); - if (send_status != coro::net::send_status::ok) - { - co_return; // Handle error, see net::send_status for detailed error states. - } - - if (r.empty()) - { - break; // The entire message has been sent. - } - - // Re-assign remaining bytes for the next loop iteration and poll for the socket to be - // able to be written to again. - remaining = r; - auto pstatus = co_await client.poll(coro::poll_op::write); - if (pstatus != coro::poll_status::event) - { - co_return; // Handle error. - } - } while (true); - - co_return; - }; - - auto make_client_task = [&]() -> coro::task { - // Immediately schedule onto the scheduler. - co_await scheduler->schedule(); - - // Create the tcp_client with the default settings, see tcp_client for how to set the - // ip address, port, and optionally enabling SSL/TLS. - coro::net::tcp_client client{scheduler}; - - // Ommitting error checking code for the client, each step should check the status and - // verify the number of bytes sent or received. - - // Connect to the server. - co_await client.connect(); - - // Send the request data. - client.send(std::string_view{"Hello from client."}); - - // Wait for the response an receive it. - co_await client.poll(coro::poll_op::read); - std::string response(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(response); - response.resize(recv_bytes.size()); - - std::cout << "client: " << response << "\n"; - co_return; - }; - - // Create and wait for the server and client tasks to complete. - coro::sync_wait(coro::when_all(make_server_task(), make_client_task())); -} ``` Example output: @@ -875,88 +280,7 @@ io_scheduler::process event thread stop All tasks that are stored within a `coro::task_container` must have a `void` return type since their result cannot be accessed due to the task's lifetime being indeterminate. ```C++ -#include -#include -int main() -{ - auto scheduler = std::make_shared( - coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}); - - auto make_server_task = [&]() -> coro::task { - // This is the task that will handle processing a client's requests. - auto serve_client = [](coro::net::tcp_client client) -> coro::task { - size_t requests{1}; - - while (true) - { - // Continue to accept more requests until the client closes the connection. - co_await client.poll(coro::poll_op::read); - - std::string request(64, '\0'); - auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status == coro::net::recv_status::closed) - { - break; - } - - request.resize(recv_bytes.size()); - std::cout << "server: " << request << "\n"; - - auto response = "Hello from server " + std::to_string(requests); - client.send(response); - - ++requests; - } - - co_return; - }; - - // Spin up the tcp_server and schedule it onto the io_scheduler. - coro::net::tcp_server server{scheduler}; - co_await scheduler->schedule(); - - // All incoming connections will be stored into the task container until they are completed. - coro::task_container tc{scheduler}; - - // Wait for an incoming connection and accept it, this example will only use 1 connection. - co_await server.poll(); - auto client = server.accept(); - // Store the task that will serve the client into the container and immediately begin executing it - // on the task container's thread pool, which is the same as the scheduler. - tc.start(serve_client(std::move(client))); - - // Wait for all clients to complete before shutting down the tcp_server. - co_await tc.garbage_collect_and_yield_until_empty(); - co_return; - }; - - auto make_client_task = [&](size_t request_count) -> coro::task { - co_await scheduler->schedule(); - coro::net::tcp_client client{scheduler}; - - co_await client.connect(); - - // Send N requests on the same connection and wait for the server response to each one. - for (size_t i = 1; i <= request_count; ++i) - { - // Send the request data. - auto request = "Hello from client " + std::to_string(i); - client.send(request); - - co_await client.poll(coro::poll_op::read); - std::string response(64, '\0'); - auto [recv_status, recv_bytes] = client.recv(response); - response.resize(recv_bytes.size()); - - std::cout << "client: " << response << "\n"; - } - - co_return; // Upon exiting the tcp_client will close its connection to the server. - }; - - coro::sync_wait(coro::when_all(make_server_task(), make_client_task(5))); -} ``` ```bash diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 0cd343d..0000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(libcoro_examples) - -add_executable(coro_task coro_task.cpp) -target_compile_features(coro_task PUBLIC cxx_std_20) -target_link_libraries(coro_task PUBLIC libcoro) - -add_executable(coro_generator coro_generator.cpp) -target_compile_features(coro_generator PUBLIC cxx_std_20) -target_link_libraries(coro_generator PUBLIC libcoro) - -add_executable(coro_event coro_event.cpp) -target_compile_features(coro_event PUBLIC cxx_std_20) -target_link_libraries(coro_event PUBLIC libcoro) - -add_executable(coro_latch coro_latch.cpp) -target_compile_features(coro_latch PUBLIC cxx_std_20) -target_link_libraries(coro_latch PUBLIC libcoro) - -add_executable(coro_mutex coro_mutex.cpp) -target_compile_features(coro_mutex PUBLIC cxx_std_20) -target_link_libraries(coro_mutex PUBLIC libcoro) - -add_executable(coro_thread_pool coro_thread_pool.cpp) -target_compile_features(coro_thread_pool PUBLIC cxx_std_20) -target_link_libraries(coro_thread_pool PUBLIC libcoro) - -add_executable(coro_io_scheduler coro_io_scheduler.cpp) -target_compile_features(coro_io_scheduler PUBLIC cxx_std_20) -target_link_libraries(coro_io_scheduler PUBLIC libcoro) - -add_executable(coro_task_container coro_task_container.cpp) -target_compile_features(coro_task_container PUBLIC cxx_std_20) -target_link_libraries(coro_task_container PUBLIC libcoro) - -add_executable(coro_semaphore coro_semaphore.cpp) -target_compile_features(coro_semaphore PUBLIC cxx_std_20) -target_link_libraries(coro_semaphore PUBLIC libcoro) - -add_executable(coro_ring_buffer coro_ring_buffer.cpp) -target_compile_features(coro_ring_buffer PUBLIC cxx_std_20) -target_link_libraries(coro_ring_buffer PUBLIC libcoro) - -add_executable(coro_shared_mutex coro_shared_mutex.cpp) -target_compile_features(coro_shared_mutex PUBLIC cxx_std_20) -target_link_libraries(coro_shared_mutex PUBLIC libcoro) - -if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") - target_compile_options(coro_task PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_generator PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_event PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_latch PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_mutex PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_thread_pool PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_io_scheduler PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_task_container PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_semaphore PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_ring_buffer PUBLIC -fcoroutines -Wall -Wextra -pipe) - target_compile_options(coro_shared_mutex PUBLIC -fcoroutines -Wall -Wextra -pipe) -elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - message(FATAL_ERROR "Clang is currently not supported.") -else() - message(FATAL_ERROR "Unsupported compiler.") -endif() \ No newline at end of file diff --git a/examples/coro_event.cpp b/examples/coro_event.cpp deleted file mode 100644 index 4469294..0000000 --- a/examples/coro_event.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include - -int main() -{ - coro::event e; - - // These tasks will wait until the given event has been set before advancing. - auto make_wait_task = [](const coro::event& e, uint64_t i) -> coro::task { - std::cout << "task " << i << " is waiting on the event...\n"; - co_await e; - std::cout << "task " << i << " event triggered, now resuming.\n"; - co_return; - }; - - // This task will trigger the event allowing all waiting tasks to proceed. - auto make_set_task = [](coro::event& e) -> coro::task { - std::cout << "set task is triggering the event\n"; - e.set(); - co_return; - }; - - // Given more than a single task to synchronously wait on, use when_all() to execute all the - // tasks concurrently on this thread and then sync_wait() for them all to complete. - coro::sync_wait(coro::when_all(make_wait_task(e, 1), make_wait_task(e, 2), make_wait_task(e, 3), make_set_task(e))); -} diff --git a/examples/coro_generator.cpp b/examples/coro_generator.cpp deleted file mode 100644 index c006b90..0000000 --- a/examples/coro_generator.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -int main() -{ - auto task = [](uint64_t count_to) -> coro::task { - // Create a generator function that will yield and incrementing - // number each time its called. - auto gen = []() -> coro::generator { - uint64_t i = 0; - while (true) - { - co_yield i++; - } - }; - - // Generate the next number until its greater than count to. - for (auto val : gen()) - { - std::cout << val << ", "; - - if (val >= count_to) - { - break; - } - } - co_return; - }; - - coro::sync_wait(task(100)); -} diff --git a/examples/coro_io_scheduler.cpp b/examples/coro_io_scheduler.cpp deleted file mode 100644 index 4d45e2b..0000000 --- a/examples/coro_io_scheduler.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include - -int main() -{ - auto scheduler = std::make_shared(coro::io_scheduler::options{ - // The scheduler will spawn a dedicated event processing thread. This is the default, but - // it is possible to use 'manual' and call 'process_events()' to drive the scheduler yourself. - .thread_strategy = coro::io_scheduler::thread_strategy_t::spawn, - // If the scheduler is in spawn mode this functor is called upon starting the dedicated - // event processor thread. - .on_io_thread_start_functor = [] { std::cout << "io_scheduler::process event thread start\n"; }, - // If the scheduler is in spawn mode this functor is called upon stopping the dedicated - // event process thread. - .on_io_thread_stop_functor = [] { std::cout << "io_scheduler::process event thread stop\n"; }, - // The io scheduler uses a coro::thread_pool to process the events or tasks it is given. - // The tasks are not processed inline on the dedicated event processor thread so events can - // be received and handled as soon as a worker thread is available. See the coro::thread_pool - // for the available options and their descriptions. - .pool = - coro::thread_pool::options{ - .thread_count = 2, - .on_thread_start_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " starting\n"; }, - .on_thread_stop_functor = - [](size_t i) { std::cout << "io_scheduler::thread_pool worker " << i << " stopping\n"; }}}); - - auto make_server_task = [&]() -> coro::task { - // Start by creating a tcp server, we'll do this before putting it into the scheduler so - // it is immediately available for the client to connect since this will create a socket, - // bind the socket and start listening on that socket. See tcp_server for more details on - // how to specify the local address and port to bind to as well as enabling SSL/TLS. - coro::net::tcp_server server{scheduler}; - - // Now scheduler this task onto the scheduler. - co_await scheduler->schedule(); - - // Wait for an incoming connection and accept it. - auto poll_status = co_await server.poll(); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error, see poll_status for detailed error states. - } - - // Accept the incoming client connection. - auto client = server.accept(); - - // Verify the incoming connection was accepted correctly. - if (!client.socket().is_valid()) - { - co_return; // Handle error. - } - - // Now wait for the client message, this message is small enough it should always arrive - // with a single recv() call. - poll_status = co_await client.poll(coro::poll_op::read); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error. - } - - // Prepare a buffer and recv() the client's message. This function returns the recv() status - // as well as a span that overlaps the given buffer for the bytes that were read. This - // can be used to resize the buffer or work with the bytes without modifying the buffer at all. - std::string request(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status != coro::net::recv_status::ok) - { - co_return; // Handle error, see net::recv_status for detailed error states. - } - - request.resize(recv_bytes.size()); - std::cout << "server: " << request << "\n"; - - // Make sure the client socket can be written to. - poll_status = co_await client.poll(coro::poll_op::write); - if (poll_status != coro::poll_status::event) - { - co_return; // Handle error. - } - - // Send the server response to the client. - // This message is small enough that it will be sent in a single send() call, but to demonstrate - // how to use the 'remaining' portion of the send() result this is wrapped in a loop until - // all the bytes are sent. - std::string response = "Hello from server."; - std::span remaining = response; - do - { - // Optimistically send() prior to polling. - auto [send_status, r] = client.send(remaining); - if (send_status != coro::net::send_status::ok) - { - co_return; // Handle error, see net::send_status for detailed error states. - } - - if (r.empty()) - { - break; // The entire message has been sent. - } - - // Re-assign remaining bytes for the next loop iteration and poll for the socket to be - // able to be written to again. - remaining = r; - auto pstatus = co_await client.poll(coro::poll_op::write); - if (pstatus != coro::poll_status::event) - { - co_return; // Handle error. - } - } while (true); - - co_return; - }; - - auto make_client_task = [&]() -> coro::task { - // Immediately schedule onto the scheduler. - co_await scheduler->schedule(); - - // Create the tcp_client with the default settings, see tcp_client for how to set the - // ip address, port, and optionally enabling SSL/TLS. - coro::net::tcp_client client{scheduler}; - - // Ommitting error checking code for the client, each step should check the status and - // verify the number of bytes sent or received. - - // Connect to the server. - co_await client.connect(); - - // Send the request data. - client.send(std::string_view{"Hello from client."}); - - // Wait for the response an receive it. - co_await client.poll(coro::poll_op::read); - std::string response(256, '\0'); - auto [recv_status, recv_bytes] = client.recv(response); - response.resize(recv_bytes.size()); - - std::cout << "client: " << response << "\n"; - co_return; - }; - - // Create and wait for the server and client tasks to complete. - coro::sync_wait(coro::when_all(make_server_task(), make_client_task())); -} diff --git a/examples/coro_latch.cpp b/examples/coro_latch.cpp deleted file mode 100644 index 01fc899..0000000 --- a/examples/coro_latch.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include - -int main() -{ - // Complete worker tasks faster on a thread pool, using the io_scheduler version so the worker - // tasks can yield for a specific amount of time to mimic difficult work. The pool is only - // setup with a single thread to showcase yield_for(). - coro::io_scheduler tp{coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}}; - - // This task will wait until the given latch setters have completed. - auto make_latch_task = [](coro::latch& l) -> coro::task { - // It seems like the dependent worker tasks could be created here, but in that case it would - // be superior to simply do: `co_await coro::when_all(tasks);` - // It is also important to note that the last dependent task will resume the waiting latch - // task prior to actually completing -- thus the dependent task's frame could be destroyed - // by the latch task completing before it gets a chance to finish after calling resume() on - // the latch task! - - std::cout << "latch task is now waiting on all children tasks...\n"; - co_await l; - std::cout << "latch task dependency tasks completed, resuming.\n"; - co_return; - }; - - // This task does 'work' and counts down on the latch when completed. The final child task to - // complete will end up resuming the latch task when the latch's count reaches zero. - auto make_worker_task = [](coro::io_scheduler& tp, coro::latch& l, int64_t i) -> coro::task { - // Schedule the worker task onto the thread pool. - co_await tp.schedule(); - std::cout << "worker task " << i << " is working...\n"; - // Do some expensive calculations, yield to mimic work...! Its also important to never use - // std::this_thread::sleep_for() within the context of coroutines, it will block the thread - // and other tasks that are ready to execute will be blocked. - co_await tp.yield_for(std::chrono::milliseconds{i * 20}); - std::cout << "worker task " << i << " is done, counting down on the latch\n"; - l.count_down(); - co_return; - }; - - const int64_t num_tasks{5}; - coro::latch l{num_tasks}; - std::vector> tasks{}; - - // Make the latch task first so it correctly waits for all worker tasks to count down. - tasks.emplace_back(make_latch_task(l)); - for (int64_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_worker_task(tp, l, i)); - } - - // Wait for all tasks to complete. - coro::sync_wait(coro::when_all(std::move(tasks))); -} diff --git a/examples/coro_mutex.cpp b/examples/coro_mutex.cpp deleted file mode 100644 index ebfde63..0000000 --- a/examples/coro_mutex.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -int main() -{ - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 4}}; - std::vector output{}; - coro::mutex mutex; - - auto make_critical_section_task = [&](uint64_t i) -> coro::task { - co_await tp.schedule(); - // To acquire a mutex lock co_await its lock() function. Upon acquiring the lock the - // lock() function returns a coro::scoped_lock that holds the mutex and automatically - // unlocks the mutex upon destruction. This behaves just like std::scoped_lock. - { - auto scoped_lock = co_await mutex.lock(); - output.emplace_back(i); - } // <-- scoped lock unlocks the mutex here. - co_return; - }; - - const size_t num_tasks{100}; - std::vector> tasks{}; - tasks.reserve(num_tasks); - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_critical_section_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); - - // The output will be variable per run depending on how the tasks are picked up on the - // thread pool workers. - for (const auto& value : output) - { - std::cout << value << ", "; - } -} diff --git a/examples/coro_ring_buffer.cpp b/examples/coro_ring_buffer.cpp deleted file mode 100644 index f4f06bd..0000000 --- a/examples/coro_ring_buffer.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -int main() -{ - const size_t iterations = 100; - const size_t consumers = 4; - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 4}}; - coro::ring_buffer rb{}; - coro::mutex m{}; - - std::vector> tasks{}; - - auto make_producer_task = [&]() -> coro::task { - co_await tp.schedule(); - - for (size_t i = 1; i <= iterations; ++i) - { - co_await rb.produce(i); - } - - // Wait for the ring buffer to clear all items so its a clean stop. - while (!rb.empty()) - { - co_await tp.yield(); - } - - // Now that the ring buffer is empty signal to all the consumers its time to stop. Note that - // the stop signal works on producers as well, but this example only uses 1 producer. - { - auto scoped_lock = co_await m.lock(); - std::cerr << "\nproducer is sending stop signal"; - } - rb.stop_signal_notify_waiters(); - co_return; - }; - - auto make_consumer_task = [&](size_t id) -> coro::task { - co_await tp.schedule(); - - try - { - while (true) - { - auto value = co_await rb.consume(); - { - auto scoped_lock = co_await m.lock(); - std::cout << "(id=" << id << ", v=" << value << "), "; - } - - // Mimic doing some work on the consumed value. - co_await tp.yield(); - } - } - catch (const coro::stop_signal&) - { - auto scoped_lock = co_await m.lock(); - std::cerr << "\nconsumer " << id << " shutting down, stop signal received"; - } - - co_return; - }; - - // Create N consumers - for (size_t i = 0; i < consumers; ++i) - { - tasks.emplace_back(make_consumer_task(i)); - } - // Create 1 producer. - tasks.emplace_back(make_producer_task()); - - // Wait for all the values to be produced and consumed through the ring buffer. - coro::sync_wait(coro::when_all(std::move(tasks))); -} diff --git a/examples/coro_semaphore.cpp b/examples/coro_semaphore.cpp deleted file mode 100644 index e7a313f..0000000 --- a/examples/coro_semaphore.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include - -int main() -{ - // Have more threads/tasks than the semaphore will allow for at any given point in time. - coro::thread_pool tp{coro::thread_pool::options{.thread_count = 8}}; - coro::semaphore semaphore{2}; - - auto make_rate_limited_task = [&](uint64_t task_num) -> coro::task { - co_await tp.schedule(); - - // This will only allow 2 tasks through at any given point in time, all other tasks will - // await the resource to be available before proceeding. - co_await semaphore.acquire(); - std::cout << task_num << ", "; - semaphore.release(); - co_return; - }; - - const size_t num_tasks{100}; - std::vector> tasks{}; - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_rate_limited_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); -} diff --git a/examples/coro_shared_mutex.cpp b/examples/coro_shared_mutex.cpp deleted file mode 100644 index e3eda66..0000000 --- a/examples/coro_shared_mutex.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include - -int main() -{ - // Shared mutexes require an excutor type to be able to wake up multiple shared waiters when - // there is an exclusive lock holder releasing the lock. This example uses a single thread - // to also show the interleaving of coroutines acquiring the shared lock in shared and - // exclusive mode as they resume and suspend in a linear manner. Ideally the thread pool - // executor would have more than 1 thread to resume all shared waiters in parallel. - auto tp = std::make_shared(coro::thread_pool::options{.thread_count = 1}); - coro::shared_mutex mutex{tp}; - - auto make_shared_task = [&](uint64_t i) -> coro::task { - co_await tp->schedule(); - { - std::cerr << "shared task " << i << " lock_shared()\n"; - auto scoped_lock = co_await mutex.lock_shared(); - std::cerr << "shared task " << i << " lock_shared() acquired\n"; - /// Immediately yield so the other shared tasks also acquire in shared state - /// while this task currently holds the mutex in shared state. - co_await tp->yield(); - std::cerr << "shared task " << i << " unlock_shared()\n"; - } - co_return; - }; - - auto make_exclusive_task = [&]() -> coro::task { - co_await tp->schedule(); - - std::cerr << "exclusive task lock()\n"; - auto scoped_lock = co_await mutex.lock(); - std::cerr << "exclusive task lock() acquired\n"; - // Do the exclusive work.. - std::cerr << "exclusive task unlock()\n"; - co_return; - }; - - // Create 3 shared tasks that will acquire the mutex in a shared state. - const size_t num_tasks{3}; - std::vector> tasks{}; - for (size_t i = 1; i <= num_tasks; ++i) - { - tasks.emplace_back(make_shared_task(i)); - } - // Create an exclusive task. - tasks.emplace_back(make_exclusive_task()); - // Create 3 more shared tasks that will be blocked until the exclusive task completes. - for (size_t i = num_tasks + 1; i <= num_tasks * 2; ++i) - { - tasks.emplace_back(make_shared_task(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); -} diff --git a/examples/coro_task.cpp b/examples/coro_task.cpp deleted file mode 100644 index 38af00a..0000000 --- a/examples/coro_task.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include - -int main() -{ - // Task that takes a value and doubles it. - auto double_task = [](uint64_t x) -> coro::task { co_return x* x; }; - - // Create a task that awaits the doubling of its given value and - // then returns the result after adding 5. - auto double_and_add_5_task = [&](uint64_t input) -> coro::task { - auto doubled = co_await double_task(input); - co_return doubled + 5; - }; - - auto output = coro::sync_wait(double_and_add_5_task(2)); - std::cout << "Task1 output = " << output << "\n"; - - struct expensive_struct - { - std::string id{}; - std::vector records{}; - - expensive_struct() = default; - ~expensive_struct() = default; - - // Explicitly delete copy constructor and copy assign, force only moves! - // While the default move constructors will work for this struct the example - // inserts explicit print statements to show the task is moving the value - // out correctly. - expensive_struct(const expensive_struct&) = delete; - auto operator=(const expensive_struct&) -> expensive_struct& = delete; - - expensive_struct(expensive_struct&& other) : id(std::move(other.id)), records(std::move(other.records)) - { - std::cout << "expensive_struct() move constructor called\n"; - } - auto operator=(expensive_struct&& other) -> expensive_struct& - { - if (std::addressof(other) != this) - { - id = std::move(other.id); - records = std::move(other.records); - } - std::cout << "expensive_struct() move assignment called\n"; - return *this; - } - }; - - // Create a very large object and return it by moving the value so the - // contents do not have to be copied out. - auto move_output_task = []() -> coro::task { - expensive_struct data{}; - data.id = "12345678-1234-5678-9012-123456781234"; - for (size_t i = 10'000; i < 100'000; ++i) - { - data.records.emplace_back(std::to_string(i)); - } - - // Because the struct only has move contructors it will be forced to use - // them, no need to explicitly std::move(data). - co_return data; - }; - - auto data = coro::sync_wait(move_output_task()); - std::cout << data.id << " has " << data.records.size() << " records.\n"; - - // std::unique_ptr can also be used to return a larger object. - auto unique_ptr_task = []() -> coro::task> { co_return std::make_unique(42); }; - - auto answer_to_everything = coro::sync_wait(unique_ptr_task()); - if (answer_to_everything != nullptr) - { - std::cout << "Answer to everything = " << *answer_to_everything << "\n"; - } -} diff --git a/examples/coro_task_container.cpp b/examples/coro_task_container.cpp deleted file mode 100644 index 9085bc1..0000000 --- a/examples/coro_task_container.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include -#include - -int main() -{ - auto scheduler = std::make_shared( - coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}); - - auto make_server_task = [&]() -> coro::task { - // This is the task that will handle processing a client's requests. - auto serve_client = [](coro::net::tcp_client client) -> coro::task { - size_t requests{1}; - - while (true) - { - // Continue to accept more requests until the client closes the connection. - co_await client.poll(coro::poll_op::read); - - std::string request(64, '\0'); - auto [recv_status, recv_bytes] = client.recv(request); - if (recv_status == coro::net::recv_status::closed) - { - break; - } - - request.resize(recv_bytes.size()); - std::cout << "server: " << request << "\n"; - - auto response = "Hello from server " + std::to_string(requests); - client.send(response); - - ++requests; - } - - co_return; - }; - - // Spin up the tcp_server and schedule it onto the io_scheduler. - coro::net::tcp_server server{scheduler}; - co_await scheduler->schedule(); - - // All incoming connections will be stored into the task container until they are completed. - coro::task_container tc{scheduler}; - - // Wait for an incoming connection and accept it, this example will only use 1 connection. - co_await server.poll(); - auto client = server.accept(); - // Store the task that will serve the client into the container and immediately begin executing it - // on the task container's thread pool, which is the same as the scheduler. - tc.start(serve_client(std::move(client))); - - // Wait for all clients to complete before shutting down the tcp_server. - co_await tc.garbage_collect_and_yield_until_empty(); - co_return; - }; - - auto make_client_task = [&](size_t request_count) -> coro::task { - co_await scheduler->schedule(); - coro::net::tcp_client client{scheduler}; - - co_await client.connect(); - - // Send N requests on the same connection and wait for the server response to each one. - for (size_t i = 1; i <= request_count; ++i) - { - // Send the request data. - auto request = "Hello from client " + std::to_string(i); - client.send(request); - - co_await client.poll(coro::poll_op::read); - std::string response(64, '\0'); - auto [recv_status, recv_bytes] = client.recv(response); - response.resize(recv_bytes.size()); - - std::cout << "client: " << response << "\n"; - } - - co_return; // Upon exiting the tcp_client will close its connection to the server. - }; - - coro::sync_wait(coro::when_all(make_server_task(), make_client_task(5))); -} diff --git a/examples/coro_thread_pool.cpp b/examples/coro_thread_pool.cpp deleted file mode 100644 index 398ff48..0000000 --- a/examples/coro_thread_pool.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -int main() -{ - coro::thread_pool tp{coro::thread_pool::options{ - // By default all thread pools will create its thread count with the - // std::thread::hardware_concurrency() as the number of worker threads in the pool, - // but this can be changed via this thread_count option. This example will use 4. - .thread_count = 4, - // Upon starting each worker thread an optional lambda callback with the worker's - // index can be called to make thread changes, perhaps priority or change the thread's - // name. - .on_thread_start_functor = [](std::size_t worker_idx) -> void { - std::cout << "thread pool worker " << worker_idx << " is starting up.\n"; - }, - // Upon stopping each worker thread an optional lambda callback with the worker's - // index can b called. - .on_thread_stop_functor = [](std::size_t worker_idx) -> void { - std::cout << "thread pool worker " << worker_idx << " is shutting down.\n"; - }}}; - - auto offload_task = [&](uint64_t child_idx) -> coro::task { - // Start by scheduling this offload worker task onto the thread pool. - co_await tp.schedule(); - // Now any code below this schedule() line will be executed on one of the thread pools - // worker threads. - - // Mimic some expensive task that should be run on a background thread... - std::random_device rd; - std::mt19937 gen{rd()}; - std::uniform_int_distribution<> d{0, 1}; - - size_t calculation{0}; - for (size_t i = 0; i < 1'000'000; ++i) - { - calculation += d(gen); - - // Lets be nice and yield() to let other coroutines on the thread pool have some cpu - // time. This isn't necessary but is illustrated to show how tasks can cooperatively - // yield control at certain points of execution. Its important to never call the - // std::this_thread::sleep_for() within the context of a coroutine, that will block - // and other coroutines which are ready for execution from starting, always use yield() - // or within the context of a coro::io_scheduler you can use yield_for(amount). - if (i == 500'000) - { - std::cout << "Task " << child_idx << " is yielding()\n"; - co_await tp.yield(); - } - } - co_return calculation; - }; - - auto primary_task = [&]() -> coro::task { - const size_t num_children{10}; - std::vector> child_tasks{}; - child_tasks.reserve(num_children); - for (size_t i = 0; i < num_children; ++i) - { - child_tasks.emplace_back(offload_task(i)); - } - - // Wait for the thread pool workers to process all child tasks. - auto results = co_await coro::when_all(std::move(child_tasks)); - - // Sum up the results of the completed child tasks. - size_t calculation{0}; - for (const auto& task : results) - { - calculation += task.return_value(); - } - co_return calculation; - }; - - auto result = coro::sync_wait(primary_task()); - std::cout << "calculated thread pool result = " << result << "\n"; -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 91331e5..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(libcoro_test) - -set(LIBCORO_TEST_SOURCE_FILES - net/test_dns_resolver.cpp - net/test_ip_address.cpp - net/test_tcp_server.cpp - net/test_udp_peers.cpp - - bench.cpp - test_event.cpp - test_generator.cpp - test_io_scheduler.cpp - test_latch.cpp - test_mutex.cpp - test_ring_buffer.cpp - test_semaphore.cpp - test_shared_mutex.cpp - test_sync_wait.cpp - test_task.cpp - test_thread_pool.cpp - test_when_all.cpp -) - -add_executable(${PROJECT_NAME} main.cpp ${LIBCORO_TEST_SOURCE_FILES}) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(${PROJECT_NAME} PUBLIC libcoro) -target_compile_options(${PROJECT_NAME} PUBLIC -fcoroutines) - -if(LIBCORO_CODE_COVERAGE) - target_compile_options(${PROJECT_NAME} PRIVATE --coverage) - target_link_libraries(${PROJECT_NAME} PRIVATE gcov) -endif() - -if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") - target_compile_options(${PROJECT_NAME} PUBLIC -fcoroutines -Wall -Wextra -pipe) -elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - message(FATAL_ERROR "Clang is currently not supported.") -endif() - -add_test(NAME libcoro_tests COMMAND ${PROJECT_NAME}) \ No newline at end of file diff --git a/test/bench.cpp b/test/bench.cpp deleted file mode 100644 index a9ed775..0000000 --- a/test/bench.cpp +++ /dev/null @@ -1,735 +0,0 @@ -#include "catch.hpp" - -#include - -#include -#include -#include -#include - -using namespace std::chrono_literals; -using sc = std::chrono::steady_clock; - -constexpr std::size_t default_iterations = 5'000'000; - -static auto print_stats(const std::string& bench_name, uint64_t operations, sc::time_point start, sc::time_point stop) - -> void -{ - auto duration = std::chrono::duration_cast(stop - start); - auto ms = std::chrono::duration_cast(duration); - - std::cout << bench_name << "\n"; - std::cout << " " << operations << " ops in " << ms.count() << "ms\n"; - - double seconds = duration.count() / 1'000'000'000.0; - double ops_per_sec = static_cast(operations / seconds); - - std::cout << " ops/sec: " << std::fixed << ops_per_sec << "\n"; -} - -TEST_CASE("benchmark counter func direct call", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - std::atomic counter{0}; - auto func = [&]() -> void { - counter.fetch_add(1, std::memory_order::relaxed); - return; - }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - func(); - } - - print_stats("benchmark counter func direct call", iterations, start, sc::now()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark counter func coro::sync_wait(awaitable)", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - uint64_t counter{0}; - auto func = []() -> coro::task { co_return 1; }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - counter += coro::sync_wait(func()); - } - - print_stats("benchmark counter func coro::sync_wait(awaitable)", iterations, start, sc::now()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark counter func coro::sync_wait(coro::when_all(awaitable)) x10", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - uint64_t counter{0}; - auto f = []() -> coro::task { co_return 1; }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; i += 10) - { - auto tasks = coro::sync_wait(coro::when_all(f(), f(), f(), f(), f(), f(), f(), f(), f(), f())); - - std::apply([&counter](auto&&... t) { ((counter += t.return_value()), ...); }, tasks); - } - - print_stats("benchmark counter func coro::sync_wait(coro::when_all(awaitable))", iterations, start, sc::now()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark counter func coro::sync_wait(coro::when_all(vector)) x10", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - uint64_t counter{0}; - auto f = []() -> coro::task { co_return 1; }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; i += 10) - { - std::vector> tasks{}; - tasks.reserve(10); - for (size_t j = 0; j < 10; ++j) - { - tasks.emplace_back(f()); - } - - auto results = coro::sync_wait(coro::when_all(std::move(tasks))); - - for (const auto& r : results) - { - counter += r.return_value(); - } - } - - print_stats("benchmark counter func coro::sync_wait(coro::when_all(awaitable))", iterations, start, sc::now()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark thread_pool{1} counter task", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - - coro::thread_pool tp{coro::thread_pool::options{1}}; - std::atomic counter{0}; - - auto make_task = [](coro::thread_pool& tp, std::atomic& c) -> coro::task { - co_await tp.schedule(); - c.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - std::vector> tasks; - tasks.reserve(iterations); - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(make_task(tp, counter)); - tasks.back().resume(); - } - - // This will fail in valgrind since it runs in a single 'thread', and thus is shutsdown prior - // to any coroutine actually getting properly scheduled onto the background thread pool. - // Inject a sleep here so it forces a thread context switch within valgrind. - std::this_thread::sleep_for(std::chrono::milliseconds{10}); - tp.shutdown(); - - print_stats("benchmark thread_pool{1} counter task", iterations, start, sc::now()); - REQUIRE(counter == iterations); - REQUIRE(tp.empty()); -} - -TEST_CASE("benchmark thread_pool{2} counter task", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - - coro::thread_pool tp{coro::thread_pool::options{2}}; - std::atomic counter{0}; - - auto make_task = [](coro::thread_pool& tp, std::atomic& c) -> coro::task { - co_await tp.schedule(); - c.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - std::vector> tasks; - tasks.reserve(iterations); - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(make_task(tp, counter)); - tasks.back().resume(); - } - - // This will fail in valgrind since it runs in a single 'thread', and thus is shutsdown prior - // to any coroutine actually getting properly scheduled onto the background thread pool. - // Inject a sleep here so it forces a thread context switch within valgrind. - std::this_thread::sleep_for(std::chrono::milliseconds{10}); - tp.shutdown(); - - print_stats("benchmark thread_pool{2} counter task", iterations, start, sc::now()); - REQUIRE(counter == iterations); - REQUIRE(tp.empty()); -} - -TEST_CASE("benchmark thread_pool{N} counter task", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - - coro::thread_pool tp{}; - std::atomic counter{0}; - - auto make_task = [](coro::thread_pool& tp, std::atomic& c) -> coro::task { - co_await tp.schedule(); - c.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - std::vector> tasks; - tasks.reserve(iterations); - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(make_task(tp, counter)); - tasks.back().resume(); - } - - // This will fail in valgrind since it runs in a single 'thread', and thus is shutsdown prior - // to any coroutine actually getting properly scheduled onto the background thread pool. - // Inject a sleep here so it forces a thread context switch within valgrind. - std::this_thread::sleep_for(std::chrono::milliseconds{10}); - tp.shutdown(); - - print_stats("benchmark thread_pool{N} counter task", iterations, start, sc::now()); - REQUIRE(counter == iterations); - REQUIRE(tp.empty()); -} - -TEST_CASE("benchmark counter task scheduler{1} yield", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - constexpr std::size_t ops = iterations * 2; // the external resume is still a resume op - - coro::io_scheduler s{coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}}; - - std::atomic counter{0}; - std::vector> tasks{}; - tasks.reserve(iterations); - - auto make_task = [&]() -> coro::task { - co_await s.schedule(); - co_await s.yield(); - counter.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(make_task()); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); - - auto stop = sc::now(); - print_stats("benchmark counter task scheduler{1} yield", ops, start, stop); - REQUIRE(s.empty()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark counter task scheduler{1} yield_for", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - constexpr std::size_t ops = iterations * 2; // the external resume is still a resume op - - coro::io_scheduler s{coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}}; - - std::atomic counter{0}; - std::vector> tasks{}; - tasks.reserve(iterations); - - auto make_task = [&]() -> coro::task { - co_await s.schedule(); - co_await s.yield_for(std::chrono::milliseconds{1}); - counter.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(make_task()); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); - - auto stop = sc::now(); - print_stats("benchmark counter task scheduler{1} yield", ops, start, stop); - REQUIRE(s.empty()); - REQUIRE(counter == iterations); -} - -TEST_CASE("benchmark counter task scheduler await event from another coroutine", "[benchmark]") -{ - constexpr std::size_t iterations = default_iterations; - constexpr std::size_t ops = iterations * 3; // two tasks + event resume - - coro::io_scheduler s{coro::io_scheduler::options{.pool = coro::thread_pool::options{.thread_count = 1}}}; - - std::vector> events{}; - events.reserve(iterations); - for (std::size_t i = 0; i < iterations; ++i) - { - events.emplace_back(std::make_unique()); - } - - std::vector> tasks{}; - tasks.reserve(iterations * 2); // one for wait, one for resume - - std::atomic counter{0}; - - auto wait_func = [&](std::size_t index) -> coro::task { - co_await s.schedule(); - co_await* events[index]; - counter.fetch_add(1, std::memory_order::relaxed); - co_return; - }; - - auto resume_func = [&](std::size_t index) -> coro::task { - co_await s.schedule(); - events[index]->set(); - co_return; - }; - - auto start = sc::now(); - - for (std::size_t i = 0; i < iterations; ++i) - { - tasks.emplace_back(wait_func(i)); - tasks.emplace_back(resume_func(i)); - } - - coro::sync_wait(coro::when_all(std::move(tasks))); - - auto stop = sc::now(); - print_stats("benchmark counter task scheduler await event from another coroutine", ops, start, stop); - REQUIRE(counter == iterations); - - // valgrind workaround - while (!s.empty()) - { - std::this_thread::sleep_for(std::chrono::milliseconds{1}); - } - REQUIRE(s.empty()); -} - -TEST_CASE("benchmark tcp_server echo server thread pool", "[benchmark]") -{ - const constexpr std::size_t connections = 100; - const constexpr std::size_t messages_per_connection = 1'000; - const constexpr std::size_t ops = connections * messages_per_connection; - - const std::string msg = "im a data point in a stream of bytes"; - - const constexpr std::size_t server_count = 5; - const constexpr std::size_t client_count = 5; - - const constexpr std::size_t server_thread_count = 4; - const constexpr std::size_t client_thread_count = 4; - - std::atomic listening{0}; - std::atomic accepted{0}; - std::atomic clients_completed{0}; - - std::atomic server_id{0}; - - struct server - { - uint64_t id; - std::shared_ptr scheduler{std::make_shared(coro::io_scheduler::options{ - .pool = coro::thread_pool::options{.thread_count = server_thread_count}, - .execution_strategy = coro::io_scheduler::execution_strategy_t::process_tasks_on_thread_pool})}; - // coro::task_container task_container{scheduler}; - uint64_t live_clients{0}; - coro::event wait_for_clients{}; - }; - - struct client - { - std::shared_ptr scheduler{std::make_shared(coro::io_scheduler::options{ - .pool = coro::thread_pool::options{.thread_count = client_thread_count}, - .execution_strategy = coro::io_scheduler::execution_strategy_t::process_tasks_on_thread_pool})}; - std::vector> tasks{}; - }; - - auto make_on_connection_task = [&](server& s, coro::net::tcp_client client) -> coro::task { - std::string in(64, '\0'); - - // Echo the messages until the socket is closed. - while (true) - { - auto pstatus = co_await client.poll(coro::poll_op::read); - REQUIRE(pstatus == coro::poll_status::event); - - auto [rstatus, rspan] = client.recv(in); - if (rstatus == coro::net::recv_status::closed) - { - REQUIRE(rspan.empty()); - break; - } - REQUIRE(rstatus == coro::net::recv_status::ok); - - in.resize(rspan.size()); - - auto [sstatus, remaining] = client.send(in); - REQUIRE(sstatus == coro::net::send_status::ok); - REQUIRE(remaining.empty()); - } - - s.live_clients--; - if (s.live_clients == 0) - { - s.wait_for_clients.set(); - } - co_return; - }; - - auto make_server_task = [&](server& s) -> coro::task { - co_await s.scheduler->schedule(); - - coro::net::tcp_server server{s.scheduler}; - - listening++; - - while (accepted.load(std::memory_order::acquire) < connections) - { - auto pstatus = co_await server.poll(std::chrono::milliseconds{1}); - if (pstatus == coro::poll_status::event) - { - auto c = server.accept(); - if (c.socket().is_valid()) - { - accepted.fetch_add(1, std::memory_order::release); - - s.live_clients++; - s.scheduler->schedule(make_on_connection_task(s, std::move(c))); - // s.task_container.start(make_on_connection_task(s, std::move(c))); - } - } - } - - co_await s.wait_for_clients; - co_return; - }; - - std::mutex g_histogram_mutex; - std::map g_histogram; - - auto make_client_task = [&](client& c) -> coro::task { - co_await c.scheduler->schedule(); - std::map histogram; - coro::net::tcp_client client{c.scheduler}; - - auto cstatus = co_await client.connect(); // std::chrono::seconds{1}); - REQUIRE(cstatus == coro::net::connect_status::connected); - - for (size_t i = 1; i <= messages_per_connection; ++i) - { - auto req_start = std::chrono::steady_clock::now(); - auto [sstatus, remaining] = client.send(msg); - REQUIRE(sstatus == coro::net::send_status::ok); - REQUIRE(remaining.empty()); - - auto pstatus = co_await client.poll(coro::poll_op::read); - REQUIRE(pstatus == coro::poll_status::event); - - std::string response(64, '\0'); - auto [rstatus, rspan] = client.recv(response); - REQUIRE(rstatus == coro::net::recv_status::ok); - REQUIRE(rspan.size() == msg.size()); - response.resize(rspan.size()); - REQUIRE(response == msg); - - auto req_stop = std::chrono::steady_clock::now(); - histogram[std::chrono::duration_cast(req_stop - req_start)]++; - } - - { - std::scoped_lock lk{g_histogram_mutex}; - for (auto [ms, count] : histogram) - { - g_histogram[ms] += count; - } - } - - clients_completed.fetch_add(1); - - co_return; - }; - - auto start = sc::now(); - - // Create the server to accept incoming tcp connections. - std::vector server_threads{}; - for (size_t i = 0; i < server_count; ++i) - { - server_threads.emplace_back(std::thread{[&]() { - server s{}; - s.id = server_id++; - coro::sync_wait(make_server_task(s)); - s.scheduler->shutdown(); - }}); - } - - // The server can take a small bit of time to start up, if we don't wait for it to notify then - // the first few connections can easily fail to connect causing this test to fail. - while (listening != server_count) - { - std::this_thread::sleep_for(std::chrono::milliseconds{1}); - } - - // Spawn N client connections across a set number of clients. - std::vector client_threads{}; - std::vector clients{}; - for (size_t i = 0; i < client_count; ++i) - { - client_threads.emplace_back(std::thread{[&]() { - client c{}; - for (size_t i = 0; i < connections / client_count; ++i) - { - c.tasks.emplace_back(make_client_task(c)); - } - coro::sync_wait(coro::when_all(std::move(c.tasks))); - c.scheduler->shutdown(); - }}); - } - - for (auto& ct : client_threads) - { - ct.join(); - } - - for (auto& st : server_threads) - { - st.join(); - } - - auto stop = sc::now(); - print_stats("benchmark tcp_client and tcp_server thread_pool", ops, start, stop); - - for (const auto& [ms, count] : g_histogram) - { - std::cerr << ms.count() << " : " << count << "\n"; - } -} - -TEST_CASE("benchmark tcp_server echo server inline", "[benchmark]") -{ - const constexpr std::size_t connections = 100; - const constexpr std::size_t messages_per_connection = 1'000; - const constexpr std::size_t ops = connections * messages_per_connection; - - const std::string msg = "im a data point in a stream of bytes"; - - const constexpr std::size_t server_count = 10; - const constexpr std::size_t client_count = 10; - - std::atomic listening{0}; - std::atomic accepted{0}; - std::atomic clients_completed{0}; - - std::atomic server_id{0}; - - using estrat = coro::io_scheduler::execution_strategy_t; - - struct server - { - uint64_t id; - std::shared_ptr scheduler{std::make_shared( - coro::io_scheduler::options{.execution_strategy = estrat::process_tasks_inline})}; - // coro::task_container task_container{scheduler}; - uint64_t live_clients{0}; - coro::event wait_for_clients{}; - }; - - struct client - { - std::shared_ptr scheduler{std::make_shared( - coro::io_scheduler::options{.execution_strategy = estrat::process_tasks_inline})}; - std::vector> tasks{}; - }; - - auto make_on_connection_task = [&](server& s, coro::net::tcp_client client) -> coro::task { - std::string in(64, '\0'); - - // Echo the messages until the socket is closed. - while (true) - { - auto pstatus = co_await client.poll(coro::poll_op::read); - REQUIRE(pstatus == coro::poll_status::event); - - auto [rstatus, rspan] = client.recv(in); - if (rstatus == coro::net::recv_status::closed) - { - REQUIRE(rspan.empty()); - break; - } - REQUIRE(rstatus == coro::net::recv_status::ok); - - in.resize(rspan.size()); - - auto [sstatus, remaining] = client.send(in); - REQUIRE(sstatus == coro::net::send_status::ok); - REQUIRE(remaining.empty()); - } - - s.live_clients--; - if (s.live_clients == 0) - { - s.wait_for_clients.set(); - } - co_return; - }; - - auto make_server_task = [&](server& s) -> coro::task { - co_await s.scheduler->schedule(); - - coro::net::tcp_server server{s.scheduler}; - - listening++; - - while (accepted.load(std::memory_order::acquire) < connections) - { - auto pstatus = co_await server.poll(std::chrono::milliseconds{1}); - if (pstatus == coro::poll_status::event) - { - auto c = server.accept(); - if (c.socket().is_valid()) - { - accepted.fetch_add(1, std::memory_order::release); - - s.live_clients++; - s.scheduler->schedule(make_on_connection_task(s, std::move(c))); - // s.task_container.start(make_on_connection_task(s, std::move(c))); - } - } - } - - co_await s.wait_for_clients; - co_return; - }; - - std::mutex g_histogram_mutex; - std::map g_histogram; - - auto make_client_task = [&](client& c) -> coro::task { - co_await c.scheduler->schedule(); - std::map histogram; - coro::net::tcp_client client{c.scheduler}; - - auto cstatus = co_await client.connect(); // std::chrono::seconds{1}); - REQUIRE(cstatus == coro::net::connect_status::connected); - - for (size_t i = 1; i <= messages_per_connection; ++i) - { - auto req_start = std::chrono::steady_clock::now(); - auto [sstatus, remaining] = client.send(msg); - REQUIRE(sstatus == coro::net::send_status::ok); - REQUIRE(remaining.empty()); - - auto pstatus = co_await client.poll(coro::poll_op::read); - REQUIRE(pstatus == coro::poll_status::event); - - std::string response(64, '\0'); - auto [rstatus, rspan] = client.recv(response); - REQUIRE(rstatus == coro::net::recv_status::ok); - REQUIRE(rspan.size() == msg.size()); - response.resize(rspan.size()); - REQUIRE(response == msg); - - auto req_stop = std::chrono::steady_clock::now(); - histogram[std::chrono::duration_cast(req_stop - req_start)]++; - } - - { - std::scoped_lock lk{g_histogram_mutex}; - for (auto [ms, count] : histogram) - { - g_histogram[ms] += count; - } - } - - clients_completed.fetch_add(1); - - co_return; - }; - - auto start = sc::now(); - - // Create the server to accept incoming tcp connections. - std::vector server_threads{}; - for (size_t i = 0; i < server_count; ++i) - { - server_threads.emplace_back(std::thread{[&]() { - server s{}; - s.id = server_id++; - coro::sync_wait(make_server_task(s)); - s.scheduler->shutdown(); - }}); - } - - // The server can take a small bit of time to start up, if we don't wait for it to notify then - // the first few connections can easily fail to connect causing this test to fail. - while (listening != server_count) - { - std::this_thread::sleep_for(std::chrono::milliseconds{1}); - } - - // Spawn N client connections across a set number of clients. - std::vector client_threads{}; - std::vector clients{}; - for (size_t i = 0; i < client_count; ++i) - { - client_threads.emplace_back(std::thread{[&]() { - client c{}; - for (size_t i = 0; i < connections / client_count; ++i) - { - c.tasks.emplace_back(make_client_task(c)); - } - coro::sync_wait(coro::when_all(std::move(c.tasks))); - c.scheduler->shutdown(); - }}); - } - - for (auto& ct : client_threads) - { - ct.join(); - } - - for (auto& st : server_threads) - { - st.join(); - } - - auto stop = sc::now(); - print_stats("benchmark tcp_client and tcp_server inline", ops, start, stop); - - for (const auto& [ms, count] : g_histogram) - { - std::cerr << ms.count() << " : " << count << "\n"; - } -} \ No newline at end of file diff --git a/test/catch.hpp b/test/catch.hpp deleted file mode 100644 index 6c1756a..0000000 --- a/test/catch.hpp +++ /dev/null @@ -1,17615 +0,0 @@ -/* - * Catch v2.11.1 - * Generated: 2019-12-28 21:22:11.930976 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2019 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 11 -#define CATCH_VERSION_PATCH 1 - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ - // Because REQUIREs trigger GCC's -Wparentheses, and because still - // supported version of g++ have only buggy support for _Pragmas, - // Wparentheses have to be suppressed globally. -# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -# endif -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -#ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -# define CATCH_CPP14_OR_GREATER -# endif - -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -#endif - -#if defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -// We have to avoid both ICC and Clang, because they try to mask themselves -// as gcc, and we want only GCC in this block -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) -#endif - -#if defined(__clang__) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -# endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) - -# if _MSC_VER >= 1900 // Visual Studio 2015 or newer -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -# endif - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(__clang__) // Handle Clang masquerading for msvc -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL -# endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE -#endif - -#if defined(__UCLIBC__) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) -# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE -#endif - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo( SourceLineInfo&& ) noexcept = default; - SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - - bool empty() const noexcept { return file[0] == '\0'; } - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool isThrowSafe( TestCase const& testCase, IConfig const& config ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include -#include - -namespace Catch { - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; - - private: - static constexpr char const* const s_empty = ""; - - char const* m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef( char const* rawChars ) noexcept; - - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - explicit operator std::string() const { - return std::string(m_start, m_size); - } - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } - - auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; - - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr( size_type start, size_type length ) const noexcept -> StringRef; - - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const*; - - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } - - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; - - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch - -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} - -// end catch_stringref.h -// start catch_preprocessor.hpp - - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template