{"id":17099963,"url":"https://github.com/gummif/coma","last_synced_at":"2025-03-23T18:27:55.210Z","repository":{"id":79100361,"uuid":"349444295","full_name":"gummif/coma","owner":"gummif","description":"Async concurrency primatives and synchronization for C++11 and later","archived":false,"fork":false,"pushed_at":"2021-05-18T23:06:57.000Z","size":155,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-29T00:47:12.127Z","etag":null,"topics":["asio","async","concurrency","condition-variable","executors","semaphore","synchronization"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gummif.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-19T14:06:23.000Z","updated_at":"2021-08-02T09:54:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"91a31373-f87e-4fa2-bc58-3ba6c951d530","html_url":"https://github.com/gummif/coma","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gummif%2Fcoma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gummif%2Fcoma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gummif%2Fcoma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gummif%2Fcoma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gummif","download_url":"https://codeload.github.com/gummif/coma/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245147730,"owners_count":20568560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asio","async","concurrency","condition-variable","executors","semaphore","synchronization"],"created_at":"2024-10-14T15:11:52.688Z","updated_at":"2025-03-23T18:27:55.181Z","avatar_url":"https://github.com/gummif.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# coma\n[![ubuntu linux](https://github.com/gummif/coma/actions/workflows/linux.yml/badge.svg)](https://github.com/gummif/coma/actions?query=workflow%3ALinux)\n[![windows](https://github.com/gummif/coma/actions/workflows/windows.yml/badge.svg)](https://github.com/gummif/coma/actions?query=workflow%3AWindows)\n\n\n- [Introduction](#introduction)\n- [Overview](#overview)\n- [Design](#design)\n- [Examples](#examples)\n- [Gotchas](#gotchas)\n- [Synopsis](#synopsis)\n- [Nomenclature](#nomenclature)\n\n# Introduction\n\nComa is a C++11 header-only library providing asynchronous concurrency primatives, based on the asynchronous model of [Boost.ASIO](https://www.boost.org/doc/libs/1_76_0/doc/html/boost_asio.html) (and proposed [standard executors](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0443r14.html)). Utilities include RAII guards for semaphores, async semaphores and async condition variables.\n\nComa depends on Boost.ASIO (executors, timers and utilities) and Boost.Beast (initiating function and composed operation utilities) and familiarity with executors and completion handlers/tokens is a prerequisite. Minimum supported Boost version is 1.72. An exception is made for the semaphore guards, which only depend on the standard library and can be used without bringing in Boost.\n\nTo integrate it into you project you can add `include` to your include directories, use `add_subdirectory(path/to/coma)` or download it automatically using [FetchContent](https://cmake.org/cmake/help/v3.11/module/FetchContent.html) from your CMake project, or use the Conan package manager (WIP).\n\nThe library provides:\n\n* `coma::async_semaphore` lightweight async semaphore, _not_ thread-safe, no additional synchronization, atomics or reference counting. With FIFO ordering of waiting tasks.\n* `coma::async_cond_var` lightweight async condition variable, _not_ thread-safe, no additional synchronization, atomics or reference counting. With FIFO ordering of waiting tasks and without spurious wakening.\n* `coma::async_cond_var_timed` lightweight async condition variable, _not_ thread-safe, with support for timed waits and cancellation. With FIFO ordering of waiting tasks. May experience spurious wakening.\n* `coma::acquire_guard` equivalent to `std::lock_guard` for semaphores using acquire/release instead of lock/unlock.\n* `coma::unique_acquire_guard` equivalent to `std::unique_lock` for semaphores using acquire/release instead of lock/unlock.\n\nWork in progress:\n* `coma::async_semaphore_timed` with timed functions and cancellation.\n* `coma::async_semaphore_timed_s` synchronized variant.\n* `coma::async_synchronized` thread-safe async wrapper of values through a strand (similar to proposed `std::synchronized_value`).\n\nComa is tested with:\n* GCC 10.2 (C++20, address sanitizer, coroutines, Boost 1.76)\n* GCC 10.2 (C++11, address sanitizer, Boost 1.76)\n* GCC 10.2 (C++11, address sanitizer, Boost 1.72)\n* Clang 11.0 (C++20, address sanitizer, Boost 1.76)\n* MSVC 16.9 (C++20, Boost 1.76).\n* MSVC 16.9 (C++17, Boost 1.76).\n\n## Overview\n\nThere are a few variant of semaphores and condition variables where there is a tradeoff between the guarantees the types make and completeness of the API versus performance.\n\n| Semaphore                 | Async | Thread-safe | Timeout |\n|---------------------------|-------|-------|------|\n| `std::counting_semaphore` | No | **Yes** | **Yes** |\n| `coma::async_semaphore` | **Yes** | No | No |\n| `coma::async_semaphore_timed` (WIP) | **Yes** | No | **Yes** |\n| `coma::async_semaphore_timed_s` (WIP) | **Yes** | **Yes** | **Yes** |\n\n| Condition variable        | Async | Thread-safe | Timeout | Cancellation | SW\\* |\n|---------------------------|-------|-------|------|------|------|\n| `std::conndition_variable` | No | **Yes** | **Yes** | No | **Yes** |\n| `std::conndition_variable_any` | No | **Yes** | **Yes** | **Yes** | **Yes** |\n| `coma::async_cond_var` | **Yes** | No | No | No | No |\n| `coma::async_cond_var_timed` | **Yes** | No | **Yes** | **Yes** | **Yes** |\n\n\\* SW = spurious wakeup\n\n## Examples\nMost of the examples use coroutines with `awaitable` for simplicity, but the library supports any valid completion token (such as callbacks). The examples will use the alias `net` for `boost::asio`.\n\nHello world:\n```c++\nint main()\n{\n    net::io_context ctx;\n    coma::async_cond_var cv{ctx.get_executor()};\n\n    bool done = false;\n    cv.async_wait([\u0026] { return done; },\n        [\u0026](boost::system::error_code ec) {\n            assert(!ec);\n            puts(\"Hello world!\");\n        });\n    net::post(ctx, [\u0026] {\n        done = true;\n        cv.notify_one();\n    });\n    ctx.run();\n}\n```\n\nLimiting number of resources in use with a semaphore:\n```c++\nnet::awaitable\u003cvoid\u003e\nhandle_connection(tcp_socket socket,\n                  coma::async_acquire_guard guard);\nnet::awaitable\u003cvoid\u003e listen(tcp_listener listener,\n                            coma::async_semaphore_timed_s sem)\n{\n    while (true)\n    {\n        co_await sem.async_acquire(net::use_awaitable);\n        coma::async_acquire_guard guard{sem, coma::adapt_acquire};\n\n        auto socket = listener.async_listen(net::use_awaitable);\n        // note: unstructured concurrency, that's why\n        // we need a synchronized semaphore and\n        // async_acquire_guard for this to be safe\n        net::co_spawn(co_await net::this_coro::executor,\n            handle_connection(std::move(socket), std::move(g)),\n            net::detached);\n    }\n}\n```\n\nUsing async semaphore as a lightweight async latch between two threads. This example spawns a new thread to execute some heavy task without blocking the current executor/execution context. Using `async_acquire_n` to synchronize the completion of multiple task is left as an excercise. \n\n```c++\n// execute f on in new thread without blocking current executor\ntemplate\u003cclass F, class R = decltype(f())\u003e\nauto co_spawn_thread(F f) -\u003e net::awaitable\u003cR\u003e\n{\n    coma::async_semaphore_s sem{co_await net::this_coro::executor, 0};\n    R ret;\n    std::exception_ptr e;\n    // if we use jthread then the shared state can\n    // live on the stack, otherwise we would need to\n    // store it in a shared_ptr\n    std::jthread t([\u0026]() noexcept {\n        // release at scope exit\n        coma::acquire_guard g{sem, coma::adapt_acquire};\n        try\n        {\n            ret = f();\n        }\n        catch (...)\n        {\n            e = std::current_exception();\n        }\n    });\n    \n    // non-blocking wait for thread to finish\n    co_await sem.async_acquire(net::use_awaitable);\n    if (e)\n        std::rethrow_exception(e);\n    co_return std::move(ret);\n}\n\nint blocking_get_answer()\n{\n    //simulate CPU bound or blocking operation, may throw\n    std::this_thread::sleep_for(1s);\n    return 42;\n}\n\nnet::awaitable\u003cint\u003e async_get_answer()\n{\n    return co_spawn_thread(\u0026blocking_get_answer);\n}\n```\n\nAn async queue is almost trivial to write with an async condition variable and coroutines.\n```c++\ntemplate\u003cclass T\u003e\nclass async_queue\n{\npublic:\n    using executor_type = typename coma::async_cond_var\u003c\u003e::executor_type;\n    explicit async_queue(const executor_type\u0026 ex) : cv{ex}\n    {}\n    net::awaitable\u003cT\u003e async_pop()\n    {\n        co_await cv.async_wait([\u0026] {\n            return !q.empty();\n        }, net::use_awaitable);\n        auto item = std::move(q.front());\n        q.pop_front();\n        co_return std::move(item);\n    }\n    void push(T item)\n    {\n        q.push_back(std::move(item));\n        cv.notify_one();\n    }\nprivate:\n    coma::async_cond_var\u003c\u003e cv;\n    std::queue\u003cT\u003e q;\n};\n```\n\nWe now make a thread-safe async queue (a go channel if you will) based on the above queue and `coma::async_synchronized` (WIP):\n```c++\ntemplate\u003cclass T\u003e\nclass async_queue_s\n{\npublic:\n    using executor_type = typename async_queue\u003cT\u003e::executor_type;\n    explicit async_queue_s(const executor_type\u0026 ex) : sq{ex, std::in_place, ex}\n    {}\n    net::awaitable\u003cT\u003e async_pop()\n    {\n        return sq.invoke([](auto\u0026 q) {\n            return q.async_pop();\n        }, net::use_awaitable);\n        // NOTE this is the non-coroutine \"awaitable backwarding\" variant of\n        // co_return co_await sq.invoke([](auto\u0026 q) -\u003e net::awaitable\u003cT\u003e {\n        //   co_return co_await q.async_pop(); });\n    }\n    net::awaitable\u003cvoid\u003e async_push(T item)\n    {\n        return sq.invoke([item = std::move(item)](auto\u0026 q) mutable\n        {\n            q.push(std::move(item));\n        }, net::use_awaitable);\n    }\nprivate:\n    coma::async_synchronized\u003casync_queue\u003cT\u003e\u003e sq;\n};\n```\n\n## Design\n\nThe non-thread-safe/unsynchronized types are designed efficient use in single threaded (or externally synchronized context e.g. via a strand). If you are unsure which variant to use in your program then the synchronized variants (`*_s`) are a good choise (since they are both thread-safe and atomically reference counted). Also note that the semaphore guards can be dangerous if used ina a non-structured concurrency manner (see Gotchas section below). Be espepecially careful when using `detached` or `execute`/`post` without reference counting.\n\n## Gotchas\n\nThere are many ways to shoot yourself in the foot with the unsynchronized variants:\n```c++\nvoid BAD()\n{\n    net::io_context ctx;\n    coma::async_semaphore sem{ctx.get_executor(), 1};\n    net::co_spawn(ctx, [\u0026]() -\u003e net::awaitable\u003cvoid\u003e\n    {\n        co_await sem.async_acquire(net::use_awaitabke);\n        coma::acquire_guard g{sem, coma::adapt_lock};\n        co_await some_async_op();\n    }, net::detached);\n    // ... setup to stop ctx on interrupt\n    ctx.run();\n    // sem may be destructed before the destructor\n    // for the spawned task runs resulting in\n    // use after free in ~acquire_guard()\n}\n\nvoid OK()\n{\n    net::io_context ctx;\n    net::co_spawn(ctx, []() -\u003e net::awaitable\u003cvoid\u003e\n    {\n        // OK structured concurrency within this task\n        coma::async_semaphore sem{ctx.get_executor(), 1};\n        co_await sem.async_acquire(net::use_awaitabke);\n        coma::acquire_guard g{sem, coma::adapt_lock};\n        co_await some_async_op();\n    }, net::detached);\n    // ... setup to stop ctx on interrupt\n    ctx.run();\n}\n```\n\n## Synopsis\n\nIn header `\u003ccoma/async_semaphore.hpp\u003e`\n```c++\ntemplate\u003cclass Executor\u003e\nclass async_semaphore;\n```\n\nIn header `\u003ccoma/async_cond_var.hpp\u003e`\n```c++\ntemplate\u003cclass Executor\u003e\nclass async_cond_var;\n```\n\nIn header `\u003ccoma/async_cond_var_timed.hpp\u003e`\n```c++\ntemplate\u003cclass Executor\u003e\nclass async_cond_var_timed;\n```\n\nIn header `\u003ccoma/semaphore_guards.hpp\u003e`\n```c++\ntemplate\u003cclass Semaphore\u003e\nclass acquire_guard;\n\ntemplate\u003cclass Semaphore\u003e\nclass unique_acquire_guard;\n```\n\n## Nomenclature\n\nWe can try to classify functions that do multi-thread or multi-task synchronization into:\n\n* *Asynchronous*: Possibly deferred function continuation. Can be\n    * waiting (`awaitable\u003cvoid\u003e async_lock()`)\n    * non-waiting  (`awaitable\u003cbool\u003e async_try_lock()`)\n* *Synchronous*: Inline function completion. Can be\n    * blocking (`void lock()`) or \n    * non-blocking (`bool try_lock()`).\n\nAsync vs sync in general says nothing of the thread-safety of a function. Functions in this library should be considered non-thread-safe unless specified otherwise. Classes with a `_s` suffix provide a thread-safe API.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgummif%2Fcoma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgummif%2Fcoma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgummif%2Fcoma/lists"}