├── docs ├── en-us │ ├── src │ │ └── SUMMARY.md │ └── book.toml └── zh-cn │ ├── book.toml │ └── src │ ├── SUMMARY.md │ ├── 快速入门.md │ ├── advance │ ├── advance.md │ ├── panic.md │ ├── join_set.md │ ├── context.md │ ├── daemon.md │ └── assert.md │ ├── sync │ ├── 同步原语.md │ ├── notify.md │ ├── semaphore.md │ ├── rwlock.md │ ├── mutex.md │ └── channel.md │ ├── generator.md │ └── future.md ├── .gitignore ├── asco ├── future.cpp ├── utils │ ├── defines.h │ ├── types.h │ ├── erased.h │ ├── concepts.h │ └── memory_slot.h ├── assert.cpp ├── sync │ ├── notify.cpp │ ├── notify.h │ ├── spin.h │ ├── mutex.h │ ├── semaphore.h │ ├── channel.h │ └── rwspin.h ├── panic │ ├── unwind.h │ ├── color.h │ ├── panic.h │ ├── panic.cpp │ └── unwind.cpp ├── asco_main.cpp ├── core │ ├── time │ │ ├── timer.cpp │ │ ├── timer_concept.h │ │ ├── high_resolution_timer.h │ │ ├── timer.h │ │ └── high_resolution_timer.cpp │ ├── wait_queue.h │ ├── daemon.h │ ├── wait_queue.cpp │ ├── pool_allocator.h │ ├── daemon.cpp │ ├── worker.h │ ├── runtime.h │ ├── task.h │ ├── worker.cpp │ └── runtime.cpp ├── assert.h ├── yield.h ├── time │ ├── interval.cpp │ ├── interval.h │ └── sleep.h ├── context.cpp ├── invoke.h ├── concurrency │ ├── queue.h │ └── concurrency.h ├── context.h ├── join_set.h ├── compile_time │ └── platform.h ├── CMakeLists.txt └── generator.h ├── .gitmodules ├── tests ├── time │ ├── test_interval.cpp │ └── test_sleep.cpp ├── future_base.cpp ├── CMakeLists.txt ├── test_generator.cpp ├── test_join_set.cpp ├── sync │ ├── test_notify.cpp │ ├── test_channel.cpp │ ├── test_mutex.cpp │ └── test_semaphore.cpp └── context │ └── test_context.cpp ├── cmake └── ascoConfig.cmake.in ├── LICENSE.md ├── CONTRIBUTING.md ├── .github ├── workflows │ ├── mdbook.yaml │ └── ci.yml └── build │ └── arch-llvm │ └── Dockerfile ├── .clang-format ├── README.md └── CMakeLists.txt /docs/en-us/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.idea 3 | /.cache 4 | /build 5 | callgrind.out.* 6 | core.* 7 | book 8 | perf.data 9 | -------------------------------------------------------------------------------- /asco/future.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/cpptrace"] 2 | path = deps/cpptrace 3 | url = https://github.com/jeremy-rifkin/cpptrace.git 4 | branch = v1.0.4 5 | -------------------------------------------------------------------------------- /asco/utils/defines.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | // Yes, do NOT use #pragma once 5 | 6 | #ifdef __linux__ 7 | # undef linux 8 | #endif 9 | 10 | #define with if 11 | -------------------------------------------------------------------------------- /docs/zh-cn/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["pointertobios"] 3 | language = "zh" 4 | src = "src" 5 | title = "ASCO协程异步框架" 6 | 7 | [output.html] 8 | smart-punctuation = true 9 | mathjax-support = true 10 | git-repository-url = "https://github.com/pointertobios/asco" 11 | hash-files = true 12 | -------------------------------------------------------------------------------- /docs/en-us/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["pointertobios"] 3 | language = "en" 4 | src = "src" 5 | title = "ASCO Coroutine Async Framework" 6 | 7 | [output.html] 8 | smart-punctuation = true 9 | mathjax-support = true 10 | git-repository-url = "https://github.com/pointertobios/asco" 11 | hash-files = true 12 | -------------------------------------------------------------------------------- /asco/assert.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | namespace asco { 7 | 8 | [[noreturn]] void assert_failed(std::string_view expr) { panic::co_panic("Assertion failed on {}", expr); } 9 | 10 | [[noreturn]] void assert_failed(std::string_view expr, std::string_view hint) { 11 | panic::co_panic("Assertion failed on {}: {}", expr, hint); 12 | } 13 | 14 | }; // namespace asco 15 | -------------------------------------------------------------------------------- /tests/time/test_interval.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace asco; 10 | using namespace std::chrono_literals; 11 | 12 | future async_main() { 13 | interval iv{500ms}; 14 | for (int i = 0; i < 5; ++i) { 15 | co_await iv.tick(); 16 | std::println("Tick {}", i + 1); 17 | } 18 | co_return 0; 19 | } 20 | -------------------------------------------------------------------------------- /asco/sync/notify.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include "asco/yield.h" 5 | #include 6 | 7 | namespace asco::sync { 8 | 9 | yield<> notify::wait() { return static_cast>(core::wait_queue::wait()); } 10 | 11 | void notify::notify_one() { core::wait_queue::notify(1, false); } 12 | 13 | void notify::notify_all() { core::wait_queue::notify(std::numeric_limits::max(), false); } 14 | 15 | }; // namespace asco::sync 16 | -------------------------------------------------------------------------------- /asco/panic/unwind.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace asco::panic { 11 | 12 | using coroutine_trace_handle = cpptrace::stacktrace_frame; 13 | 14 | std::string unwind(size_t skip = 0, bool color = true); 15 | 16 | std::string co_unwind(size_t skip = 0, bool color = true); 17 | 18 | cpptrace::stacktrace co_stacktrace(size_t skip = 0); 19 | 20 | }; // namespace asco::panic 21 | -------------------------------------------------------------------------------- /asco/sync/notify.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace asco::sync { 9 | 10 | class notify : private core::wait_queue { 11 | public: 12 | notify() = default; 13 | 14 | yield<> wait(); 15 | void notify_one(); 16 | void notify_all(); 17 | 18 | private: 19 | }; 20 | 21 | }; // namespace asco::sync 22 | 23 | namespace asco { 24 | 25 | using sync::notify; 26 | 27 | }; // namespace asco 28 | -------------------------------------------------------------------------------- /docs/zh-cn/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [快速入门](./快速入门.md) 4 | - [`future`下的异步编程](./future.md) 5 | - [`generator` 数据流](./generator.md) 6 | - [同步原语](./sync/同步原语.md) 7 | - [Mutex](./sync/mutex.md) 8 | - [RWLock](./sync/rwlock.md) 9 | - [Semaphore](./sync/semaphore.md) 10 | - [Notify](./sync/notify.md) 11 | - [Channel](./sync/channel.md) 12 | - [进阶](./advance/advance.md) 13 | - [Panic](./advance/panic.md) 14 | - [带有堆栈回溯的断言 (asco_assert)](./advance/assert.md) 15 | - [守护线程基类 `daemon`](./advance/daemon.md) 16 | - [Context 取消原语](./advance/context.md) 17 | - [Join Set 聚合器](./advance/join_set.md) 18 | -------------------------------------------------------------------------------- /asco/asco_main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | inline asco::runtime_initializer_t runtime_initializer; 7 | 8 | asco::core::runtime &_ = asco::core::runtime::init([] { 9 | if (runtime_initializer) { 10 | if (auto builder_func = *runtime_initializer; builder_func) { 11 | return builder_func(); 12 | } 13 | } 14 | return asco::core::runtime_builder{}; 15 | }()); 16 | 17 | #ifndef __ASCORT__ 18 | 19 | extern asco::future async_main(); 20 | 21 | int main() { return async_main().spawn().await(); } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /asco/panic/color.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | namespace asco::panic { 7 | 8 | inline const char *number_color{"\033[1;35m"}; 9 | inline const char *address_color{"\033[1;34m"}; 10 | inline const char *name_color{"\033[1;37m"}; 11 | inline const char *co_name_color{"\033[1;32m"}; 12 | inline const char *file_color{"\033[33m"}; 13 | inline const char *file_tail_color{"\033[1;33m"}; 14 | inline const char *lineno_color{"\033[1;34m"}; 15 | inline const char *reset_color{"\033[0m"}; 16 | inline const char *panic_color{"\033[1;31m"}; 17 | 18 | }; // namespace asco::panic 19 | -------------------------------------------------------------------------------- /tests/time/test_sleep.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace asco; 11 | using namespace std::chrono_literals; 12 | 13 | future async_main() { 14 | std::println("Sleeping for 1 second..."); 15 | co_await sleep_for(1s); 16 | std::println("Awake!"); 17 | std::println("Sleeping until 2 seconds from now..."); 18 | co_await sleep_until(std::chrono::high_resolution_clock::now() + 2s); 19 | std::println("Awake again!"); 20 | co_return 0; 21 | } 22 | -------------------------------------------------------------------------------- /docs/zh-cn/src/ 快速入门.md: -------------------------------------------------------------------------------- 1 | # 快速入门 2 | 3 | 本节将引导您快速入门 ASCO 库的使用。我们将通过一个简单的示例来展示如何使用 ASCO 创建和调度异步任务。 4 | 5 | ```c++ 6 | #include 7 | #include 8 | 9 | using asco::future, asco::future_spawn; 10 | 11 | future bar(int x) { 12 | std::println("running bar({})", x); 13 | co_return x; 14 | } 15 | 16 | future_spawn foo() { 17 | std::println("running foo()"); 18 | co_return; 19 | } 20 | 21 | future async_main() { 22 | auto f1 = foo(); 23 | std::println("foo()"); 24 | co_await f1; 25 | auto f2 = bar(42); 26 | std::println("bar(42)"); 27 | auto x = co_await f2; 28 | std::println("bar(42) = {}", x); 29 | co_return 0; 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/advance.md: -------------------------------------------------------------------------------- 1 | # 进阶(Advance)导读 2 | 3 | 本章面向已经熟悉 ASCO 基本概念(例如 `future`、协程、以及同步原语)的读者,介绍更贴近运行时实现与系统集成的高级主题。目标是帮助你: 4 | 5 | - 理解框架内部的守护线程与运行时组件如何协作; 6 | - 学会扩展和定制守护/后台服务; 7 | - 了解不可恢复错误(panic)与断言(assert)的处理与集成方案; 8 | - 获取与生产环境相关的实践建议(生命周期、停止策略、日志与崩溃处理)。 9 | 10 | 当前本章包含的主题(将持续扩展): 11 | 12 | - [Panic](./panic.md) — ASCO 的 panic 机制,支持协程栈回溯与自定义回调,适用于不可恢复的致命错误处理。 13 | - [带有堆栈回溯的断言 (asco_assert)](./assert.md) — 基于 panic 框架实现的断言宏,支持表达式定位与失败处理集成。 14 | - [守护线程基类 `daemon`](./daemon.md) — 一个用于实现后台服务/周期性任务的轻量基类,封装了线程生命周期、初始化同步与唤醒等待逻辑。 15 | 16 | 交互与实践建议: 17 | 18 | - 若你希望把守护线程与任务队列、channel 或 worker pool 集成,本章后续会给出示例;欢迎在仓库 Issue 中提出常见场景。 19 | - 本章不直接覆盖 `future` 或协程语义的基础内容;若需要可在“快速入门”或“future”章节补充链接。 20 | -------------------------------------------------------------------------------- /asco/core/time/timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace asco::core::time { 9 | 10 | timer_id timer_entry::gen_id( 11 | const std::chrono::high_resolution_clock::time_point &expire_time, const worker &worker_ptr, 12 | task_id tid) noexcept { 13 | auto expire = 14 | std::chrono::duration_cast(expire_time.time_since_epoch()).count(); 15 | auto meta = std::hash{}(&worker_ptr) ^ std::hash{}(tid); 16 | return {meta, static_cast(expire)}; 17 | } 18 | 19 | }; // namespace asco::core::time 20 | -------------------------------------------------------------------------------- /cmake/ascoConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | # Dependencies 6 | # Hint: if this config file is used from the build tree, prefer the vendored cpptrace in deps/ 7 | # This makes find_dependency(cpptrace ...) work without requiring consumers to set cpptrace_DIR. 8 | set(_ASCO_BUILD_CPPTRACE_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/deps/cpptrace/cmake") 9 | if(EXISTS "${_ASCO_BUILD_CPPTRACE_CMAKE_DIR}/cpptrace-config.cmake") 10 | # Provide a direct hint for config-mode find_package 11 | set(cpptrace_DIR "${_ASCO_BUILD_CPPTRACE_CMAKE_DIR}") 12 | endif() 13 | 14 | find_dependency(cpptrace CONFIG REQUIRED) 15 | 16 | # Exported targets 17 | include("${CMAKE_CURRENT_LIST_DIR}/ascoTargets.cmake") 18 | -------------------------------------------------------------------------------- /asco/assert.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace asco { 11 | 12 | [[noreturn]] void assert_failed(std::string_view expr); 13 | 14 | [[noreturn]] void assert_failed(std::string_view expr, std::string_view hint); 15 | 16 | }; // namespace asco 17 | 18 | #define asco_assert(expr, ...) \ 19 | do { \ 20 | if (!(expr)) [[unlikely]] { \ 21 | asco::assert_failed(#expr, ##__VA_ARGS__); \ 22 | } \ 23 | } while (0) 24 | -------------------------------------------------------------------------------- /asco/yield.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace asco { 11 | 12 | template 13 | struct yield; 14 | 15 | template<> 16 | struct yield : std::suspend_always {}; 17 | 18 | template 19 | struct yield : std::suspend_always { 20 | T value; 21 | 22 | yield(T &&v) noexcept 23 | : value{std::move(v)} {} 24 | 25 | T await_resume() { return std::move(value); } 26 | }; 27 | 28 | struct noop : yield<> { 29 | bool await_ready() noexcept { return true; } 30 | }; 31 | 32 | }; // namespace asco 33 | -------------------------------------------------------------------------------- /asco/core/wait_queue.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace asco::core { 14 | 15 | using namespace types; 16 | 17 | class wait_queue { 18 | public: 19 | wait_queue() = default; 20 | 21 | void notify(size_t n = 1, bool record_untriggered = true); 22 | 23 | private: 24 | spin<> mut; 25 | atomic_size_t untriggered_notifications{0}; 26 | 27 | protected: 28 | std::list> waiters; 29 | 30 | public: 31 | yield> wait(); 32 | void interrupt_wait(decltype(waiters)::iterator); 33 | }; 34 | 35 | }; // namespace asco::core 36 | -------------------------------------------------------------------------------- /asco/time/interval.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace asco::time { 13 | 14 | future interval::tick() { 15 | auto now = std::chrono::high_resolution_clock::now(); 16 | auto next_timepoint = last_timepoint + duration; 17 | if (now < next_timepoint) { 18 | auto &w = core::worker::this_worker(); 19 | auto tid = w.current_task(); 20 | core::runtime::this_runtime().timer().register_timer(next_timepoint, w, tid); 21 | w.suspend_task(tid); 22 | co_await yield<>{}; 23 | last_timepoint = next_timepoint; 24 | } 25 | co_return; 26 | } 27 | 28 | }; // namespace asco::time 29 | -------------------------------------------------------------------------------- /asco/time/interval.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace asco::time { 12 | 13 | using namespace concepts; 14 | 15 | class interval { 16 | public: 17 | template 18 | explicit interval(const Dur &duration) 19 | : duration{std::chrono::duration_cast(duration)} 20 | , last_timepoint{std::chrono::high_resolution_clock::now()} {} 21 | 22 | future tick(); 23 | 24 | private: 25 | const std::chrono::nanoseconds duration; 26 | std::chrono::high_resolution_clock::time_point last_timepoint; 27 | }; 28 | 29 | }; // namespace asco::time 30 | 31 | namespace asco { 32 | 33 | using interval = time::interval; 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /asco/panic/panic.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace asco::panic { 13 | 14 | [[noreturn]] void panic(std::string msg) noexcept; 15 | 16 | [[noreturn]] void co_panic(std::string msg) noexcept; 17 | 18 | template 19 | [[noreturn]] void panic(std::format_string fmt, Args &&...args) { 20 | panic(std::vformat(fmt.get(), std::make_format_args(args...))); 21 | } 22 | 23 | template 24 | [[noreturn]] void co_panic(std::format_string fmt, Args &&...args) { 25 | co_panic(std::vformat(fmt.get(), std::make_format_args(args...))); 26 | } 27 | 28 | void register_callback(std::function cb); 29 | 30 | }; // namespace asco::panic 31 | -------------------------------------------------------------------------------- /asco/utils/types.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace asco::types { 14 | 15 | using uint8_t = std::uint8_t; 16 | using size_t = std::size_t; 17 | using uint128_t = __uint128_t; 18 | 19 | using morder = std::memory_order; 20 | 21 | template 22 | using atomic = std::atomic; 23 | 24 | using atomic_bool = std::atomic_bool; 25 | using atomic_uint8_t = std::atomic_uint8_t; 26 | using atomic_size_t = std::atomic_size_t; 27 | 28 | template 29 | using monostate_if_void = std::conditional_t, std::monostate, T>; 30 | 31 | struct type_erase {}; 32 | 33 | template 34 | using passing = std::conditional_t, T, T &&>; 35 | 36 | }; // namespace asco::types 37 | -------------------------------------------------------------------------------- /tests/future_base.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace asco; 11 | 12 | inline asco::runtime_initializer_t runtime_initializer{[] { 13 | std::println("Custom runtime initializer called."); 14 | return asco::core::runtime_builder{}; 15 | }}; 16 | 17 | future_spawn wtf() { 18 | for (int i = 0; i < 10; ++i) { 19 | asco::concurrency::withdraw<100000>(); 20 | std::println("In wtf()"); 21 | } 22 | co_return; 23 | } 24 | 25 | future foo() { 26 | co_await wtf(); 27 | co_return 42; 28 | } 29 | 30 | future_spawn bar() { 31 | std::println("In bar()"); 32 | co_return; 33 | } 34 | 35 | future async_main() { 36 | std::println("Hello, World! {}", co_await foo()); 37 | co_await bar(); 38 | std::println("Back to async_main()"); 39 | co_return 0; 40 | } 41 | -------------------------------------------------------------------------------- /asco/context.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | namespace asco::contexts { 7 | 8 | std::shared_ptr context::with_cancel() { 9 | return std::allocate_shared(core::mm::allocator(context::_allocator)); 10 | } 11 | 12 | future context::cancel() { 13 | _cancelled.store(true, morder::release); 14 | _notify.notify_all(); 15 | if (auto cb = co_await _cancel_callback.read(); *cb) { 16 | (*cb)(); 17 | } 18 | } 19 | 20 | bool context::is_cancelled() const noexcept { return _cancelled.load(morder::relaxed); } 21 | 22 | yield<> context::operator co_await() noexcept { 23 | if (!is_cancelled()) 24 | return _notify.wait(); 25 | else 26 | return yield<>{}; 27 | } 28 | 29 | yield<> operator co_await(const std::shared_ptr &ctx) noexcept { return ctx->operator co_await(); } 30 | 31 | future context::set_cancel_callback(std::function &&callback) { 32 | *(co_await _cancel_callback.write()) = std::move(callback); 33 | } 34 | 35 | }; // namespace asco::contexts 36 | -------------------------------------------------------------------------------- /asco/utils/erased.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace asco::utils { 9 | 10 | // No type security erased type wrapper 11 | class erased { 12 | public: 13 | template 14 | erased(T &&value, void (*deleter)(void *)) noexcept 15 | : align{alignof(T)} 16 | , storage(::operator new(sizeof(T), std::align_val_t{alignof(T)})) 17 | , deleter(deleter) { 18 | new (storage) T(std::move(value)); 19 | } 20 | 21 | template 22 | T &get() noexcept { 23 | return *reinterpret_cast(storage); 24 | } 25 | 26 | template 27 | const T &get() const noexcept { 28 | return *reinterpret_cast(storage); 29 | } 30 | 31 | ~erased() { 32 | if (deleter) { 33 | deleter(storage); 34 | } 35 | ::operator delete(storage, align); 36 | } 37 | 38 | private: 39 | std::align_val_t align; 40 | void *storage; 41 | void (*deleter)(void *); 42 | }; 43 | 44 | }; // namespace asco::utils 45 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/同步原语.md: -------------------------------------------------------------------------------- 1 | # 同步原语 2 | 3 | 本章节介绍 ASCO 提供的基础同步原语,包括互斥锁(Mutex)、读写锁(RWLock)、信号量(Semaphore)与通道(Channel)。它们均为多线程/多协程安全的构件,可与 `future`/`co_await` 无缝配合。 4 | 5 | ## 何时使用 6 | 7 | - Mutex:需要在多个协程/线程之间串行化访问临界区或受保护对象时使用,支持 `with (auto guard = co_await ...)` 的 RAII 写法。 8 | - RWLock:读多写独的场景,可允许大量读者并发访问,写者持锁期间阻塞全部读者与其它写者。 9 | - Semaphore:需要对共享资源进行计数式访问控制,或实现简单的事件/通知机制时使用。适合“一端释放(release),另一端等待(acquire)”的场景。 10 | - Channel:需要在任务之间传递数据(消息)时使用。基于无锁 MPMC 队列封装并配合信号量实现“就绪通知”,保证 FIFO 顺序,可多生产者/多消费者。 11 | 12 | ## 公共特性 13 | 14 | - 线程安全:在多线程/多协程环境下可安全调用。 15 | - 与协程兼容:阻塞等待均以 `co_await` 的方式暴露,不会阻塞线程。 16 | - 异常安全:在常见使用路径中不抛出异常(若违反约束,如在通道关闭后继续发送,将触发 panic)。 17 | 18 | ## 目录 19 | 20 | - [Mutex](./mutex.md):纯互斥锁及携带数据的互斥锁,推荐用法与注意事项。 21 | - [RWLock](./rwlock.md):协程友好的读写锁,支持封装对象的读写访问。 22 | - [Semaphore](./semaphore.md):计数信号量、二值信号量与不限量信号量,API 与使用示例。 23 | - [Channel](./channel.md):`sender`/`receiver`、`send/recv/try_recv/stop` 语义与注意事项。 24 | 25 | ## 典型组合用法 26 | 27 | - 使用 Channel 进行任务间消息传递;在消费者端如果需要批处理或节流,可结合计时器 `interval` 或 `sleep` 实现节奏控制。 28 | - 使用 Semaphore 表达“完成/可用”事件;多个生产者可安全地 `release(n)` 增加配额,多个消费者以协程方式 `co_await acquire()`。 29 | 30 | 更多协程与运行时的信息,请先阅读《[`future`下的异步编程](../future.md)》。 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2025` `pointer-to-bios ` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /asco/utils/concepts.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace asco::concepts { 10 | 11 | template 12 | concept move_secure = 13 | std::is_void_v || std::is_integral_v || std::is_floating_point_v || std::is_pointer_v 14 | || (std::is_move_constructible_v, std::monostate, T>> 15 | && std::is_move_assignable_v, std::monostate, T>>); 16 | 17 | template 18 | concept base_type = 19 | std::is_void_v || std::is_integral_v || std::is_floating_point_v || std::is_pointer_v; 20 | 21 | template 22 | concept is_void = std::is_void_v; 23 | 24 | template 25 | concept non_void = !std::is_void_v; 26 | 27 | template 28 | concept duration_type = std::is_same_v>; 29 | 30 | template 31 | concept time_point_type = 32 | std::is_same_v>; 33 | 34 | }; // namespace asco::concepts 35 | -------------------------------------------------------------------------------- /asco/core/time/timer_concept.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace asco::core::time { 13 | 14 | class timer_concept { 15 | public: 16 | template 17 | timer_concept(std::unique_ptr t) 18 | requires timer 19 | : timer{std::move(t)} {} 20 | 21 | timer_id register_timer( 22 | const std::chrono::high_resolution_clock::time_point &expire_time, worker &worker_ptr, 23 | task_id task_id) { 24 | return std::visit( 25 | [&expire_time, &worker_ptr, task_id](auto &t) { 26 | return t->register_timer(expire_time, worker_ptr, task_id); 27 | }, 28 | timer); 29 | } 30 | 31 | void unregister_timer(timer_id id) { 32 | std::visit([id](auto &t) { t->unregister_timer(id); }, timer); 33 | } 34 | 35 | private: 36 | template 37 | using up = std::unique_ptr; 38 | 39 | std::variant> timer; 40 | }; 41 | 42 | }; // namespace asco::core::time 43 | -------------------------------------------------------------------------------- /asco/time/sleep.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace asco::time { 13 | 14 | using namespace concepts; 15 | 16 | future sleep_for(const duration_type auto &duration) { 17 | auto &w = core::worker::this_worker(); 18 | auto tid = w.current_task(); 19 | auto expire_time = std::chrono::high_resolution_clock::now() 20 | + std::chrono::duration_cast(duration); 21 | core::runtime::this_runtime().timer().register_timer(expire_time, w, tid); 22 | w.suspend_task(tid); 23 | co_await yield<>{}; 24 | co_return; 25 | } 26 | 27 | future sleep_until(const time_point_type auto &timepoint) { 28 | auto &w = core::worker::this_worker(); 29 | auto tid = w.current_task(); 30 | core::runtime::this_runtime().timer().register_timer(timepoint, w, tid); 31 | w.suspend_task(tid); 32 | co_await yield<>{}; 33 | co_return; 34 | } 35 | 36 | }; // namespace asco::time 37 | 38 | namespace asco { 39 | 40 | using time::sleep_for; 41 | using time::sleep_until; 42 | 43 | }; // namespace asco 44 | -------------------------------------------------------------------------------- /asco/core/daemon.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace asco::core { 12 | 13 | class daemon { 14 | public: 15 | daemon(std::string name); 16 | ~daemon(); 17 | 18 | void awake(); 19 | 20 | private: 21 | struct init_waiter { 22 | daemon &self; 23 | 24 | ~init_waiter() { self.init_sem.acquire(); } 25 | }; 26 | 27 | protected: 28 | init_waiter start(); 29 | 30 | void sleep_until_awake(); 31 | void sleep_until_awake_for(const std::chrono::seconds &duration); 32 | void sleep_until_awake_for(const std::chrono::milliseconds &duration); 33 | void sleep_until_awake_for(const std::chrono::microseconds &duration); 34 | void sleep_until_awake_for(const std::chrono::nanoseconds &duration); 35 | void sleep_until_awake_before(const std::chrono::high_resolution_clock::time_point &time_point); 36 | 37 | virtual bool init(); 38 | virtual bool run_once(std::stop_token &st); 39 | virtual void shutdown(); 40 | 41 | private: 42 | std::jthread dthread; 43 | std::string name{"asco::daemon"}; 44 | ::pthread_t ptid{0}; 45 | std::binary_semaphore init_sem{0}; 46 | 47 | std::binary_semaphore sem{0}; 48 | }; 49 | 50 | }; // namespace asco::core 51 | -------------------------------------------------------------------------------- /asco/invoke.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace asco::invoke { 14 | 15 | using namespace concepts; 16 | 17 | template Fn> 18 | requires(!std::is_function_v>) 19 | constexpr auto co_invoke(Fn &&f, Args &&...args) { 20 | if constexpr (std::is_rvalue_reference_v) { 21 | using FnType = std::remove_cvref_t; 22 | std::unique_ptr fnp = std::make_unique( 23 | std::forward(f), +[](void *p) noexcept { reinterpret_cast(p)->~FnType(); }); 24 | auto task = fnp->get()(std::forward(args)...); 25 | task.bind_lambda(std::move(fnp)); 26 | return task; 27 | } else { 28 | return f(std::forward(args)...); 29 | } 30 | } 31 | 32 | template Fn> 33 | requires(std::is_function_v>) 34 | constexpr auto co_invoke(Fn &&f, Args &&...args) { 35 | return f(std::forward(args)...); 36 | } 37 | 38 | }; // namespace asco::invoke 39 | 40 | namespace asco { 41 | 42 | using invoke::co_invoke; 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /asco/panic/panic.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace asco::panic { 13 | 14 | std::vector> panic_callbacks; 15 | 16 | [[noreturn]] void panic(std::string msg) noexcept { 17 | auto formatted_msg = std::format( 18 | "{}[ASCO] {}{}\nStack trace(most recent call last):\n{}", panic_color, msg, reset_color, unwind(2)); 19 | auto st = cpptrace::stacktrace::current(1); 20 | for (auto &cb : panic_callbacks) { cb(st, msg); } 21 | std::println(stderr, "{}", formatted_msg); 22 | std::abort(); 23 | } 24 | 25 | [[noreturn]] void co_panic(std::string msg) noexcept { 26 | auto formatted_msg = std::format( 27 | "{}[ASCO] {}{}\nStack and await chain trace(most recent call/await last):\n{}", panic_color, msg, 28 | reset_color, co_unwind(2)); 29 | auto st = co_stacktrace(2); 30 | for (auto &cb : panic_callbacks) { cb(st, msg); } 31 | std::println(stderr, "{}", formatted_msg); 32 | std::abort(); 33 | } 34 | 35 | void register_callback(std::function cb) { 36 | panic_callbacks.push_back(std::move(cb)); 37 | } 38 | 39 | }; // namespace asco::panic 40 | -------------------------------------------------------------------------------- /asco/utils/memory_slot.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace asco::utils { 12 | 13 | using namespace types; 14 | using namespace concepts; 15 | 16 | template 17 | struct memory_slot; 18 | 19 | template 20 | struct memory_slot { 21 | char _[0]; 22 | }; 23 | 24 | template 25 | struct memory_slot { 26 | alignas(alignof(T)) unsigned char data[sizeof(T)]; 27 | 28 | atomic_bool data_lives{false}; 29 | 30 | void put(passing value, bool neverdrop = false) noexcept { 31 | new (data) T(std::forward(value)); 32 | if (!neverdrop) 33 | data_lives.store(true, morder::release); 34 | } 35 | 36 | void emplace(bool neverdrop, auto &&...args) noexcept { 37 | new (data) T(std::forward(args)...); 38 | if (!neverdrop) 39 | data_lives.store(true, morder::release); 40 | } 41 | 42 | T &get() noexcept { return *reinterpret_cast(data); } 43 | 44 | const T &get() const noexcept { return *reinterpret_cast(data); } 45 | 46 | T &&move() noexcept { return std::move(*reinterpret_cast(data)); } 47 | 48 | ~memory_slot() { 49 | if (data_lives.load(morder::acquire)) 50 | get().~T(); 51 | } 52 | }; 53 | 54 | }; // namespace asco::utils 55 | 56 | namespace asco { 57 | 58 | using utils::memory_slot; 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/panic.md: -------------------------------------------------------------------------------- 1 | # Panic 模块(asco::panic/panic.h) 2 | 3 | 本文档介绍 ASCO 框架中的 panic 机制及其头文件 `panic/panic.h` 的接口与用法。 4 | 5 | ## 概述 6 | 7 | Panic 用于处理**不可恢复的致命错误**,如断言失败、严重逻辑漏洞等。调用 panic 后,程序会立即终止,并可输出详细的错误信息与栈回溯,便于定位问题。 8 | 9 | ## 主要接口 10 | 11 | ### 1. `panic(std::string msg) noexcept` 12 | 13 | - 触发 panic,输出指定消息,终止程序。 14 | - `[[noreturn]]`:不会返回。 15 | - 推荐用于普通同步场景。 16 | 17 | ### 2. `co_panic(std::string msg) noexcept` 18 | 19 | - 协程环境下触发 panic,输出消息并终止。 20 | - 用法与 `panic` 类似。 21 | 22 | ### 3. 格式化接口 23 | 24 | - 支持 C++20 std::format 风格: 25 | 26 | ```cpp 27 | panic("错误码: {},原因: {}", code, reason); 28 | co_panic("协程异常: {}", info); 29 | ``` 30 | 31 | - 自动格式化参数并输出。 32 | 33 | ### 4. `register_callback(std::function cb)` 34 | 35 | - 注册 panic 回调,可自定义 panic 行为(如日志落地、核心转储等)。 36 | - 回调参数: 37 | - `cpptrace::stacktrace &`:当前栈(包括异步调用链,当使用 co_panic 时)回溯信息。 38 | - `std::string_view`:panic 消息。 39 | - 适合集成自定义监控、调试工具。 40 | 41 | ## 使用示例 42 | 43 | ```cpp 44 | #include 45 | 46 | void fatal_error() { 47 | asco::panic::panic("致命错误,无法恢复"); 48 | } 49 | 50 | void format_example(int code) { 51 | asco::panic::panic("错误码: {}", code); 52 | } 53 | 54 | // 注册自定义回调 55 | asco::panic::register_callback([](cpptrace::stacktrace &st, std::string_view msg) { 56 | // 可在此保存日志、输出栈(异步调用链)信息等 57 | }); 58 | ``` 59 | 60 | ## 设计要点 61 | 62 | - panic 只用于**绝不应继续执行**的场景。 63 | - 格式化接口便于输出结构化错误信息。 64 | - 回调机制支持扩展和集成第三方工具。 65 | - 协程专用接口保证异步场景下的正确终止。 66 | 67 | ## 与断言的关系 68 | 69 | - `asco_assert` 断言失败时会调用 panic,统一致命错误处理路径。 70 | - panic 可作为所有“不可恢复”错误的终极出口。 71 | -------------------------------------------------------------------------------- /asco/core/wait_queue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace asco::core { 9 | 10 | yield> wait_queue::wait() { 11 | auto _ = mut.lock(); 12 | 13 | if (auto u = untriggered_notifications.load(morder::acquire)) { 14 | do { 15 | u = untriggered_notifications.load(morder::acquire); 16 | if (u == 0) 17 | goto do_suspend; 18 | } while ( 19 | !untriggered_notifications.compare_exchange_weak(u, u - 1, morder::acq_rel, morder::acquire)); 20 | return {std::nullopt}; 21 | } 22 | 23 | do_suspend: 24 | auto &w = worker::this_worker(); 25 | auto this_task = w.current_task(); 26 | w.suspend_task(this_task); 27 | waiters.push_back({w, this_task}); 28 | return {std::prev(waiters.end())}; 29 | } 30 | 31 | void wait_queue::interrupt_wait(decltype(waiters)::iterator it) { 32 | auto _ = mut.lock(); 33 | 34 | auto [w, task_id] = *it; 35 | waiters.erase(it); 36 | } 37 | 38 | void wait_queue::notify(size_t n, bool record_untriggered) { 39 | auto _ = mut.lock(); 40 | 41 | while (n) { 42 | if (waiters.empty()) 43 | break; 44 | auto [w, task_id] = waiters.front(); 45 | waiters.pop_front(); 46 | w.activate_task(task_id); 47 | n--; 48 | } 49 | if (record_untriggered && n) 50 | untriggered_notifications.fetch_add(n, morder::release); 51 | } 52 | 53 | }; // namespace asco::core 54 | -------------------------------------------------------------------------------- /asco/core/pool_allocator.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace asco::core::mm { 12 | 13 | template 14 | inline std::pmr::synchronized_pool_resource &default_pool() { 15 | static utils::memory_slot pool_slot; 16 | static std::once_flag flag; 17 | std::call_once(flag, [] { 18 | // We use neverdrop flag here, because we should ensure those memory never be released during the 19 | // program lifetime. 20 | pool_slot.emplace(true); 21 | }); 22 | return pool_slot.get(); 23 | } 24 | 25 | template 26 | inline std::pmr::unsynchronized_pool_resource &local_pool() { 27 | static utils::memory_slot pool_slot; 28 | static std::once_flag flag; 29 | std::call_once(flag, [] { 30 | // We use neverdrop flag here, because we should ensure those memory never be released during the 31 | // program lifetime. 32 | pool_slot.emplace(true); 33 | }); 34 | return pool_slot.get(); 35 | } 36 | 37 | template 38 | inline std::pmr::polymorphic_allocator allocator(std::pmr::synchronized_pool_resource &pool) { 39 | return std::pmr::polymorphic_allocator{&pool}; 40 | } 41 | 42 | template 43 | inline std::pmr::polymorphic_allocator allocator(std::pmr::unsynchronized_pool_resource &pool) { 44 | return std::pmr::polymorphic_allocator{&pool}; 45 | } 46 | 47 | }; // namespace asco::core::mm 48 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/join_set.md: -------------------------------------------------------------------------------- 1 | # Join Set 聚合器 2 | 3 | `asco:::join_set` 提供一个轻量的“任务收集器”,用于在多个异步计算完成后,以生成器形式逐个取回结果。它内部使用 `channel` 管道按完成顺序输送值,适合将若干协程并发运行、再统一遍历其结果。 4 | 5 | - 头文件:`#include ` 6 | - 依赖组件:`future`、`generator`、`channel` 7 | 8 | ## 基础用法 9 | 10 | 典型流程: 11 | 12 | 1. 创建 `join_set` 实例。 13 | 2. 调用 `spawn(fn)` 注册若干异步任务。`fn` 必须结果类型统一为 `T`。 14 | 3. 调用 `join()` 得到 `generator`,随后通过 `co_await` 按完成顺序消费结果。 15 | 16 | 示例: 17 | 18 | ```cpp 19 | #include 20 | #include 21 | #include 22 | using namespace asco; 23 | 24 | future async_main() { 25 | base::join_set set; 26 | 27 | for (int i = 0; i < 3; ++i) { 28 | set.spawn([i]() -> future { 29 | co_await sleep_for(std::chrono::milliseconds{10 * (i + 1)}); 30 | co_return i; 31 | }) 32 | .ignore(); 33 | } 34 | 35 | auto results = set.join(); 36 | while (auto value = co_await results()) { 37 | std::println("result = {}", *value); 38 | } 39 | co_return 0; 40 | } 41 | ``` 42 | 43 | ## 接口概览 44 | 45 | - `explicit join_set(Creator ctor)`:允许自定义底层 `channel` 的创建器;默认使用 `continuous_queue`。 46 | - `future_spawn spawn(Fn &&fn)`:启动一个异步任务,将其结果推入内部 `channel`。`Fn` 必须返回 `future` 或 `future_spawn`,否则编译失败。 47 | - `generator join()`:停止接受新任务(内部关闭 `sender`),并返回一个生成器。生成器被迭代完毕后会自动退出。 48 | 49 | ## 行为与保证 50 | 51 | - 结果顺序等于任务实际完成顺序;如果多个任务同时完成,顺序取决于 `channel` 的排队次序。 52 | - 任务抛出异常时会沿 `future` 传播;可以在调用处使用 `.ignore(on_exception)` 或额外捕获。 53 | 54 | ## 使用建议 55 | 56 | - 若任务很多,建议结合 `std::for_each` / `ranges` 批量调用 `spawn`,并尽早 `.ignore()` 避免遗漏。 57 | - 如果需要自定义任务队列特性(例如环形缓冲区),可以提供自定义 `Creator` 参数构造 `join_set`。 58 | -------------------------------------------------------------------------------- /asco/core/time/high_resolution_timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace asco::core::time { 17 | 18 | using namespace types; 19 | 20 | class high_resolution_timer final : public daemon { 21 | public: 22 | high_resolution_timer(); 23 | 24 | high_resolution_timer(const high_resolution_timer &) = delete; 25 | high_resolution_timer &operator=(const high_resolution_timer &) = delete; 26 | 27 | timer_id register_timer( 28 | const std::chrono::high_resolution_clock::time_point &expire_time, worker &worker_ptr, 29 | task_id task_id); 30 | 31 | void unregister_timer(timer_id id); 32 | 33 | private: 34 | bool init() override; 35 | bool run_once(std::stop_token &st) override; 36 | void shutdown() override; 37 | 38 | struct timer_area { 39 | size_t earliest_expire_time; // In seconds 40 | std::map entries{}; 41 | 42 | std::strong_ordering operator<=>(const timer_area &other) const noexcept { 43 | return earliest_expire_time <=> other.earliest_expire_time; 44 | } 45 | 46 | std::strong_ordering operator<=>(const timer_entry &entry) const noexcept { 47 | return earliest_expire_time <=> entry.expire_seconds_since_epoch(); 48 | } 49 | }; 50 | 51 | spin> timer_tree; 52 | }; 53 | 54 | }; // namespace asco::core::time 55 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/notify.md: -------------------------------------------------------------------------------- 1 | # Notify(轻量通知器) 2 | 3 | `asco::notify` 提供一个极轻量的事件通知原语,支持多个协程等待并由通知唤醒。它基于内部的 `wait_queue`,适合在不需要复杂状态同步、只需“唤醒即可”的场景中使用。 4 | 5 | - 头文件:`#include ` 6 | - 命名空间:`asco` 7 | 8 | ## 接口概览 9 | 10 | - `yield<> wait()` 11 | - 以协程方式挂起当前任务,等待 `notify_one()` 或 `notify_all()` 唤醒。 12 | - `void notify_one()` 13 | - 唤醒至多一个等待中的协程。若当前没有等待者,通知会被丢弃(不计数、不排队)。 14 | - `void notify_all()` 15 | - 唤醒所有等待中的协程,同样不会记录通知历史。 16 | 17 | ## 使用示例 18 | 19 | ### 1. 简单的等待-通知 20 | 21 | ```cpp 22 | #include 23 | #include 24 | using namespace asco; 25 | 26 | future_spawn worker(notify &n, std::atomic &flag) { 27 | co_await n.wait(); 28 | flag.store(true, std::memory_order_release); 29 | co_return; 30 | } 31 | 32 | future async_main() { 33 | notify n; 34 | std::atomic flag{false}; 35 | 36 | auto w = worker(n, flag); 37 | 38 | // 执行若干工作… 39 | n.notify_one(); 40 | 41 | co_await w; 42 | co_return flag.load(std::memory_order_acquire) ? 0 : 1; 43 | } 44 | ``` 45 | 46 | ### 2. 广播唤醒多个等待者 47 | 48 | ```cpp 49 | #include 50 | #include 51 | #include 52 | using namespace asco; 53 | 54 | future_spawn waiter(notify &n, std::atomic &counter) { 55 | co_await n.wait(); 56 | counter.fetch_add(1, std::memory_order_acq_rel); 57 | co_return; 58 | } 59 | 60 | future async_main() { 61 | notify n; 62 | std::atomic counter{0}; 63 | std::vector> waiters; 64 | 65 | for (int i = 0; i < 3; ++i) { 66 | waiters.push_back(waiter(n, counter)); 67 | } 68 | 69 | n.notify_all(); 70 | for (auto &w : waiters) co_await w; 71 | 72 | co_return counter.load(std::memory_order_acquire) == 3 ? 0 : 1; 73 | } 74 | ``` 75 | 76 | ## 注意事项 77 | 78 | - `notify` 不会记录历史通知;在没有等待者时调用 `notify_one()` / `notify_all()` 会被直接丢弃。 79 | - 若需要具备通知计数或超时语义,可考虑使用 `semaphore` 或其他同步原语。 80 | -------------------------------------------------------------------------------- /asco/concurrency/queue.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace asco::queue { 14 | 15 | enum class push_fail { 16 | full, // Queue is full now. 17 | closed // This queue is closed. 18 | }; 19 | 20 | enum class pop_fail { 21 | non_object, // No object available now. 22 | closed // This queue is closed. 23 | }; 24 | 25 | // Concept for the sender of a MPMC queue. 26 | // Never stop automatically. 27 | template 28 | concept sender = requires(S s) { 29 | { S::max_volume } -> std::same_as; 30 | { s.push(std::declval>()) } -> std::same_as>>; 31 | { s.stop() } -> std::same_as; 32 | { s.is_stopped() } -> std::same_as; 33 | } && concepts::move_secure && concepts::move_secure; 34 | 35 | // Concept for the receiver of a MPMC queue. 36 | // Never stop automatically. 37 | template 38 | concept receiver = requires(R r) { 39 | { R::max_volume } -> std::same_as; 40 | { r.pop() } -> std::same_as>; 41 | { r.stop() } -> std::same_as; 42 | { r.is_stopped() } -> std::same_as; 43 | } && concepts::move_secure && concepts::move_secure; 44 | 45 | template 46 | concept creator = requires(const C c) { 47 | typename C::Sender; 48 | typename C::Receiver; 49 | { C::max_volume } -> std::same_as; 50 | { c.operator()() } -> std::same_as>; 51 | }; 52 | 53 | }; // namespace asco::queue 54 | 55 | namespace asco { 56 | 57 | using queue::pop_fail; 58 | using queue::push_fail; 59 | 60 | }; // namespace asco 61 | -------------------------------------------------------------------------------- /asco/context.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace asco::contexts { 18 | 19 | using namespace types; 20 | using namespace concepts; 21 | 22 | class context { 23 | public: 24 | context() = default; 25 | 26 | static std::shared_ptr with_cancel(); 27 | 28 | static std::shared_ptr with_timeout(const duration_type auto &dur) { 29 | auto res = std::allocate_shared(core::mm::allocator(context::_allocator)); 30 | co_invoke([ctx = res, dur] -> future_spawn { 31 | co_await sleep_for(dur); 32 | co_await ctx->cancel(); 33 | }).ignore(); 34 | return res; 35 | } 36 | 37 | context(const context &) = delete; 38 | context &operator=(const context &) = delete; 39 | 40 | ~context() noexcept = default; 41 | 42 | future cancel(); 43 | bool is_cancelled() const noexcept; 44 | 45 | yield<> operator co_await() noexcept; 46 | 47 | // This callback must be reentrant 48 | future set_cancel_callback(std::function &&callback); 49 | 50 | private: 51 | atomic_bool _cancelled{false}; 52 | notify _notify; 53 | rwlock> _cancel_callback; 54 | 55 | static std::pmr::synchronized_pool_resource &_allocator; 56 | }; 57 | 58 | inline std::pmr::synchronized_pool_resource &context::_allocator{core::mm::default_pool()}; 59 | 60 | yield<> operator co_await(const std::shared_ptr &ctx) noexcept; 61 | 62 | }; // namespace asco::contexts 63 | 64 | namespace asco { 65 | 66 | using contexts::context; 67 | 68 | }; // namespace asco 69 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 pointer-to-bios 2 | # SPDX-License-Identifier: MIT 3 | 4 | add_executable(test_future_base future_base.cpp) 5 | target_link_libraries(test_future_base PRIVATE asco::core asco::main) 6 | 7 | add_executable(test_semaphore sync/test_semaphore.cpp) 8 | target_link_libraries(test_semaphore PRIVATE asco::core asco::main) 9 | 10 | add_executable(test_interval time/test_interval.cpp) 11 | target_link_libraries(test_interval PRIVATE asco::core asco::main) 12 | 13 | add_executable(test_sleep time/test_sleep.cpp) 14 | target_link_libraries(test_sleep PRIVATE asco::core asco::main) 15 | 16 | add_executable(test_channel sync/test_channel.cpp) 17 | target_link_libraries(test_channel PRIVATE asco::core asco::main) 18 | 19 | add_executable(test_mutex sync/test_mutex.cpp) 20 | target_link_libraries(test_mutex PRIVATE asco::core asco::main) 21 | 22 | add_executable(test_generator test_generator.cpp) 23 | target_link_libraries(test_generator PRIVATE asco::core asco::main) 24 | 25 | add_executable(test_notify sync/test_notify.cpp) 26 | target_link_libraries(test_notify PRIVATE asco::core asco::main) 27 | 28 | add_executable(test_rwlock sync/test_rwlock.cpp) 29 | target_link_libraries(test_rwlock PRIVATE asco::core asco::main) 30 | 31 | add_executable(test_context context/test_context.cpp) 32 | target_link_libraries(test_context PRIVATE asco::core asco::main) 33 | 34 | add_executable(test_join_set test_join_set.cpp) 35 | target_link_libraries(test_join_set PRIVATE asco::core asco::main) 36 | 37 | add_test(NAME future_base COMMAND test_future_base) 38 | add_test(NAME semaphore COMMAND test_semaphore) 39 | add_test(NAME interval COMMAND test_interval) 40 | add_test(NAME sleep COMMAND test_sleep) 41 | add_test(NAME channel COMMAND test_channel) 42 | add_test(NAME mutex COMMAND test_mutex) 43 | add_test(NAME generator COMMAND test_generator) 44 | add_test(NAME notify COMMAND test_notify) 45 | add_test(NAME rwlock COMMAND test_rwlock) 46 | add_test(NAME context COMMAND test_context) 47 | add_test(NAME join_set COMMAND test_join_set) 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Development & Contributing 2 | 3 | Pull requests must be reviewed and approved by at least one project maintainer before merging. 4 | 5 | ## Feature Development 6 | 7 | * Please create a issue to show your idea of new feature and ask for joining into this repo as a collaborator. 8 | 9 | 1. (Optional) Fork the repo if you are not a collaborator or prefer not to work directly in the main repository. 10 | 2. Create a new branch from the latest `main` branch. Name the new branch with the following format: `feature/`. 11 | 3. Develop your feature in the new branch. 12 | 4. Commit your changes and push them to the new branch. 13 | 5. Create a pull request to request to merge your changes into the `main` branch. 14 | 15 | * New features must include comprehensive unit tests and documentation in either Chinese or English; otherwise, they will not be accepted. 16 | The documentation requirements are flexible — a high-level overview of the feature's functionality is sufficient. 17 | 18 | ## Feature branches lifecycle 19 | 20 | * Only for this repo 21 | 22 | 1. Create a new branch from `main` for new feature. 23 | 2. Implement the feature. 24 | 3. Write unit tests for the feature. 25 | 4. Write documentation for the feature. 26 | 5. Merge the feature branch into `main` when the feature is ready. 27 | 6. Delete the feature branch. 28 | 29 | * Everyone who had joined this repo can contribute on the feature branches. 30 | 31 | ## Versioning 32 | 33 | The version number follows a three-segment format: `a.b.c`. The release should be tagged in Git using annotated tags (e.g., `1.2.0`). 34 | 35 | 1. **Breaking Change Release** 36 | * The `a` version is incremented. 37 | 38 | 2. **Scheduled Minor Release** (every 3 months): 39 | * The `b` version is incremented. 40 | * May include new features, improvements, and bug fixes accumulated since the last minor release. 41 | 42 | 3. **Patch Release** (as needed): 43 | * The `c` version is incremented. 44 | * Triggered by critical bug fixes or important feature additions outside of the scheduled minor release cycle. 45 | -------------------------------------------------------------------------------- /.github/workflows/mdbook.yaml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Deploy mdBook site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["remake"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | env: 32 | MDBOOK_VERSION: 0.4.36 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Install mdBook 36 | run: | 37 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 38 | rustup update 39 | cargo install mdbook 40 | - name: Setup Pages 41 | id: pages 42 | uses: actions/configure-pages@v5 43 | - name: Build with mdBook 44 | run: | 45 | rm -rf docs/book 46 | cd docs/zh-cn 47 | mdbook build --dest-dir ../book/zhcn 48 | cd ../en-us 49 | mdbook build --dest-dir ../book/enus 50 | cd ../.. 51 | - name: Upload artifact 52 | uses: actions/upload-pages-artifact@v3 53 | with: 54 | path: ./docs/book 55 | 56 | # Deployment job 57 | deploy: 58 | environment: 59 | name: github-pages 60 | url: ${{ steps.deployment.outputs.page_url }} 61 | runs-on: ubuntu-latest 62 | needs: build 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /asco/core/daemon.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #ifdef __linux__ 7 | # include 8 | #endif 9 | 10 | #include 11 | 12 | namespace asco::core { 13 | 14 | daemon::daemon(std::string name) 15 | : name(std::move(name)) {} 16 | 17 | void daemon::awake() { sem.release(); } 18 | 19 | daemon::init_waiter daemon::start() { 20 | dthread = std::jthread{[&self = *this](std::stop_token st) { 21 | #ifdef __linux__ 22 | self.ptid = ::pthread_self(); 23 | ::pthread_setname_np(self.ptid, self.name.c_str()); 24 | #endif 25 | 26 | if (!self.init()) { 27 | self.init_sem.release(); 28 | self.shutdown(); 29 | return; 30 | } 31 | self.init_sem.release(); 32 | 33 | while (!st.stop_requested() && self.run_once(st)); 34 | 35 | self.shutdown(); 36 | }}; 37 | 38 | return {*this}; 39 | } 40 | 41 | daemon::~daemon() { 42 | if (dthread.joinable()) { 43 | dthread.request_stop(); 44 | awake(); 45 | dthread.join(); 46 | } 47 | } 48 | 49 | void daemon::sleep_until_awake() { sem.acquire(); } 50 | 51 | void daemon::sleep_until_awake_for(const std::chrono::seconds &duration) { sem.try_acquire_for(duration); } 52 | 53 | void daemon::sleep_until_awake_for(const std::chrono::milliseconds &duration) { 54 | sem.try_acquire_for(duration); 55 | } 56 | 57 | void daemon::sleep_until_awake_for(const std::chrono::microseconds &duration) { 58 | sem.try_acquire_for(duration); 59 | } 60 | 61 | void daemon::sleep_until_awake_for(const std::chrono::nanoseconds &duration) { 62 | sem.try_acquire_for(duration); 63 | } 64 | 65 | void daemon::sleep_until_awake_before(const std::chrono::high_resolution_clock::time_point &time_point) { 66 | sem.try_acquire_until(time_point); 67 | } 68 | 69 | bool daemon::init() { return true; } 70 | 71 | bool daemon::run_once(std::stop_token &) { 72 | sleep_until_awake(); 73 | return true; 74 | } 75 | 76 | void daemon::shutdown() {} 77 | 78 | }; // namespace asco::core 79 | -------------------------------------------------------------------------------- /asco/join_set.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace asco::base { 13 | 14 | using namespace concepts; 15 | 16 | namespace cq = continuous_queue; 17 | 18 | template Creator = decltype(cq::create)> 19 | class join_set { 20 | public: 21 | explicit join_set(Creator ctor) { std::tie(tx, rx) = channel(ctor); } 22 | 23 | explicit join_set() 24 | : join_set(cq::create) {} 25 | 26 | join_set(const join_set &) noexcept = delete; 27 | join_set &operator=(const join_set &) noexcept = delete; 28 | 29 | join_set(join_set &&) noexcept = delete; 30 | join_set &operator=(join_set &&) noexcept = delete; 31 | 32 | // Just call but do not co_await 33 | template 34 | requires std::is_same_v::deliver_type, T> 35 | future_spawn spawn(Fn &&fn) { 36 | active_count.fetch_add(1, std::memory_order_acq_rel); 37 | if constexpr (std::is_same_v>, future>) { 38 | co_await tx.send(co_await co_invoke(std::forward>(fn)).spawn()); 39 | } else { 40 | co_await tx.send(co_await co_invoke(std::forward>(fn))); 41 | } 42 | } 43 | 44 | generator join() { 45 | for (; active_count.load(morder::acquire) > 0; active_count.fetch_sub(1, morder::acq_rel)) { 46 | if (auto res = co_await rx.recv(); res) { 47 | co_yield std::move(*res); 48 | } else { 49 | break; 50 | } 51 | } 52 | } 53 | 54 | private: 55 | atomic_size_t active_count{0}; 56 | sender tx; 57 | receiver rx; 58 | }; 59 | 60 | }; // namespace asco::base 61 | 62 | namespace asco { 63 | 64 | using base::join_set; 65 | 66 | }; // namespace asco 67 | -------------------------------------------------------------------------------- /asco/core/time/timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace asco::core::time { 12 | 13 | using namespace types; 14 | 15 | struct alignas(sizeof(uint128_t)) timer_id { 16 | alignas(sizeof(uint64_t)) const uint64_t meta; 17 | alignas(sizeof(uint64_t)) const uint64_t expire_time_nanos; 18 | 19 | std::strong_ordering operator<=>(const timer_id &other) const noexcept { 20 | if constexpr (std::endian::native == std::endian::little) { 21 | return *reinterpret_cast(this) 22 | <=> *reinterpret_cast(&other); 23 | } else { 24 | return (static_cast(expire_time_nanos) << 64 | meta) 25 | <=> (static_cast(other.expire_time_nanos) << 64 | other.meta); 26 | } 27 | } 28 | }; 29 | 30 | static_assert(sizeof(timer_id) == 16); 31 | 32 | struct timer_entry { 33 | const std::chrono::high_resolution_clock::time_point expire_time; 34 | worker &worker_ref; 35 | const task_id tid; 36 | const timer_id _id{gen_id(expire_time, worker_ref, tid)}; 37 | const size_t _expire_seconds_since_epoch{static_cast( 38 | std::chrono::duration_cast(expire_time.time_since_epoch()).count())}; 39 | 40 | std::strong_ordering operator<=>(const timer_entry &other) const noexcept { 41 | return expire_time <=> other.expire_time; 42 | } 43 | 44 | timer_id id() const noexcept { return _id; } 45 | 46 | size_t expire_seconds_since_epoch() const noexcept { return _expire_seconds_since_epoch; } 47 | 48 | private: 49 | static timer_id gen_id( 50 | const std::chrono::high_resolution_clock::time_point &expire_time, const worker &worker_ptr, 51 | task_id tid) noexcept; 52 | }; 53 | 54 | template 55 | concept timer = requires(T t) { 56 | T(); 57 | { 58 | t.register_timer( 59 | std::declval(), std::declval(), 60 | std::declval()) 61 | } -> std::same_as; 62 | { t.unregister_timer(std::declval()) } -> std::same_as; 63 | }; 64 | 65 | }; // namespace asco::core::time 66 | -------------------------------------------------------------------------------- /asco/core/worker.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace asco::core { 18 | 19 | using namespace types; 20 | 21 | namespace cq = continuous_queue; 22 | 23 | using task_tuple = std::tuple>>; 24 | using task_sender = cq::sender; 25 | using task_receiver = cq::receiver; 26 | 27 | using awake_queue = spin>; 28 | 29 | class worker final : public daemon { 30 | public: 31 | worker( 32 | size_t id, atomic_size_t &load_counter, awake_queue &awake_q, task_receiver &&task_recv, 33 | atomic_size_t &worker_count, atomic_bool &shutting_down); 34 | ~worker(); 35 | 36 | size_t id() const noexcept { return _id; } 37 | 38 | static bool in_worker() noexcept; 39 | static worker &this_worker() noexcept; 40 | 41 | void register_task(task_id id, std::shared_ptr> task, bool non_spawn = false); 42 | void unregister_task(task_id id); 43 | 44 | void task_enter(task_id id); 45 | task_id task_exit(); 46 | 47 | task_id current_task(); 48 | 49 | bool activate_task(task_id id); 50 | bool suspend_task(task_id id); 51 | 52 | std::shared_ptr> move_out_suspended_task(task_id id); 53 | void move_in_suspended_task(task_id id, std::shared_ptr> t); 54 | 55 | private: 56 | bool init() override; 57 | bool run_once(std::stop_token &st) override; 58 | void shutdown() override; 59 | 60 | const size_t _id; 61 | task_receiver task_recv; 62 | 63 | atomic_size_t &load_counter; 64 | atomic_size_t &worker_count; 65 | atomic_bool &shutting_down; 66 | 67 | awake_queue &awake_q; 68 | 69 | std::stack task_stack; 70 | 71 | spin>>> tasks{}; 72 | spin>>> suspended_tasks{}; 73 | spin>>> active_tasks{}; 74 | 75 | std::tuple>> sched(); 76 | 77 | thread_local static atomic _this_worker; 78 | }; 79 | 80 | }; // namespace asco::core 81 | -------------------------------------------------------------------------------- /.github/build/arch-llvm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux:latest 2 | 3 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 4 | 5 | LABEL org.opencontainers.image.title="asco-build (Archlinux + LLVM)" 6 | LABEL org.opencontainers.image.description="ASCO build environment - Arch Linux build image with clang/llvm, cmake, ninja, ccache" 7 | LABEL org.opencontainers.image.source="https://github.com/pointertobios/asco" 8 | LABEL org.opencontainers.image.licenses="MIT" 9 | 10 | ENV PATH="/usr/lib/ccache/bin:${PATH}" 11 | 12 | RUN set -eux; \ 13 | pacman -Sy --noconfirm archlinux-keyring && \ 14 | pacman -Syu --noconfirm && \ 15 | pacman-key --init && \ 16 | pacman-key --populate archlinux 17 | 18 | RUN set -eux; \ 19 | pacman -S --noconfirm --needed \ 20 | base-devel \ 21 | cmake \ 22 | ninja \ 23 | clang \ 24 | llvm \ 25 | lld \ 26 | lldb \ 27 | pkgconf \ 28 | ccache \ 29 | git \ 30 | liburing \ 31 | ca-certificates; \ 32 | update-ca-trust 33 | 34 | ARG USERNAME=builder 35 | ARG USER_UID=1000 36 | ARG USER_GID=1000 37 | RUN set -eux; \ 38 | groupadd -g "${USER_GID}" "${USERNAME}" && \ 39 | useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash "${USERNAME}" 40 | 41 | ENV CC=clang \ 42 | CXX=clang++ \ 43 | CMAKE_C_COMPILER=clang \ 44 | CMAKE_CXX_COMPILER=clang++ \ 45 | CMAKE_C_COMPILER_LAUNCHER=ccache \ 46 | CMAKE_CXX_COMPILER_LAUNCHER=ccache \ 47 | CCACHE_DIR=/home/${USERNAME}/.cache/ccache \ 48 | CCACHE_MAXSIZE=2G \ 49 | CCACHE_COMPRESS=1 50 | 51 | RUN set -eux; \ 52 | install -d -m 0755 -o "${USER_UID}" -g "${USER_GID}" "${CCACHE_DIR}" 53 | 54 | RUN set -eux; \ 55 | pacman -Scc --noconfirm 56 | 57 | RUN mkdir -p /work 58 | WORKDIR /work 59 | USER ${USERNAME} 60 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/context.md: -------------------------------------------------------------------------------- 1 | # Context(协程上下文) 2 | 3 | `asco::context`(底层定义在 `asco::contexts::context`)提供了一个轻量的协程取消原语,可用于在多个协程之间传播“停止/超时”信号。它与 `notify` 结合使用,支持显式取消与超时自动取消。 4 | 5 | - 头文件:`#include ` 6 | - 命名空间:`asco`(通过别名导出) 7 | 8 | ## 创建方式 9 | 10 | - `static std::shared_ptr with_cancel()` 11 | - 创建一个手动可取消的上下文,初始状态为 *未取消*。 12 | - `static std::shared_ptr with_timeout(const duration_type auto &dur)` 13 | - 创建一个上下文并在 `dur` 后自动取消。内部会启动一个协程调用 `cancel()`。 14 | 15 | 两个工厂函数都返回 `std::shared_ptr`。使用共享指针便于在多个协程中传播同一取消源。 16 | 17 | ## 取消与状态查询 18 | 19 | - `future cancel()` 20 | - 将上下文标记为已取消,并唤醒所有等待该上下文的协程,然后执行取消回调。该协程可以被并发调用;每次调用都会执行已注册的回调。 21 | - `future set_cancel_callback(std::function)` 22 | - 注册一个在取消发生时调用的回调。若上下文已取消,请先重新检查状态;回调仅在随后执行的 `cancel()` 协程中触发。 23 | - 回调实现必须是可重入的,并且要做好幂等处理,以便在并发取消时安全地再次触发取消或操作上下文。 24 | - `bool is_cancelled() const noexcept` 25 | - 查询当前是否处于已取消状态。 26 | 27 | 取消操作是幂等的;重复调用 `cancel()` 不会带来额外副作用。 28 | 29 | ## 等待取消 30 | 31 | `context` 定义了成员 `operator co_await()`,同时也为 `std::shared_ptr` 提供了自由函数 `operator co_await()`。因此既可以在上下文对象本身上 `co_await ctx_ref;`,也可以直接 `co_await ctx_ptr;` 来等待取消事件: 32 | 33 | - 若上下文尚未取消,当前协程将挂起,直到 `cancel()` 或超时触发。 34 | - 若上下文已取消,则立即恢复,且 `co_await` 不返回任何额外数据:它仅表示“取消已经发生”。 35 | 36 | 示例: 37 | 38 | ```cpp 39 | #include 40 | #include 41 | #include 42 | using namespace asco; 43 | 44 | future_spawn worker(context &ctx_ref, std::atomic &flag) { 45 | co_await ctx_ref; // 等待取消信号 46 | flag.store(true, std::memory_order_release); 47 | co_return; 48 | } 49 | 50 | future async_main() { 51 | auto ctx = context::with_cancel(); 52 | std::atomic flag{false}; 53 | 54 | auto w = worker(*ctx, flag); // 也可以 co_await ctx(shared_ptr) 55 | 56 | // 进行其他操作… 57 | co_await sleep_for(10ms); 58 | co_await ctx->cancel(); // 通知所有等待方并等待回调执行 59 | 60 | co_await w; 61 | return flag.load(std::memory_order_acquire) ? 0 : 1; 62 | } 63 | ``` 64 | 65 | ## 与超时结合 66 | 67 | `with_timeout()` 会在后台调用 `sleep_for()` 后自动取消: 68 | 69 | ```cpp 70 | future async_main() { 71 | auto ctx = context::with_timeout(50ms); 72 | 73 | co_await ctx; // 最多等待 50ms 74 | 75 | // 此时 ctx 已经处于取消状态 76 | if (!ctx->is_cancelled()) { 77 | co_return 1; 78 | } 79 | co_return 0; 80 | } 81 | ``` 82 | 83 | ## 注意事项 84 | 85 | - `context` 仅负责取消信号的传播,不携带附加信息。若需要携带错误码或取消原因,请在业务代码中自行维护。 86 | - 上下文内部使用 `notify` 唤醒等待者;在没有协程等待时调用 `cancel()` 也会正确记录状态,随后等待者会立即返回。 87 | - 取消回调必须是可重入的,且需要自行保证并发调用时的幂等性。 88 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Left 7 | AlignOperands: true 8 | AllowAllArgumentsOnNextLine: true 9 | AllowAllConstructorInitializersOnNextLine: true 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLoopsOnASingleLine: true 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: Yes 19 | BinPackArguments: true 20 | BinPackParameters: true 21 | BraceWrapping: 22 | AfterCaseLabel: false 23 | AfterClass: false 24 | AfterControlStatement: Never 25 | AfterEnum: false 26 | AfterFunction: false 27 | AfterNamespace: false 28 | AfterStruct: false 29 | AfterUnion: false 30 | BeforeCatch: false 31 | BeforeElse: false 32 | SplitEmptyFunction: false 33 | SplitEmptyRecord: false 34 | SplitEmptyNamespace: false 35 | BreakBeforeBinaryOperators: NonAssignment 36 | BreakBeforeBraces: Custom 37 | BreakBeforeInheritanceComma: false 38 | PenaltyBreakBeforeFirstCallParameter: 0 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeComma 41 | BreakStringLiterals: false 42 | ColumnLimit: 110 43 | CompactNamespaces: false 44 | ConstructorInitializerIndentWidth: 8 45 | ContinuationIndentWidth: 4 46 | DerivePointerAlignment: false 47 | FixNamespaceComments: true 48 | IncludeBlocks: Preserve 49 | IndentCaseLabels: false 50 | IndentPPDirectives: AfterHash 51 | IndentWidth: 4 52 | IndentWrappedFunctionNames: false 53 | KeepEmptyLinesAtTheStartOfBlocks: false 54 | Language: Cpp 55 | MaxEmptyLinesToKeep: 1 56 | NamespaceIndentation: None 57 | PointerAlignment: Right 58 | ReflowComments: true 59 | SortIncludes: true 60 | SortUsingDeclarations: true 61 | SpaceAfterCStyleCast: false 62 | SpaceAfterLogicalNot: false 63 | SpaceAfterTemplateKeyword: false 64 | SpaceBeforeAssignmentOperators: true 65 | SpaceBeforeCpp11BracedList: false 66 | SpaceBeforeCtorInitializerColon: true 67 | SpaceBeforeInheritanceColon: true 68 | SpaceBeforeParens: ControlStatements 69 | SpaceBeforeRangeBasedForLoopColon: true 70 | SpaceInEmptyParentheses: false 71 | SpacesBeforeTrailingComments: 2 72 | SpacesInAngles: false 73 | SpacesInContainerLiterals: true 74 | SpacesInCStyleCastParentheses: false 75 | SpacesInParentheses: false 76 | SpacesInSquareBrackets: false 77 | Standard: Latest 78 | TabWidth: 4 79 | UseTab: Never 80 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main, remake] 6 | pull_request: 7 | branches: [main, remake] 8 | workflow_dispatch: {} 9 | 10 | permissions: 11 | contents: read 12 | packages: read 13 | 14 | concurrency: 15 | group: ci-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | build-and-test: 20 | name: Build and Test (${{ matrix.name }}) 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | include: 27 | - name: arch 28 | image: ghcr.io/pointertobios/asco-build:arch-llvm 29 | 30 | container: 31 | image: ${{ matrix.image }} 32 | credentials: 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | options: --user 0:0 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: Toolchain versions 42 | shell: bash 43 | run: | 44 | whoami || true 45 | clang --version || true 46 | cmake --version || true 47 | ninja --version || true 48 | ccache --version || true 49 | 50 | - name: Checkout source 51 | uses: actions/checkout@v4 52 | with: 53 | fetch-depth: 0 54 | submodules: recursive 55 | ref: ${{ github.ref }} 56 | 57 | - name: Restore ccache 58 | uses: actions/cache@v4 59 | with: 60 | path: ~/.cache/ccache 61 | key: ccache-${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 62 | restore-keys: | 63 | ccache-${{ runner.os }}-${{ matrix.name }}- 64 | 65 | - name: Configure 66 | shell: bash 67 | run: | 68 | cmake -S . -B build -G Ninja \ 69 | -DCMAKE_BUILD_TYPE=Release \ 70 | -DCMAKE_C_COMPILER="${CC:-clang}" \ 71 | -DCMAKE_CXX_COMPILER="${CXX:-clang++}" \ 72 | -DCMAKE_C_COMPILER_LAUNCHER=ccache \ 73 | -DCMAKE_CXX_COMPILER_LAUNCHER=ccache 74 | 75 | - name: Build 76 | shell: bash 77 | run: cmake --build build -j"$(nproc || getconf _NPROCESSORS_ONLN || echo 2)" 78 | 79 | - name: Test 80 | shell: bash 81 | run: ctest --test-dir build --output-on-failure 82 | 83 | # 上传产物(可根据实际项目调整) 84 | # - name: Upload build artifacts 85 | # if: always() 86 | # uses: actions/upload-artifact@v4 87 | # with: 88 | # name: build-${{ matrix.name }}-${{ github.sha }} 89 | # path: | 90 | # build/** 91 | # !build/**/*.o 92 | # !build/**/*.obj 93 | -------------------------------------------------------------------------------- /asco/core/runtime.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace asco::core { 18 | 19 | using namespace asco::types; 20 | 21 | struct runtime_builder { 22 | std::unique_ptr timer{ 23 | std::make_unique(std::make_unique())}; 24 | size_t parallel{0}; 25 | }; 26 | 27 | class runtime { 28 | public: 29 | static runtime &init(runtime_builder &&builder) noexcept; 30 | static runtime &this_runtime() noexcept; 31 | 32 | time::timer_concept &timer() noexcept; 33 | 34 | task_id alloc_task_id() noexcept { return task_id_generator.fetch_add(1, morder::acq_rel); } 35 | 36 | void register_task(task_id id, std::shared_ptr> task); 37 | void unregister_task(task_id id); 38 | std::shared_ptr> get_task_by(std::coroutine_handle<> handle); 39 | std::shared_ptr> get_task_by(task_id id); 40 | task_id get_task_id_by(std::coroutine_handle<> handle); 41 | 42 | void spawn_task(task_id id, std::shared_ptr> task); 43 | void spawn_core_task(task_id id, std::shared_ptr> task); 44 | 45 | void awake_all(); 46 | void awake_io_worker_once(); 47 | void awake_calcu_worker_once(); 48 | 49 | private: 50 | explicit runtime(size_t parallel, std::unique_ptr &&timer_ptr); 51 | ~runtime(); 52 | 53 | rwspin>>> tasks_by_id; 54 | rwspin, std::shared_ptr>>> tasks_by_handle; 55 | rwspin, task_id>> task_ids_by_handle; 56 | 57 | atomic_size_t worker_count{0}; 58 | atomic_bool shutting_down{false}; 59 | std::vector> workers; 60 | size_t calcu_worker_count{0}; 61 | atomic_size_t calcu_worker_load{0}; 62 | size_t io_worker_count{0}; 63 | atomic_size_t io_worker_load{0}; 64 | 65 | task_sender io_task_tx; 66 | awake_queue io_worker_queue; 67 | task_sender calcu_task_tx; 68 | awake_queue calcu_worker_queue; 69 | 70 | atomic_size_t task_id_generator{1}; 71 | 72 | std::unique_ptr _timer; 73 | 74 | static runtime *_this_runtime; 75 | }; 76 | 77 | inline runtime *runtime::_this_runtime{nullptr}; 78 | 79 | }; // namespace asco::core 80 | -------------------------------------------------------------------------------- /docs/zh-cn/src/generator.md: -------------------------------------------------------------------------------- 1 | # 协程生成器 `generator` 2 | 3 | `generator` 是 ASCO 中基于 C++ 协程实现的懒序列。它继承自 `future_base`,因此可以像 `future` 一样被调度,但每次 `co_yield` 会把一个元素推入内部队列,供消费端按需提取。 4 | 5 | ## 设计概览 6 | 7 | - **逐项产出**:生成器协程通过 `co_yield` 把值送入 `continuous_queue`,调用方每次等待一个元素。 8 | - **基于 `future`**:`generator` 依旧是异步任务,`operator()` 返回另一个 `future>`,因此可以在任何 `co_await` 环境里消费。 9 | - **跨线程安全**:内部通过信号量与无锁队列保证并发安全,能够在不同工作线程间生成与消费。 10 | - **结束语义**:当协程 `co_return` 或 `co_yield` 通道关闭时,`operator()` 会返回 `std::nullopt`,并且 `operator bool()` 变为 `false`。 11 | 12 | ## 基本流程 13 | 14 | ```cpp 15 | #include 16 | #include 17 | 18 | using asco::future; 19 | using asco::generator; 20 | 21 | // 生产 1..n 的序列 22 | generator gen_count(int n) { 23 | for (int i = 1; i <= n; ++i) { 24 | co_yield i; 25 | } 26 | co_return; 27 | } 28 | 29 | future consume(generator& g) { 30 | int sum = 0; 31 | while (auto i = co_await g()) { // 每次等待一个元素 32 | sum += *i; 33 | } 34 | // generator 被耗尽或停止时跳出循环 35 | co_return; 36 | } 37 | ``` 38 | 39 | 调用 `g()` 返回一个可等待的 future,获取到 `std::optional`。当生成器结束时返回 `std::nullopt`,表明序列已经耗尽。 40 | 41 | ## 组合消费 (`operator|`) 42 | 43 | `generator` 支持通过 `operator|` 与异步函数拼接,形成更简洁的管道式写法。`consumer` 需要是一个返回 `future` 的函数,并接受生成器作为参数。 44 | 45 | ```cpp 46 | future consume_sum(generator& g) { 47 | int sum = 0; 48 | while (auto item = co_await g()) { 49 | sum += *item; 50 | } 51 | co_return sum; 52 | } 53 | 54 | future async_main() { 55 | auto g = gen_count(5); 56 | 57 | // 传统写法 58 | auto sum1 = co_await consume_sum(g); 59 | 60 | // 管道写法,会自动调用 co_invoke(consumer, generator) 61 | auto g2 = gen_count(5); 62 | auto sum2 = co_await (g2 | consume_sum); 63 | 64 | co_return; 65 | } 66 | ``` 67 | 68 | 该操作符同样支持右值生成器,内部会移动到消费函数中: 69 | 70 | ```cpp 71 | auto total = co_await (gen_count(5) | [](auto gen) -> future { 72 | int sum = 0; 73 | while (auto item = co_await gen()) { 74 | sum += *item; 75 | } 76 | co_return sum; 77 | }); 78 | 79 | // 消费函数还可以返回新的 generator/generator_core,实现链式加工 80 | auto doubled = co_await (gen_count(3) | [](auto gen) -> generator { 81 | while (auto item = co_await gen()) { 82 | co_yield *item * 2; 83 | } 84 | co_return; 85 | }); 86 | ``` 87 | 88 | ## 异常与停止 89 | 90 | - 生成器内部抛出的异常会在下一次 `co_await generator()` 时重新抛出,调用方可以像处理普通异步任务一样捕获。 91 | - 调用生成器的 `operator bool()` 可以检测它是否仍然可继续产出值。 92 | - 一旦协程结束或调用方显式停止(例如销毁生成器),内部队列会关闭,后续获取将立即返回 `std::nullopt`。 93 | 94 | ## 与运行时的关系 95 | 96 | `generator` 的调度由 ASCO 运行时处理。和 `future` 一样,需要在 `async_main` 或其他运行时任务中 `co_await`,以确保协程被调度执行。若要在核心 runtime 上下文内使用,可以改用 `generator_core`,它会绑定到核心执行器。 97 | -------------------------------------------------------------------------------- /tests/test_generator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using asco::future; 11 | using asco::future_spawn; 12 | using asco::generator; 13 | 14 | generator gen_count(int n) { 15 | for (int i = 1; i <= n; ++i) { co_yield i; } 16 | co_return; 17 | } 18 | 19 | generator gen_then_throw() { 20 | co_yield 42; 21 | throw std::runtime_error("boom"); 22 | } 23 | 24 | future consume_sum(generator &g) { 25 | int sum = 0; 26 | while (auto e = co_await g()) { // 27 | sum += *e; 28 | } 29 | co_return sum; 30 | } 31 | 32 | future consume_sum_by_value(generator g) { 33 | int sum = 0; 34 | while (auto e = co_await g()) { sum += *e; } 35 | co_return sum; 36 | } 37 | 38 | future async_main() { 39 | { 40 | auto g = gen_count(1000); 41 | auto sum = co_await consume_sum(g); 42 | asco_assert(sum == 1000 * 1001 / 2); 43 | asco_assert(!g); 44 | std::println("test_generator: full generation passed"); 45 | } 46 | 47 | { 48 | auto g = gen_then_throw(); 49 | auto first = co_await g(); 50 | asco_assert(first == 42); 51 | bool got_exc = false; 52 | try { 53 | (void)co_await g(); 54 | } catch (const std::exception &) { got_exc = true; } 55 | asco_assert(got_exc); 56 | asco_assert(!g); 57 | std::println("test_generator: exception handling passed"); 58 | } 59 | 60 | { 61 | auto g1 = gen_count(10); 62 | for (int i = 1; i <= 3; ++i) { 63 | auto e = co_await g1(); 64 | asco_assert(*e == i); 65 | } 66 | auto g2 = std::move(g1); 67 | int next_expected = 4; 68 | while (auto e = co_await g2()) { // 69 | asco_assert(*e == next_expected++); 70 | } 71 | asco_assert(next_expected == 11); 72 | std::println("test_generator: move semantics passed"); 73 | } 74 | 75 | { 76 | auto g = gen_count(8); 77 | auto piped = g | [](generator gen) -> future { 78 | int sum = 0; 79 | while (auto e = co_await gen()) { // 80 | sum += *e; 81 | } 82 | co_return sum; 83 | }; 84 | auto sum = co_await piped; 85 | asco_assert(sum == 8 * 9 / 2); 86 | asco_assert(!g); 87 | std::println("test_generator: pipe with lvalue passed"); 88 | } 89 | 90 | { 91 | auto sum = co_await (gen_count(5) | consume_sum_by_value); 92 | asco_assert(sum == 5 * 6 / 2); 93 | std::println("test_generator: pipe with rvalue passed"); 94 | } 95 | 96 | co_return 0; 97 | } 98 | -------------------------------------------------------------------------------- /tests/test_join_set.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace asco; 13 | using namespace std::chrono_literals; 14 | 15 | future async_main() { 16 | std::println("test_join_set: start"); 17 | 18 | // Test A: results follow completion order 19 | { 20 | join_set set; 21 | const std::array, 3> tasks{{ 22 | {0, 40ms}, 23 | {1, 10ms}, 24 | {2, 20ms}, 25 | }}; 26 | 27 | for (auto [value, delay] : tasks) { 28 | set.spawn([value, delay]() -> future { 29 | co_await sleep_for(delay); 30 | co_return value; 31 | }) 32 | .ignore(); 33 | } 34 | 35 | auto results_gen = set.join(); 36 | std::vector observed; 37 | while (auto value = co_await results_gen()) { observed.push_back(*value); } 38 | 39 | const std::vector expected{1, 2, 0}; 40 | if (observed != expected) { 41 | std::println( 42 | "test_join_set: A FAILED - expected completion order [1,2,0], got size {}", observed.size()); 43 | for (size_t idx = 0; idx < observed.size(); ++idx) { 44 | std::println(" observed[{}] = {}", idx, observed[idx]); 45 | } 46 | co_return 1; 47 | } 48 | std::println("test_join_set: A passed"); 49 | } 50 | 51 | // Test B: supports functions returning future_spawn 52 | { 53 | join_set set; 54 | set.spawn([]() -> future_spawn { co_return 7; }).ignore(); 55 | set.spawn([]() -> future_spawn { 56 | co_await sleep_for(1ms); 57 | co_return 13; 58 | }) 59 | .ignore(); 60 | 61 | auto results_gen = set.join(); 62 | std::vector observed; 63 | while (auto value = co_await results_gen()) { observed.push_back(*value); } 64 | 65 | if (observed.size() != 2 || observed[0] != 7 || observed[1] != 13) { 66 | std::println("test_join_set: B FAILED - expected [7,13], got size {}", observed.size()); 67 | co_return 1; 68 | } 69 | std::println("test_join_set: B passed"); 70 | } 71 | 72 | // Test C: join on empty set yields nothing 73 | { 74 | join_set set; 75 | auto results_gen = set.join(); 76 | if (auto value = co_await results_gen()) { 77 | std::println("test_join_set: C FAILED - expected empty join, got {}", *value); 78 | co_return 1; 79 | } 80 | std::println("test_join_set: C passed"); 81 | } 82 | 83 | std::println("test_join_set: all checks passed"); 84 | co_return 0; 85 | } 86 | -------------------------------------------------------------------------------- /asco/core/time/high_resolution_timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace asco::core::time { 10 | 11 | high_resolution_timer::high_resolution_timer() 12 | : daemon{"asco::timer"} { 13 | auto _ = daemon::start(); 14 | } 15 | 16 | bool high_resolution_timer::init() { return true; } 17 | 18 | bool high_resolution_timer::run_once(std::stop_token &) { 19 | if (timer_tree.lock()->empty()) { 20 | sleep_until_awake(); 21 | return true; 22 | } 23 | 24 | timer_entry e = ({ 25 | alignas(alignof(timer_entry)) char res[sizeof(timer_entry)]; 26 | if (auto g = timer_tree.lock()) { 27 | if (auto &es = g->begin()->second.entries; !es.empty()) { 28 | new (&res) timer_entry{es.begin()->second}; 29 | } else { 30 | g->erase(g->begin()); 31 | return true; 32 | } 33 | } 34 | *reinterpret_cast(&res); 35 | }); 36 | sleep_until_awake_before(e.expire_time); 37 | if (std::chrono::high_resolution_clock::now() >= e.expire_time) { 38 | e.worker_ref.activate_task(e.tid); 39 | if (auto g = timer_tree.lock(); g->contains(e.expire_seconds_since_epoch())) { 40 | if (auto &es = g->at(e.expire_seconds_since_epoch()).entries; es.contains(e.id())) 41 | es.erase(e.id()); 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | void high_resolution_timer::shutdown() {} 49 | 50 | timer_id high_resolution_timer::register_timer( 51 | const std::chrono::high_resolution_clock::time_point &expire_time, worker &worker_ref, task_id tid) { 52 | timer_entry entry{expire_time, worker_ref, tid}; 53 | auto id = entry.id(); 54 | 55 | auto guard = timer_tree.lock(); 56 | if (auto it = guard->find(entry.expire_seconds_since_epoch()); it != guard->end()) { 57 | auto &[_, area] = *it; 58 | area.entries.emplace(id, std::move(entry)); 59 | } else { 60 | timer_area area{entry.expire_seconds_since_epoch()}; 61 | area.entries.emplace(id, std::move(entry)); 62 | guard->emplace(area.earliest_expire_time, std::move(area)); 63 | } 64 | 65 | awake(); 66 | 67 | return id; 68 | } 69 | 70 | void high_resolution_timer::unregister_timer(timer_id id) { 71 | auto expire_seconds = 72 | std::chrono::duration_cast(std::chrono::nanoseconds{id.expire_time_nanos}) 73 | .count(); 74 | 75 | if (auto g = timer_tree.lock(); g->contains(expire_seconds)) { 76 | auto &es = g->at(expire_seconds).entries; 77 | if (auto it = es.find(id); it != es.end()) { 78 | auto &e = it->second; 79 | e.worker_ref.activate_task(e.tid); 80 | es.erase(it); 81 | } 82 | } 83 | } 84 | 85 | }; // namespace asco::core::time 86 | -------------------------------------------------------------------------------- /asco/compile_time/platform.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | namespace asco::compile_time::platform { 7 | 8 | #ifdef linux 9 | # undef linux 10 | #endif 11 | 12 | namespace types { 13 | 14 | enum class machine { 15 | x86_64, 16 | i386, 17 | aarch64, 18 | arm, 19 | ppc64, 20 | ppc, 21 | mips, 22 | loongarch64, 23 | loongarch, 24 | }; 25 | 26 | enum class os { 27 | linux, 28 | windows, 29 | apple, 30 | }; 31 | 32 | enum class compiler { 33 | gcc, 34 | clang, 35 | msvc, 36 | clang_cl, 37 | }; 38 | 39 | }; // namespace types 40 | 41 | struct platform { 42 | constexpr static types::machine _machine = 43 | #if defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) 44 | types::machine::x86_64 45 | #elif defined(__i386__) || defined(__i386) || defined(_M_IX86) 46 | types::machine::i386 47 | #elif defined(__aarch64__) || defined(_M_ARM64) 48 | types::machine::aarch64 49 | #elif defined(__arm__) || defined(_M_ARM) 50 | types::machine::arm 51 | #elif defined(__powerpc64__) 52 | types::machine::ppc64 53 | #elif defined(__powerpc__) 54 | types::machine::ppc 55 | #elif defined(__mips__) 56 | types::machine::mips 57 | #elif defined(__loongarch__) 58 | # if _LOONGARCH_SZPTR == 64 59 | types::machine::loongarch64 60 | # elif _LOONGARCH_SZPTR == 32 61 | types::machine::loongarch 62 | # endif 63 | #else 64 | # error [ASCO] Current machine type is not considered. 65 | #endif 66 | ; 67 | consteval static types::machine machine() { return _machine; } 68 | consteval static bool machine_is(types::machine m) { return m == _machine; } 69 | 70 | constexpr static types::os _os = 71 | #if defined(__linux__) || defined(__linux) 72 | types::os::linux 73 | #elif defined(_WIN32) || defined(_WIN64) 74 | types::os::windows 75 | #elif defined(__APPLE__) 76 | types::os::apple 77 | #else 78 | # error [ASCO] Current OS is not considered. 79 | #endif 80 | ; 81 | consteval static types::os os() { return _os; } 82 | consteval static bool os_is(types::os o) { return o == _os; } 83 | 84 | constexpr static types::compiler _compiler = 85 | #if defined(__clang__) 86 | types::compiler::clang 87 | #elif defined(_MSC_VER) 88 | # if defined(__clang__) 89 | types::compiler::clang_cl 90 | # else 91 | types::compiler::msvc 92 | # error [ASCO] Compile with clang-cl instead of MSVC 93 | # endif 94 | #elif defined(__GNUC__) 95 | types::compiler::gcc 96 | #else 97 | # error [ASCO] Current compiler is not considered. 98 | #endif 99 | ; 100 | consteval static types::compiler compiler() { return _compiler; } 101 | 102 | consteval static int bit_width() { return sizeof(void *) * 8; } 103 | }; 104 | 105 | using namespace types; 106 | 107 | }; // namespace asco::compile_time::platform 108 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/semaphore.md: -------------------------------------------------------------------------------- 1 | # Semaphore(信号量) 2 | 3 | `asco::binary_semaphore`、`asco::counting_semaphore` 与 `asco::unlimited_semaphore` 提供了计数型同步原语,可用于限流、互斥(简化版)与事件通知。 4 | 5 | - 头文件:`#include ` 6 | - 命名空间:`asco`(类型别名定义在全局 `asco` 命名空间下) 7 | 8 | ## 类型与别名 9 | 10 | - `binary_semaphore`:上限为 1 的二值信号量。 11 | - `template counting_semaphore`:上限为 `N` 的计数信号量。 12 | - `unlimited_semaphore`:上限为 `size_t` 的最大值,可视为“无上限”。 13 | 14 | 底层实现均基于 `semaphore_base`。 15 | 16 | ## 接口 17 | 18 | - `bool try_acquire() noexcept` 19 | - 若计数大于 0,则原子地减 1 并返回 `true`;否则返回 `false`。 20 | - `future acquire()` 21 | - 若计数为 0,则当前协程挂起,直到被 `release()` 唤醒;恢复后原子地减 1。 22 | - `future acquire_for(const duration_type auto &timeout)` 23 | - 尝试在指定超时时间内获取许可,超时则返回 `false`。 24 | - `future acquire_until(const time_point_type auto &expire_time)` 25 | - 尝试在指定时间点前获取许可,超时则返回 `false`。 26 | - `void release(size_t update = 1)` 27 | - 将计数增加 `min(update, CountMax - old_count)`,并唤醒相应数量的等待者。 28 | 29 | 特性: 30 | 31 | - `acquire()` 以协程方式挂起,不阻塞线程。 32 | - `release()` 支持一次性增加多个许可;对上限做饱和值裁剪。 33 | - 公平性:不保证严格公平,但能够唤醒同等数量等待者。 34 | 35 | ## 使用示例 36 | 37 | ### 1. 事件通知(先通知,后等待) 38 | 39 | ```cpp 40 | #include 41 | #include 42 | using namespace asco; 43 | 44 | future async_main() { 45 | binary_semaphore sem{0}; 46 | 47 | // 先通知 48 | sem.release(); 49 | 50 | // 后等待:不会永久挂起 51 | co_await sem.acquire(); 52 | co_return 0; 53 | } 54 | ``` 55 | 56 | ### 2. 等待后再通知(跨任务) 57 | 58 | ```cpp 59 | #include 60 | #include 61 | #include 62 | using namespace asco; 63 | 64 | future_spawn worker(binary_semaphore &sem, std::atomic &done) { 65 | co_await sem.acquire(); 66 | done.store(true, std::memory_order::release); 67 | co_return; 68 | } 69 | 70 | future async_main() { 71 | binary_semaphore sem{0}; 72 | std::atomic done{false}; 73 | 74 | auto w = worker(sem, done); 75 | 76 | // 使等待中的任务恢复 77 | sem.release(); 78 | co_await w; 79 | 80 | co_return done.load(std::memory_order::acquire) ? 0 : 1; 81 | } 82 | ``` 83 | 84 | ### 3. 高并发场景(多次 release / acquire) 85 | 86 | ```cpp 87 | #include 88 | #include 89 | #include 90 | using namespace asco; 91 | 92 | future_spawn consumer(counting_semaphore<1000> &sem, std::atomic &cnt) { 93 | for (size_t i = 0; i < 1000; ++i) { 94 | co_await sem.acquire(); 95 | cnt.fetch_add(1, std::memory_order::relaxed); 96 | } 97 | co_return; 98 | } 99 | 100 | future async_main() { 101 | counting_semaphore<1000> sem{0}; 102 | std::atomic cnt{0}; 103 | 104 | auto c = consumer(sem, cnt); 105 | 106 | for (size_t i = 0; i < 1000; ++i) sem.release(); 107 | 108 | co_await c; 109 | co_return cnt.load(std::memory_order::relaxed) == 1000 ? 0 : 1; 110 | } 111 | ``` 112 | 113 | ## 注意事项 114 | 115 | - `release()` 的 `update` 值会根据上限裁剪,避免溢出。 116 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/rwlock.md: -------------------------------------------------------------------------------- 1 | # RWLock(协程读写锁) 2 | 3 | `asco::sync::rwlock<>` 与 `asco::sync::rwlock` 提供读多写独的协程同步原语。读操作可以并行获取共享锁,写操作以独占方式访问,被等待的协程会自动通过内部 `wait_queue` 挂起,不会阻塞线程。 4 | 5 | - 头文件:`#include ` 6 | - 命名空间:在全局命名空间下别名为 `asco::rwlock` 7 | 8 | ## 类型概览 9 | 10 | - `rwlock<>`:纯读写锁,不附带存储,仅管理并发访问。 11 | - `template rwlock`:内部封装一个 `T` 实例,读锁以常量视图访问,写锁以可变引用操作对象。 12 | 13 | 两种读写锁都分别提供 `read()` 与 `write()`,返回 `future` 与 `future`,需配合 `co_await` 使用。 14 | 15 | ## 接口 16 | 17 | ### `rwlock<>` 18 | 19 | - `future read()`:获取共享读锁。写者占用或排队时会自旋退避,随后挂起到读者等待队列 `rq`。 20 | - `future write()`:获取独占写锁。尝试设置写意图位,等待所有读者释放并挂起到写者等待队列 `wq`。 21 | - `class read_guard` 22 | - `operator bool() const noexcept`:指示守卫是否仍有效;移动后原守卫失效。 23 | - 析构时减少读者计数,当最后一个读者离开时唤醒写者。 24 | - `class write_guard` 25 | - `operator bool() const noexcept`:移动后原守卫失效。 26 | - 析构时清除写标志,并唤醒排队写者与阻塞读者。 27 | 28 | ### `rwlock` 29 | 30 | - `future read()`:取得常量读守卫,可直接访问 `const T`。 31 | - `future write()`:取得可写守卫,支持修改内部对象。 32 | - `T &&get()`:在确认没有其他持有者时将内部值移动出来,后续任何 `read()` / `write()` 都会触发 panic,适合一次性转移所有权的场景。 33 | - `class read_guard` 34 | - `const T &operator*() const` / `const T *operator->() const`:常量访问封装对象。 35 | - `class write_guard` 36 | - `T &operator*()` / `T *operator->()`:可变访问封装对象。 37 | 38 | ## 行为特性 39 | 40 | - **读写公平**:写协程在进入等待队列时会设置写意图,后续读者会挂起到 `rq` 等待写者释放,避免写者无限饥饿。 41 | - **协程友好**:所有等待通过 `co_await` 表达,不会阻塞线程。内部结合指数退避与 `wait_queue`,在低争用时保持轻量,在高争用时自动挂起。 42 | - **守卫语义**:守卫可移动但不可复制;移动后原对象变为无效状态(`operator bool()` 返回 `false`)。 43 | 44 | ## 示例 45 | 46 | ### 1. 在多个协程间共享配置快照 47 | 48 | ```cpp 49 | #include 50 | #include 51 | #include 52 | #include 53 | using namespace asco; 54 | 55 | rwlock config{"v1"}; 56 | 57 | future update_config(std::string next) { 58 | with (auto guard = co_await config.write()) { 59 | *guard = std::move(next); 60 | } 61 | co_return; 62 | } 63 | 64 | future read_config() { 65 | with (auto guard = co_await config.read()) { 66 | co_return *guard; // 复制快照 67 | } 68 | } 69 | ``` 70 | 71 | ### 2. 读者并发、写者独占 72 | 73 | ```cpp 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | using namespace asco; 80 | 81 | rwlock<> resource_lock; 82 | 83 | future_spawn reader_task() { 84 | with (auto guard = co_await resource_lock.read()) { 85 | // 多个读者可以同时进入 86 | } 87 | co_return; 88 | } 89 | 90 | future_spawn writer_task() { 91 | with (auto guard = co_await resource_lock.write()) { 92 | // 唯一写者,读者与其他写者都会等待 93 | } 94 | co_return; 95 | } 96 | ``` 97 | 98 | ## 注意事项 99 | 100 | - 写守卫析构后会先唤醒其它写者,再唤醒读者;若需要严格保证写者优先,可在业务层面自行排队调度。 101 | - `rwlock::get()` 会永久标记内部对象已被移走,后续任意访问都会触发 panic,用于显式暴露误用。 102 | - `rwlock<>` 不提供递归锁语义;请避免在持有写锁期间再次请求读锁或写锁。 103 | - 推荐结合 `with` 宏使用,避免手动检查守卫有效性并确保作用域结束即释放锁。 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASCO 2 | 3 | [![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md) 4 | 5 | C++20 coroutine based async framework (***C++23*** needed). 6 | 7 | ## Getting Started 8 | 9 | ```c++ 10 | #include 11 | #include 12 | 13 | using asco::future, asco::future_spawn; 14 | 15 | future non_spawn_foo() { 16 | co_return 42; 17 | } 18 | 19 | future_spawn spawn_bar() { 20 | std::println("Calling spawn coroutine"); 21 | return; 22 | } 23 | 24 | future async_main() { 25 | std::println("Hello, World!", co_await non_spawn_foo()); 26 | co_await spawn_bar(); 27 | co_return 0; 28 | } 29 | ``` 30 | 31 | ### Documentations 32 | 33 | - [简体中文](https://pointertobios.github.io/asco/zhcn/) 34 | 35 | ### Import into your project 36 | 37 | #### Use as submodule 38 | 39 | 1. Clone one of this repository's version tag. 40 | 2. Use with cmake: 41 | 42 | - Static link: 43 | 44 | ```cmake 45 | add_subdirectory() 46 | target_link_libraries( PRIVATE asco::core asco::main) 47 | ``` 48 | 49 | - Dynamic link: 50 | 51 | ```cmake 52 | add_subdirectory() 53 | target_link_libraries( PRIVATE asco::shared::core asco::shared::main) 54 | ``` 55 | 56 | #### Use with cmake FetchContent_Declare() 57 | 58 | - How to import 59 | 60 | ```cmake 61 | include(FetchContent) 62 | 63 | FetchContent_Declare( 64 | asco 65 | GIT_REPOSITORY https://github.com/pointertobios/asco.git 66 | GIT_TAG 67 | ) 68 | FetchContent_MakeAvailable(asco) 69 | 70 | ``` 71 | 72 | - Static link: 73 | 74 | ```cmake 75 | target_link_libraries( PRIVATE asco::core asco::main) 76 | ``` 77 | 78 | - Dynamic link: 79 | 80 | ```cmake 81 | target_link_libraries( PRIVATE asco::shared::core asco::shared::main) 82 | ``` 83 | 84 | ## Features 85 | 86 | - [ ] Basic async runtime 87 | - [ ] Platform support 88 | - [x] Linux full support 89 | - [ ] Windows full support 90 | - [ ] MacOS full support 91 | - [x] Runtime core 92 | - [x] Worker thread pool 93 | - [x] Task scheduling 94 | - [ ] Task stealing 95 | - [ ] Timers 96 | - [x] High resolution timer 97 | - [ ] Timer wheel 98 | - [x] Cancellation with context 99 | - [ ] IO 100 | - [ ] io_uring (Linux) 101 | - [ ] Epoll (Linux) 102 | - [ ] IOCP (Windows) 103 | - [ ] Kqueue (MacOS) (Wait please) 104 | - [ ] Basic tools for concurrent programming 105 | - [ ] Sync Primitives 106 | - [x] Spin 107 | - [x] Semaphore 108 | - [x] Channel 109 | - [x] Mutex 110 | - [x] Read-Write Lock 111 | - [ ] Condition variable 112 | - [ ] Barrier 113 | - [ ] Latch 114 | - [x] Sleep and Interval 115 | - [x] Join set 116 | - [ ] Select 117 | - [ ] Async IO 118 | - [ ] Async file IO 119 | - [ ] Async TCP/UDP stream 120 | - [ ] Buffered IO 121 | - [ ] No lock data structures and parallel algorithms 122 | - [x] continuous_queue 123 | - [ ] ring_queue 124 | - [ ] More to come... 125 | 126 | ## Compilers 127 | 128 | - Clang: Fully supported. 129 | - GCC: Not complete. 130 | - MSVC: Not supported. 131 | 132 | ## Development & Contributing 133 | 134 | See [CONTRIBUTING.md](./CONTRIBUTING.md) for details. 135 | 136 | ## License 137 | 138 | This project is licensed under the **MIT License**. 139 | 140 | Copyright (C) 2025 pointer-to-bios 141 | 142 | For full legal terms, see the [LICENSE](./LICENSE.md) file. 143 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/mutex.md: -------------------------------------------------------------------------------- 1 | # Mutex(协程互斥锁) 2 | 3 | `asco::sync::mutex<>` 与 `asco::sync::mutex` 提供协程友好的互斥保护能力。它们以 `future` 形式返回守卫对象,利用 RAII 在离开作用域后自动释放锁,避免线程级别阻塞。 4 | 5 | - 头文件:`#include ` 6 | - 命名空间:在全局命名空间下别名为 `asco::mutex` 7 | 8 | ## 类型概览 9 | 10 | - `mutex<>`:纯互斥锁,不携带值,仅提供排他访问。 11 | - `template mutex`:在互斥锁内部封装一个 `T` 实例,锁定后可通过守卫直接访问 `T`。 12 | 13 | 两种互斥锁的 `lock()` 都返回 `future`,需使用 `co_await` 获取守卫。 14 | 15 | ## 接口 16 | 17 | ### `mutex<>` 18 | 19 | - `future lock()`:等待获得互斥锁。协程在竞争失败时会自旋退避,必要时挂起在内部 `wait_queue` 上。 20 | - `class guard` 21 | - `guard::operator bool() const noexcept`:指示守卫是否仍持有锁,被移动后的守卫返回 `false`。 22 | - 析构时自动释放锁并唤醒等待者。 23 | 24 | ### `mutex` 25 | 26 | - `future lock()`:同上,但守卫允许直接操作封装的 `T`。 27 | - `class guard` 28 | - `T &operator*()` / `T *operator->()`:访问被保护的对象。 29 | - 移动构造后,原守卫失效,新守卫继续持有锁。 30 | - `T &&get()`:将内部对象移动出互斥锁,并在后续 `lock()` 时触发 panic,适用于需要一次性夺取所有权的场景。 31 | 32 | ## 推荐用法:`with` 宏 33 | 34 | 提供 `with` 宏(在 `asco/utils/defines.h` 中定义为 `if`): 35 | 36 | ```cpp 37 | #include 38 | #include 39 | using namespace asco; 40 | 41 | sync::mutex<> mtx; 42 | 43 | future do_work() { 44 | with (auto guard = co_await mtx.lock()) { 45 | // 成功获取锁后执行,作用域结束自动解锁 46 | } 47 | co_return; 48 | } 49 | ``` 50 | 51 | `with` 在语义上等同于 `if (auto guard = ...; guard) { ... }`,保证代码结构清晰且不会遗漏解锁。 52 | 53 | ## 示例 54 | 55 | ### 1. 保护共享计数器 56 | 57 | ```cpp 58 | #include 59 | #include 60 | #include 61 | #include 62 | using namespace asco; 63 | 64 | sync::mutex<> counter_mutex; 65 | int counter = 0; 66 | std::atomic active{0}; 67 | std::atomic violations{0}; 68 | 69 | future_spawn worker() { 70 | with (auto guard = co_await counter_mutex.lock()) { 71 | auto prev = active.fetch_add(1, std::memory_order_acq_rel); 72 | if (prev != 0) { 73 | violations.fetch_add(1, std::memory_order_acq_rel); 74 | } 75 | ++counter; 76 | active.fetch_sub(1, std::memory_order_acq_rel); 77 | } 78 | co_return; 79 | } 80 | ``` 81 | 82 | ### 2. 保护对象并原地修改 83 | 84 | ```cpp 85 | #include 86 | #include 87 | #include 88 | #include 89 | using namespace asco; 90 | 91 | sync::mutex name{"guest"}; 92 | 93 | future rename(std::string new_name) { 94 | with (auto guard = co_await name.lock()) { 95 | *guard = std::move(new_name); 96 | } 97 | co_return; 98 | } 99 | 100 | future snapshot() { 101 | with (auto guard = co_await name.lock()) { 102 | co_return *guard; // 复制守卫中的字符串 103 | } 104 | } 105 | ``` 106 | 107 | ### 3. 彻底移交对象所有权 108 | 109 | ```cpp 110 | #include 111 | #include 112 | using namespace asco; 113 | 114 | sync::mutex> data; 115 | 116 | future> take_all() { 117 | with (auto guard = co_await data.lock()) { 118 | co_return data.get(); // 移动出内部向量 119 | } 120 | } 121 | 122 | future reuse() { 123 | co_await take_all(); 124 | // 再次锁定将触发 panic,提醒调用者对象已被移出 125 | // co_await take_all(); // panic 126 | co_return; 127 | } 128 | ``` 129 | 130 | ## 注意事项 131 | 132 | - 守卫是可移动但不可复制的;请勿在移动后继续使用旧守卫。 133 | - 如果 `mutex::get()` 被调用,内部会标记对象已被移动;继续 `lock()` 会触发 panic,用于显式暴露错误路径。 134 | - 互斥锁内部使用自旋退避与 `wait_queue` 协程挂起组合:在低争用场景中开销仅为原子操作,在高争用场景能避免忙等。 135 | - `mutex<>` 只保障互斥,不提供递归锁语义;请避免在持锁期间再次调用 `lock()`。 136 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/daemon.md: -------------------------------------------------------------------------------- 1 | # `asco::core::daemon`(守护线程基类) 2 | 3 | 本文档说明如何继承和使用 `asco::core::daemon` 类型来实现后台守护线程。 4 | 5 | ## 概述 6 | 7 | `asco::core::daemon` 是一个轻量的守护线程基类,封装了线程生命周期、初始化同步、以及唤醒/睡眠的常用逻辑。典型用法是继承该类并覆盖 `init()`、`run_once(std::stop_token &)` 和 `shutdown()` 三个虚方法来实现特定行为。 8 | 9 | 核心特性: 10 | 11 | - 使用 `std::jthread` 管理后台线程,支持基于 `std::stop_token` 的干净停止请求。 12 | - 提供 `awake()` / `sleep_until_awake*()` 系列方法用于线程间唤醒与有时限的等待。 13 | - 构造/启动/初始化通过 `start()` + 返回的 RAII `init_waiter` 实现线程初始化同步,确保启动方能等待后台线程完成 `init()`。 14 | - 析构函数会请求停止并等待后台线程安全退出。 15 | 16 | ## 公有 API(概要) 17 | 18 | - `daemon(std::string name)`:构造器,`name` 在 Linux 上用于线程命名(可读性和调试)。 19 | - `~daemon()`:析构函数,会请求线程停止、唤醒线程(以解除阻塞),并 `join()` 线程。 20 | - `void awake()`:释放内部信号量,唤醒调用 `sleep_until_awake()` 或其变体而阻塞的守护线程。 21 | 22 | 受保护的辅助接口(给子类使用): 23 | 24 | - `init_waiter start()`:启动后台线程并返回一个 `init_waiter`。`init_waiter` 的析构函数会等待后台线程释放 `init_sem`,因此可通过 RAII 在启动代码中等待初始化完成。 25 | - `sleep_until_awake()` / `sleep_until_awake_for(...)` / `sleep_until_awake_before(...)`:基于内部 `std::binary_semaphore` 的阻塞/有时限等待,用于在 `run_once` 中等待事件或超时。 26 | 27 | 虚方法(子类通常需要覆盖) 28 | 29 | - `virtual bool init()`:线程刚启动时调用一次。默认返回 `true`。返回 `false` 表示初始化失败,线程将调用 `shutdown()` 并退出。 30 | - `virtual bool run_once(std::stop_token &st)`:守护主循环中每次执行的工作。返回 `true` 表示继续循环,返回 `false` 或在 `stop_requested()` 为真时退出循环。默认实现会 `sleep_until_awake()` 并返回 `true`。 31 | - `virtual void shutdown()`:线程退出前的清理逻辑。 32 | 33 | ## 生命周期与典型启动模式 34 | 35 | 1. 在子类构造函数或初始化函数中调用 `start()`(受保护,因此通常在子类内部调用),它会创建后台 `jthread` 并立即返回一个 `init_waiter`。 36 | 2. 当 `init_waiter` 离开作用域时,其析构函数会阻塞直到后台线程完成 `init()` 并释放内部信号量,这样启动方就可以等待初始化完成。 37 | 3. 后台线程在循环中调用 `run_once(st)`,直到 `stop_requested()` 被置位或 `run_once` 返回 `false`。 38 | 4. 析构函数会发起 `request_stop()`,并调用 `awake()` 以解除阻塞,然后 `join()` 线程,保证线程安全退出。 39 | 40 | 示例(最小子类实现): 41 | 42 | ```cpp 43 | class my_daemon : public asco::core::daemon { 44 | public: 45 | my_daemon() : daemon("my_daemon") { 46 | auto waiter = start(); // 启动线程并在 waiter 析构时等待 init 完成 47 | } 48 | 49 | protected: 50 | bool init() override { 51 | // 初始化资源 52 | return true; // 返回 false 可终止线程 53 | } 54 | 55 | bool run_once(std::stop_token &st) override { 56 | // 等待唤醒或定时任务 57 | sleep_until_awake_for(std::chrono::milliseconds(500)); 58 | 59 | if (st.stop_requested()) return false; 60 | 61 | // 执行一次工作 62 | do_work(); 63 | return true; // 继续循环 64 | } 65 | 66 | void shutdown() override { 67 | // 清理资源 68 | } 69 | }; 70 | ``` 71 | 72 | 在外部触发工作或唤醒守护线程: 73 | 74 | ```cpp 75 | my_daemon d; 76 | // 触发守护线程做一次立即处理 77 | d.awake(); 78 | ``` 79 | 80 | ## 设计细节与注意事项 81 | 82 | - `start()` 是 `protected` 的:意在由子类控制何时启动线程(例如在子类构造流程中)。 83 | - `init_waiter` 的析构会 `acquire` 内部信号量,确保调用方等待完成;不要将其返回到会较晚析构的上下文,否则可能阻塞过久。 84 | - `run_once` 接受 `std::stop_token &`:在实现中应检查 `st.stop_requested()`,并在请求停止时尽快返回 `false`。 85 | - 析构过程中 `daemon` 会调用 `awake()` 以确保如果线程正阻塞在 `sleep_until_awake()` 上,则能被唤醒并退出。 86 | - Linux 平台会把后台线程命名为构造时提供的 `name`(通过 `pthread_setname_np`),便于调试与崩溃分析。 87 | 88 | ## 推荐实践 89 | 90 | 1. 将耗时或阻塞的 I/O 放在 `run_once` 中,并确保响应 `stop_requested()`。 91 | 2. 避免在 `init()` 中执行可能长时间阻塞的操作(或在 `init()` 内使用超时机制),因为调用 `start()` 的上下文会等待 `init_waiter` 完成。 92 | 3. `awake()` 语义是“通知有新工作”,而不是强制中断正在执行的工作;若需要立即中断复杂任务,结合 `stop_token` 使用。 93 | 4. 若需要周期性工作,结合 `sleep_until_awake_for()` 或 `sleep_until_awake_before()` 实现合理的等待策略。 94 | 95 | ## 常见问题 96 | 97 | - Q: 我可以在 `run_once` 中抛异常吗? 98 | - A: 当前基类未显式捕获异常,抛出异常会导致线程异常终止。建议在 `run_once` 中自行捕获并在 `shutdown()` 中做清理,或通过 `panic` 报告致命错误。 99 | 100 | - Q: `start()` 返回的 `init_waiter` 需要手动保存吗? 101 | - A: 不需要长时间保存。通常以局部变量持有,确保在期望等待初始化完成的作用域结束时析构即可。 102 | -------------------------------------------------------------------------------- /docs/zh-cn/src/advance/assert.md: -------------------------------------------------------------------------------- 1 | # 带有 stacktrace 的动态断言 (`asco_assert`) 2 | 3 | 本文档介绍 ASCO 的断言宏 `asco_assert` 以及其底层实现、使用方式与最佳实践。 4 | 5 | ## 概述 6 | 7 | `asco_assert(expr, [hint])` 用于在运行期验证**永远应该为真**的条件。若条件失败,它会调用内部的 `asco::assert_failed`,并最终触发 `panic::panic`,以不可恢复的方式终止程序(`[[noreturn]]`)。 8 | 9 | 与普通错误处理不同,断言面向**开发阶段的逻辑不变量**: 10 | 11 | - 发现程序员假设被破坏的最早时机。 12 | - 在失败点提供清晰的表达式字符串和可选提示信息。 13 | - 将控制权交给 panic 框架,统一异常终止路径(着色输出、栈展开/回溯等)。 14 | 15 | ## 接口说明 16 | 17 | ### 宏:`asco_assert` 18 | 19 | ```cpp 20 | #define asco_assert(expr, ...) \ 21 | do { \ 22 | if (!(expr)) { \ 23 | asco::assert_failed(#expr, ##__VA_ARGS__); \ 24 | } \ 25 | } while (0) 26 | ``` 27 | 28 | 要点: 29 | 30 | 1. `expr` 只会被求值一次(包在 `if (!(expr))` 中),避免副作用重复执行。 31 | 2. 失败时字符串化表达式:`#expr`,便于定位逻辑。 32 | 3. 可选参数(提示 `hint`)通过 GNU 扩展 `##__VA_ARGS__` 消除空参数的逗号。 33 | 4. 展开后调用对应该签名的 `asco::assert_failed`。 34 | 35 | ### 函数:`asco::assert_failed` 36 | 37 | ```cpp 38 | [[noreturn]] void assert_failed(std::string_view expr); 39 | [[noreturn]] void assert_failed(std::string_view expr, std::string_view hint); 40 | ``` 41 | 42 | 实现(`assert.cpp`)内部直接委托给: 43 | 44 | ```cpp 45 | panic::co_panic("Assertion failed on {}", expr); 46 | panic::co_panic("Assertion failed on {}: {}", expr, hint); 47 | ``` 48 | 49 | 因此: 50 | 51 | - 所有断言失败统一走 panic 终止路径; 52 | - 输出格式固定,便于日志检索; 53 | - 由于采用 `std::string_view`,传入的 `hint` 应为字符串字面量或生命周期足够长的缓冲(避免悬垂)。 54 | 55 | ## 使用示例 56 | 57 | ### 基本用法 58 | 59 | ```cpp 60 | int idx = compute_index(); 61 | asco_assert(idx >= 0, "索引必须非负"); 62 | ``` 63 | 64 | 失败输出示例: 65 | 66 | ```text 67 | Assertion failed on idx >= 0: 索引必须非负 68 | ``` 69 | 70 | ### 无提示信息 71 | 72 | ```cpp 73 | asco_assert(ptr != nullptr); 74 | ``` 75 | 76 | 输出: 77 | 78 | ```text 79 | Assertion failed on ptr != nullptr 80 | ``` 81 | 82 | ### 结合内部不变量 83 | 84 | ```cpp 85 | struct range { int l; int r; }; 86 | void normalize(range& rg) { 87 | asco_assert(rg.l <= rg.r, "range 左值不能大于右值"); 88 | // ... normalize logic 89 | } 90 | ``` 91 | 92 | ### 避免副作用重复 93 | 94 | 虽然表达式只执行一次,仍建议保持无副作用: 95 | 96 | ```cpp 97 | // 不推荐:含副作用的断言表达式会降低可读性 98 | asco_assert(vec.pop_back() > 0, "不应为空"); 99 | ``` 100 | 101 | ## 与 Panic 框架的集成 102 | 103 | 断言失败直接调用 `panic::co_panic`: 104 | 105 | - 继承 panic 框架的彩色/结构化输出(若已实现)。 106 | - 可以统一在 co_panic 处理路径中做栈回溯、日志落地、核心转储等。 107 | - 所有断言失败为“致命”级别,不返回调用者。 108 | 109 | 这使断言只负责“检测 + 定位”,而终止策略(如是否生成 dump)完全由 panic 系统集中配置,降低分散的错误处理复杂度。 110 | 111 | ## 性能与开销 112 | 113 | 当前实现**无条件启用**: 114 | 115 | - 每个断言在成功时的开销 ≈ 一次条件分支 + 未触发时的宏展开(极低)。 116 | - 失败路径较重(格式化 + panic 逻辑)。 117 | 118 | 在性能关键路径依旧可以使用断言,但需确保: 119 | 120 | 1. 断言表达式本身是 O(1) 且无昂贵副作用; 121 | 2. 不使用复杂的临时格式化(`hint` 仅接受一个 `std::string_view`,避免构造代价)。 122 | 123 | ## 最佳实践 124 | 125 | 1. 只断言“绝不该失败”的内部不变量;可预期失败的输入应做显式校验与返回错误。 126 | 2. 保持表达式短小、可读:逻辑复杂时先拆成局部变量再断言。 127 | 3. `hint` 写明为何“不变量应成立”,而非简单重复表达式。例:`"range 必须标准化后 l <= r"`。 128 | 4. 不在断言中做资源释放等副作用动作,断言失败后不会继续执行。 129 | 130 | 5. 若需要更丰富提示(多变量格式化),建议在调用前自行构造稳定的字符串缓冲再传入(目前接口只接受一个 `std::string_view`)。 131 | 132 | ## 与标准库 / 其他框架对比 133 | 134 | | 项目 | 行为 | 自定义提示 | 终止统一性 | 135 | | ------------------ | --------------------------------- | ---------- | ---------- | 136 | | `assert(expr)` (C) | 条件失败 `abort()` | 需修改源码 | 分散 | 137 | | `std::assert` (宏) | 同上 | 需修改源码 | 分散 | 138 | | `asco_assert` | 进入 panic 流程(可扩展统一终止) | 有 | 统一 | 139 | 140 | ## 常见误用与规避 141 | 142 | | 误用 | 风险说明 | 推荐做法 | 143 | | ---------------------------- | -------------- | -------------------------- | 144 | | 在表达式中包含副作用 | 语义不清 | 将副作用前置,断言纯条件 | 145 | | 用断言替代输入合法性校验 | 用户可触发崩溃 | 使用返回值/错误码/异常 | 146 | | 传入临时构造的短生命周期缓冲 | 悬垂引用 | 使用字符串字面量或静态缓存 | 147 | -------------------------------------------------------------------------------- /tests/sync/test_notify.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace asco; 14 | using namespace std::chrono_literals; 15 | 16 | future async_main() { 17 | std::println("test_notify: start"); 18 | 19 | // Test A: wait before notify_one 20 | { 21 | notify n; 22 | std::atomic waiting{false}; 23 | std::atomic resumed{false}; 24 | 25 | auto waiter = co_invoke([&n, &waiting, &resumed]() -> future_spawn { 26 | waiting.store(true, std::memory_order_release); 27 | co_await n.wait(); 28 | resumed.store(true, std::memory_order_release); 29 | co_return; 30 | }); 31 | 32 | while (!waiting.load(std::memory_order_acquire)) { co_await sleep_for(1ms); } 33 | 34 | n.notify_one(); 35 | co_await waiter; 36 | 37 | if (!resumed.load(std::memory_order_acquire)) { 38 | std::println("test_notify: A FAILED - waiter did not resume after notify_one"); 39 | co_return 1; 40 | } 41 | std::println("test_notify: A passed"); 42 | } 43 | 44 | // Test B: notify_one before wait should not unblock later waiters 45 | { 46 | notify n; 47 | std::atomic waiting{false}; 48 | std::atomic resumed{false}; 49 | 50 | n.notify_one(); // should have no effect because no waiter is registered yet 51 | 52 | auto waiter = co_invoke([&n, &waiting, &resumed]() -> future_spawn { 53 | waiting.store(true, std::memory_order_release); 54 | co_await n.wait(); 55 | resumed.store(true, std::memory_order_release); 56 | co_return; 57 | }); 58 | 59 | while (!waiting.load(std::memory_order_acquire)) { co_await sleep_for(1ms); } 60 | 61 | co_await sleep_for(5ms); 62 | if (resumed.load(std::memory_order_acquire)) { 63 | std::println("test_notify: B FAILED - waiter resumed without notify"); 64 | co_return 1; 65 | } 66 | 67 | n.notify_one(); 68 | co_await waiter; 69 | 70 | if (!resumed.load(std::memory_order_acquire)) { 71 | std::println("test_notify: B FAILED - waiter did not resume after second notify"); 72 | co_return 1; 73 | } 74 | std::println("test_notify: B passed"); 75 | } 76 | 77 | // Test C: notify_all wakes every waiter exactly once 78 | { 79 | notify n; 80 | constexpr int N = 3; 81 | std::atomic waiting{0}; 82 | std::atomic resumed{0}; 83 | std::vector> waiters; 84 | waiters.reserve(N); 85 | 86 | for (int i = 0; i < N; ++i) { 87 | waiters.push_back(co_invoke([&n, &waiting, &resumed]() -> future_spawn { 88 | waiting.fetch_add(1, std::memory_order_acq_rel); 89 | co_await n.wait(); 90 | resumed.fetch_add(1, std::memory_order_acq_rel); 91 | co_return; 92 | })); 93 | } 94 | 95 | while (waiting.load(std::memory_order_acquire) != N) { co_await sleep_for(1ms); } 96 | 97 | n.notify_all(); 98 | for (auto &w : waiters) { co_await w; } 99 | 100 | if (resumed.load(std::memory_order_acquire) != N) { 101 | std::println( 102 | "test_notify: C FAILED - expected {} waiters resumed, got {}", N, 103 | resumed.load(std::memory_order_acquire)); 104 | co_return 1; 105 | } 106 | std::println("test_notify: C passed"); 107 | } 108 | 109 | std::println("test_notify: all checks passed"); 110 | co_return 0; 111 | } 112 | -------------------------------------------------------------------------------- /asco/core/task.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | namespace asco::core { 25 | 26 | using namespace types; 27 | using namespace concepts; 28 | 29 | using concurrency::atomic_ptr; 30 | 31 | class worker; 32 | 33 | using task_id = size_t; 34 | 35 | template< 36 | move_secure T = type_erase, bool Spawn = true, bool Core = true, bool UseReturnValue = true, 37 | typename StateExtra = void, typename = void> 38 | struct task; 39 | 40 | template<> 41 | struct task<> { 42 | const task_id id; 43 | const std::coroutine_handle<> corohandle; 44 | const bool spawn_task; 45 | const bool core_task; 46 | 47 | std::optional caller_coroutine_trace; 48 | 49 | atomic_bool await_started{false}; 50 | atomic_bool returned{false}; 51 | 52 | atomic_bool cancelled{false}; 53 | 54 | atomic_bool e_thrown{false}; 55 | atomic_bool e_rethrown{false}; 56 | 57 | atomic_bool scheduled{false}; 58 | 59 | std::exception_ptr e_ptr{}; // Released by `e_thrown` 60 | 61 | std::shared_ptr> caller; // Released by `await_started` 62 | 63 | worker *worker_ptr{nullptr}; // Released by `scheduled` 64 | 65 | std::optional wait_sem{std::nullopt}; 66 | rwspin<> sync_waiting_lock; 67 | 68 | cpptrace::stacktrace raw_stacktrace; 69 | 70 | std::stack call_chain; 71 | 72 | task( 73 | task_id id, std::coroutine_handle<> corohdl, bool spawn_task, bool core_task, 74 | panic::coroutine_trace_handle caller_coroutine_addr) noexcept 75 | : id{id} 76 | , corohandle{corohdl} 77 | , spawn_task{spawn_task} 78 | , core_task{core_task} 79 | , caller_coroutine_trace{caller_coroutine_addr} { 80 | call_chain.push(id); 81 | } 82 | 83 | virtual ~task() noexcept = default; 84 | 85 | protected: 86 | void destroy() noexcept { 87 | if (e_thrown.load(morder::acquire) && !e_rethrown.load(morder::acquire)) { 88 | try { 89 | std::rethrow_exception(e_ptr); 90 | } catch (const std::exception &e) { 91 | panic::panic( 92 | std::format( 93 | "Exception thrown by task {} hasn't been caught:\n what(): {}", id, e.what())); 94 | } catch (...) { 95 | panic::panic(std::format("Unknown exception thrown by task {} hasn't been caught.", id)); 96 | } 97 | } 98 | } 99 | }; 100 | 101 | template 102 | struct task : public task<> { 103 | using deliver_type = T; 104 | constexpr static bool deliver_type_is_void = std::is_void_v; 105 | 106 | memory_slot> 107 | delivery_slot; // Release by `returned` 108 | 109 | memory_slot extra_slot; 110 | 111 | task( 112 | task_id id, std::coroutine_handle<> corohdl, 113 | panic::coroutine_trace_handle caller_coroutine_addr) noexcept 114 | : task<>{id, corohdl, Spawn, Core, caller_coroutine_addr} {} 115 | 116 | ~task() noexcept override { destroy(); } 117 | 118 | void bind_lambda(std::unique_ptr &&f) { bound_lambda = std::move(f); } 119 | 120 | private: 121 | std::unique_ptr bound_lambda{nullptr}; 122 | }; 123 | 124 | }; // namespace asco::core 125 | -------------------------------------------------------------------------------- /asco/sync/spin.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace asco::sync { 10 | 11 | using namespace types; 12 | 13 | template 14 | class spin; 15 | 16 | template<> 17 | class spin { 18 | public: 19 | spin() = default; 20 | 21 | spin(const spin &) = delete; 22 | spin(spin &&) = delete; 23 | class guard { 24 | friend spin; 25 | 26 | spin &s; 27 | bool none{false}; 28 | 29 | guard(spin &s) noexcept 30 | : s{s} { 31 | size_t withdraw_count{0}; 32 | for (bool b{false}; !s.locked.compare_exchange_weak(b, true, morder::acq_rel, morder::relaxed); 33 | b = false) { 34 | while (s.locked.load(morder::acquire)) { 35 | concurrency::exp_withdraw(withdraw_count); 36 | withdraw_count++; 37 | if (withdraw_count > 16) 38 | withdraw_count = 16; 39 | } 40 | } 41 | // We don't use std::this_thread::yield() because while we use spin locks, the competitors of this 42 | // lock are largely (almost 100%, because we have cpu affinity for worker threads and task 43 | // stealing) in different worker threads. There is no need to yield because either we yield or 44 | // not, the probability of competitors releasing this lock is the same. 45 | } 46 | 47 | public: 48 | guard(guard &&rhs) noexcept 49 | : s{rhs.s} 50 | , none{rhs.none} { 51 | rhs.none = true; 52 | } 53 | 54 | ~guard() { 55 | if (!none) 56 | s.locked.store(false, morder::release); 57 | } 58 | 59 | operator bool() const noexcept { return !none; } 60 | }; 61 | 62 | guard lock() noexcept { return guard{*this}; } 63 | 64 | private: 65 | atomic_bool locked{false}; 66 | }; 67 | 68 | template 69 | class spin { 70 | public: 71 | spin() 72 | : value{} {} 73 | 74 | spin(const spin &) = delete; 75 | spin(spin &&) = delete; 76 | 77 | explicit spin(const T &val) 78 | requires std::copy_constructible 79 | : value{val} {} 80 | 81 | explicit spin(T &&val) 82 | requires std::move_constructible 83 | : value{std::move(val)} {} 84 | 85 | template 86 | explicit spin(Args &&...args) 87 | requires std::constructible_from 88 | : value(std::forward(args)...) {} 89 | 90 | class guard { 91 | friend spin; 92 | 93 | spin<>::guard g; 94 | spin &s; 95 | bool none{false}; 96 | 97 | guard(spin &s) noexcept 98 | : g{s._lock.lock()} 99 | , s{s} {} 100 | 101 | public: 102 | guard(guard &&rhs) noexcept 103 | : g{std::move(rhs.g)} 104 | , s{rhs.s} 105 | , none{rhs.none} { 106 | rhs.none = true; 107 | } 108 | 109 | operator bool() const noexcept { return !none; } 110 | 111 | T &operator*() noexcept { return s.value; } 112 | 113 | const T &operator*() const noexcept { return s.value; } 114 | 115 | T *operator->() noexcept { return &s.value; } 116 | 117 | const T *operator->() const noexcept { return &s.value; } 118 | }; 119 | 120 | guard lock() noexcept { return guard{*this}; } 121 | 122 | T &&get() noexcept 123 | requires std::move_constructible || std::is_move_assignable_v 124 | { 125 | value_moved.store(true, morder::relaxed); 126 | return std::move(value); 127 | } 128 | 129 | private: 130 | T value; 131 | atomic_bool value_moved{false}; 132 | spin<> _lock{}; 133 | }; 134 | 135 | }; // namespace asco::sync 136 | 137 | namespace asco { 138 | 139 | using sync::spin; 140 | 141 | }; // namespace asco 142 | -------------------------------------------------------------------------------- /tests/sync/test_channel.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace asco; 10 | 11 | future async_main() { 12 | std::println("test_channel: start"); 13 | 14 | // Test A: try_recv on empty channel returns non_object 15 | { 16 | auto [tx, rx] = channel(); 17 | auto r = rx.try_recv(); 18 | if (r.has_value()) { 19 | std::println("test_channel: A FAILED - unexpected value on empty channel: {}", *r); 20 | co_return 1; 21 | } 22 | if (r.error() != pop_fail::non_object) { 23 | std::println("test_channel: A FAILED - expected non_object, got closed"); 24 | co_return 1; 25 | } 26 | std::println("test_channel: A passed"); 27 | } 28 | 29 | // Test B: send then try_recv returns the value; send returns nullopt on success 30 | { 31 | auto [tx, rx] = channel(); 32 | auto send_res = co_await tx.send(123); 33 | if (send_res.has_value()) { 34 | auto &[val, _] = *send_res; 35 | std::println("test_channel: B FAILED - send should succeed, got value back: {}", val); 36 | co_return 1; 37 | } 38 | 39 | auto r = rx.try_recv(); 40 | if (!r.has_value() || *r != 123) { 41 | std::println("test_channel: B FAILED - expected 123, got {}", r.has_value() ? *r : -1); 42 | co_return 1; 43 | } 44 | 45 | // Now empty again 46 | auto r2 = rx.try_recv(); 47 | if (r2.has_value() || r2.error() != pop_fail::non_object) { 48 | std::println("test_channel: B FAILED - expected empty after single recv"); 49 | co_return 1; 50 | } 51 | std::println("test_channel: B passed"); 52 | } 53 | 54 | // Test C: ordering with async recv() 55 | { 56 | auto [tx, rx] = channel(); 57 | constexpr int N = 5; 58 | for (int i = 0; i < N; ++i) { 59 | if (auto sr = co_await tx.send(i); sr.has_value()) { 60 | auto &[val, _] = *sr; 61 | std::println("test_channel: C FAILED - send returned value {}", val); 62 | co_return 1; 63 | } 64 | } 65 | 66 | for (int i = 0; i < N; ++i) { 67 | auto v = co_await rx.recv(); 68 | if (!v.has_value() || *v != i) { 69 | std::println("test_channel: C FAILED - expected {}, got {}", i, v.has_value() ? *v : -1); 70 | co_return 1; 71 | } 72 | } 73 | 74 | // Should be empty now 75 | auto r = rx.try_recv(); 76 | if (r.has_value() || r.error() != pop_fail::non_object) { 77 | std::println("test_channel: C FAILED - expected empty after draining"); 78 | co_return 1; 79 | } 80 | std::println("test_channel: C passed"); 81 | } 82 | 83 | // Test D: stop() behavior - further send fails; drain remaining then stopped flags 84 | { 85 | auto [tx, rx] = channel(); 86 | // enqueue a couple values 87 | (void)co_await tx.send(7); 88 | (void)co_await tx.send(8); 89 | tx.stop(); 90 | 91 | // drain the two enqueued values 92 | auto v1 = co_await rx.recv(); 93 | auto v2 = co_await rx.recv(); 94 | if (!v1.has_value() || !v2.has_value() || *v1 != 7 || *v2 != 8) { 95 | std::println("test_channel: D FAILED - unexpected drained values"); 96 | co_return 1; 97 | } 98 | 99 | // now channel should be considered stopped/drained 100 | if (!tx.is_stopped() || !rx.is_stopped()) { 101 | std::println("test_channel: D FAILED - expected stopped flags true"); 102 | co_return 1; 103 | } 104 | 105 | // and no further items 106 | auto r = rx.try_recv(); 107 | if (r.has_value() || r.error() != pop_fail::closed) { 108 | std::println( 109 | "test_channel: D FAILED - expected closed after stop and drain: {}", (size_t)(r.error())); 110 | co_return 1; 111 | } 112 | std::println("test_channel: D passed"); 113 | } 114 | 115 | std::println("test_channel: all checks passed"); 116 | co_return 0; 117 | } 118 | -------------------------------------------------------------------------------- /docs/zh-cn/src/sync/channel.md: -------------------------------------------------------------------------------- 1 | # Channel(通道) 2 | 3 | ASCO 的 Channel 为多生产者/多消费者(MPMC)数据通道: 4 | 5 | - 结合 `unlimited_semaphore` 完成“有数据即通知”的等待/唤醒。 6 | 7 | - 头文件:`#include ` 8 | - 命名空间: 9 | - 类型:`asco::sender`, `asco::receiver` 10 | - 工厂:`asco::channel()` 11 | 12 | ## 构造与类型 13 | 14 | - `auto [tx, rx] = channel();` 15 | - 返回 `sender` 与 `receiver` 的二元组,二者共享同一个内部信号量。 16 | - 也可传入自定义队列工厂:`channel(Creator)`,其中 `Creator` 需满足 `queue::creator` 概念。 17 | 18 | ## 接口语义 19 | 20 | ### sender 21 | 22 | - `future>> send(T value)` 23 | - 调用方需 `co_await` 该 `future`,以便在底层使用有界队列满载时自动等待配额。 24 | - 成功:返回 `std::nullopt`,并唤醒一个等待的接收者。 25 | - 失败:返回包含原值与失败原因的 `std::tuple`: 26 | - `push_fail::closed`:队列已关闭或停止。 27 | - `push_fail::full`:自定义队列已满且不可再写入。 28 | - `void stop() noexcept` 29 | - 标记底层队列为“发送端停止”。调用后不得再调用 `send()`;否则底层会触发 panic(调试保护)。 30 | - `bool is_stopped() const noexcept` 31 | - 若发送端或接收端已停止,或未绑定到队列,返回 `true`。 32 | 33 | ### receiver 34 | 35 | - `std::expected try_recv()` 36 | - 若无可用数据:返回 `std::unexpected(pop_fail::non_object)`。 37 | - 若通道已完全关闭且无可读对象:可能返回 `std::unexpected(pop_fail::closed)`。 38 | - 若成功:返回 `T`。 39 | - `future> recv()` 40 | - 若当前无数据:协程方式挂起直至有“数据就绪”的通知; 41 | - 唤醒后尝试读取,若成功返回 `T`,若关闭导致无对象可读返回 `std::nullopt`。当底层队列为有界队列时,成功读取还会释放一个配额令发送端继续写入。 42 | - `bool is_stopped() const noexcept` 43 | - 当发送端或接收端停止,且当前帧已读尽时,返回 `true`。 44 | 45 | ## 有序性与并发性 46 | 47 | - Channel 保持 FIFO 顺序。 48 | - 支持 MPMC:多个 sender/receiver 并发安全。 49 | 50 | ## 典型用法 51 | 52 | ### 1. 单生产者-单消费者 53 | 54 | ```cpp 55 | #include 56 | #include 57 | #include 58 | using namespace asco; 59 | 60 | future async_main() { 61 | auto [tx, rx] = channel(); 62 | 63 | // 发送 64 | for (int i = 0; i < 5; ++i) { 65 | if (auto r = co_await tx.send(i); r.has_value()) { 66 | auto &[value, reason] = *r; 67 | std::println("send failed: {} (reason = {})", value, static_cast(reason)); 68 | co_return 1; 69 | } 70 | } 71 | 72 | // 接收(等待式) 73 | for (int i = 0; i < 5; ++i) { 74 | auto v = co_await rx.recv(); 75 | if (!v || *v != i) { 76 | std::println("recv mismatch: {}", v ? *v : -1); 77 | co_return 1; 78 | } 79 | } 80 | 81 | co_return 0; 82 | } 83 | ``` 84 | 85 | ### 2. 停止与排干 86 | 87 | ```cpp 88 | #include 89 | #include 90 | using namespace asco; 91 | 92 | future async_main() { 93 | auto [tx, rx] = channel(); 94 | 95 | (void)co_await tx.send(7); 96 | (void)co_await tx.send(8); 97 | 98 | // 停止发送端:之后不得再调用 send() 99 | tx.stop(); 100 | 101 | // 排干剩余元素 102 | auto a = co_await rx.recv(); 103 | auto b = co_await rx.recv(); 104 | 105 | if (!a || !b || *a != 7 || *b != 8) co_return 1; 106 | if (!tx.is_stopped() || !rx.is_stopped()) co_return 1; 107 | 108 | // 现在应无更多数据 109 | auto t = rx.try_recv(); 110 | if (t.has_value()) co_return 1; 111 | 112 | co_return 0; 113 | } 114 | ``` 115 | 116 | ### 3. 非阻塞读取与等待混合 117 | 118 | ```cpp 119 | #include 120 | #include 121 | #include 122 | #include 123 | using namespace asco; 124 | using namespace std::chrono_literals; 125 | 126 | future async_main() { 127 | auto [tx, rx] = channel(); 128 | 129 | // 生产者:异步发送 130 | auto producer = [] (sender tx) -> future_spawn { 131 | for (int i = 0; i < 3; ++i) (void)co_await tx.send(i); 132 | tx.stop(); 133 | co_return; 134 | }(tx); 135 | 136 | interval tick{100ms}; 137 | while (!rx.is_stopped()) { 138 | // 先尝试非阻塞 139 | if (auto r = rx.try_recv(); r.has_value()) { 140 | std::println("got {}", *r); 141 | continue; 142 | } 143 | // 无数据则小憩一会儿(避免空转) 144 | co_await tick.tick(); 145 | } 146 | 147 | co_await producer; 148 | co_return 0; 149 | } 150 | ``` 151 | 152 | ## 注意事项 153 | 154 | - 始终 `co_await sender::send()` 以正确处理带界队列的背压;忽略返回 `future` 可能导致任务提前退出或绕过容量控制。 155 | - `sender::send()` 返回值含 `queue::push_fail`,常见原因是通道已关闭(`push_fail::closed`);若自定义队列有容量限制,可额外关注 `push_fail::full`。 156 | - 调用 `sender::stop()` 后不可再调用 `send()`;这是通道关闭的显式信号,违背会触发 panic(调试保护)。 157 | - `recv()` 被唤醒后理论上应能读到元素;若底层已关闭并无对象可读将得到 `std::nullopt`。 158 | - 使用 `try_recv()` 判空时,请正确处理 `pop_fail::non_object` 与 `pop_fail::closed`。 159 | -------------------------------------------------------------------------------- /asco/sync/mutex.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace asco::sync { 13 | 14 | using namespace types; 15 | 16 | template 17 | class mutex; 18 | 19 | template<> 20 | class mutex<> { 21 | public: 22 | mutex() = default; 23 | 24 | mutex(const mutex &) = delete; 25 | mutex &operator=(const mutex &) = delete; 26 | 27 | mutex(mutex &&) = delete; 28 | mutex &operator=(mutex &&) = delete; 29 | 30 | class guard { 31 | friend mutex; 32 | 33 | mutex &m; 34 | bool none{false}; 35 | 36 | guard(mutex &m) 37 | : m{m} {} 38 | 39 | public: 40 | guard(guard &&rhs) noexcept 41 | : m{rhs.m} 42 | , none{rhs.none} { 43 | rhs.none = true; 44 | } 45 | 46 | ~guard() { 47 | if (none) 48 | return; 49 | 50 | m.locked.store(false, morder::release); 51 | m.wait_queue.notify(); 52 | } 53 | 54 | operator bool() const noexcept { return !none; } 55 | }; 56 | 57 | future lock() { 58 | while (true) { 59 | for (size_t i{0}; locked.load(morder::acquire); i++) { 60 | if (i < 64) { 61 | continue; 62 | } 63 | if (i < 64 + 6) { 64 | concurrency::exp_withdraw(i - 64); 65 | continue; 66 | } 67 | co_await wait_queue.wait(); 68 | } 69 | 70 | if (bool b = false; locked.compare_exchange_strong(b, true, morder::acq_rel, morder::relaxed)) 71 | co_return guard{*this}; 72 | } 73 | } 74 | 75 | private: 76 | atomic_bool locked{false}; 77 | core::wait_queue wait_queue{}; 78 | }; 79 | 80 | template 81 | class mutex { 82 | public: 83 | mutex() 84 | : value{} {} 85 | 86 | mutex(const mutex &) = delete; 87 | mutex &operator=(const mutex &) = delete; 88 | 89 | mutex(mutex &&) = delete; 90 | mutex &operator=(mutex &&) = delete; 91 | 92 | explicit mutex(const T &val) 93 | requires std::copy_constructible 94 | : value{val} {} 95 | 96 | explicit mutex(T &&val) 97 | requires std::move_constructible 98 | : value{std::move(val)} {} 99 | 100 | template 101 | explicit mutex(Args &&...args) 102 | requires std::constructible_from 103 | : value{std::forward(args)...} {} 104 | 105 | class guard { 106 | friend mutex; 107 | 108 | mutex<>::guard g; 109 | mutex &m; 110 | bool none{false}; 111 | 112 | guard(mutex<>::guard &&g, mutex &m) 113 | : g{std::move(g)} 114 | , m{m} {} 115 | 116 | public: 117 | guard(guard &&rhs) noexcept 118 | : g{std::move(rhs.g)} 119 | , m{rhs.m} 120 | , none{rhs.none} { 121 | rhs.none = true; 122 | } 123 | 124 | operator bool() const noexcept { return !none; } 125 | 126 | T &operator*() noexcept { 127 | asco_assert(!none); 128 | return m.value; 129 | } 130 | 131 | const T &operator*() const noexcept { 132 | asco_assert(!none); 133 | return m.value; 134 | } 135 | 136 | T *operator->() noexcept { 137 | asco_assert(!none); 138 | return &m.value; 139 | } 140 | 141 | const T *operator->() const noexcept { 142 | asco_assert(!none); 143 | return &m.value; 144 | } 145 | }; 146 | 147 | future lock() { 148 | asco_assert(!value_moved.load(morder::relaxed)); 149 | co_return guard{co_await mtx.lock(), *this}; 150 | } 151 | 152 | // !!! UNSAFE !!! Ensure there is no guard before calling this 153 | T &&get() noexcept 154 | requires std::move_constructible || std::is_move_assignable_v 155 | { 156 | value_moved.store(true, morder::relaxed); 157 | return std::move(value); 158 | } 159 | 160 | private: 161 | T value; 162 | atomic_bool value_moved{false}; 163 | mutex<> mtx; 164 | }; 165 | 166 | }; // namespace asco::sync 167 | 168 | namespace asco { 169 | 170 | using sync::mutex; 171 | 172 | }; // namespace asco 173 | -------------------------------------------------------------------------------- /asco/sync/semaphore.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace asco::sync { 16 | 17 | using namespace types; 18 | using namespace concepts; 19 | 20 | using std::chrono::duration_cast; 21 | using std::chrono::nanoseconds; 22 | using clock = std::chrono::high_resolution_clock; 23 | 24 | template 25 | class semaphore_base { 26 | public: 27 | explicit semaphore_base(size_t initial_count) noexcept 28 | : count{initial_count} {} 29 | 30 | bool try_acquire() noexcept { 31 | size_t old_count = count.load(morder::acquire); 32 | if (!old_count) { 33 | return false; 34 | } 35 | return count.compare_exchange_strong(old_count, old_count - 1, morder::acquire, morder::relaxed); 36 | } 37 | 38 | future acquire() { 39 | size_t old_count, new_count; 40 | do { 41 | fetch_count: 42 | old_count = count.load(morder::acquire); 43 | if (!old_count) { 44 | co_await wq.wait(); 45 | goto fetch_count; 46 | } 47 | new_count = old_count - 1; 48 | } while (!count.compare_exchange_weak(old_count, new_count, morder::acquire, morder::relaxed)); 49 | co_return; 50 | } 51 | 52 | future acquire_for(const duration_type auto &timeout) { 53 | auto expire_time = clock::now() + duration_cast(timeout); 54 | size_t old_count, new_count; 55 | do { 56 | fetch_count: 57 | old_count = count.load(morder::acquire); 58 | if (!old_count) { 59 | auto &w = core::worker::this_worker(); 60 | auto &timer = core::runtime::this_runtime().timer(); 61 | auto tid = timer.register_timer(expire_time, w, w.current_task()); 62 | auto it = co_await wq.wait(); 63 | if (it && clock::now() >= expire_time) { 64 | wq.interrupt_wait(*it); 65 | co_return false; 66 | } else { 67 | timer.unregister_timer(tid); 68 | goto fetch_count; 69 | } 70 | } 71 | new_count = old_count - 1; 72 | } while (!count.compare_exchange_weak(old_count, new_count, morder::acquire, morder::relaxed)); 73 | co_return true; 74 | } 75 | 76 | future acquire_until(const time_point_type auto &expire_time) { 77 | size_t old_count, new_count; 78 | do { 79 | fetch_count: 80 | old_count = count.load(morder::acquire); 81 | if (!old_count) { 82 | auto &w = core::worker::this_worker(); 83 | auto &timer = core::runtime::this_runtime().timer(); 84 | auto tid = timer.register_timer(expire_time, w, w.current_task()); 85 | auto it = co_await wq.wait(); 86 | if (it && clock::now() >= expire_time) { 87 | wq.interrupt_wait(*it); 88 | co_return false; 89 | } else { 90 | timer.unregister_timer(tid); 91 | goto fetch_count; 92 | } 93 | } 94 | new_count = old_count - 1; 95 | } while (!count.compare_exchange_weak(old_count, new_count, morder::acquire, morder::relaxed)); 96 | co_return true; 97 | } 98 | 99 | void release(size_t update = 1) { 100 | size_t old_count, new_count; 101 | size_t inc; 102 | do { 103 | old_count = count.load(morder::acquire); 104 | inc = std::min(update, CountMax - old_count); 105 | new_count = old_count + inc; 106 | } while (!count.compare_exchange_weak(old_count, new_count, morder::release, morder::relaxed)); 107 | wq.notify(inc); 108 | } 109 | 110 | size_t get_counter() const noexcept { return count.load(morder::acquire); } 111 | 112 | private: 113 | atomic_size_t count; 114 | core::wait_queue wq; 115 | }; 116 | 117 | }; // namespace asco::sync 118 | 119 | namespace asco { 120 | 121 | using binary_semaphore = sync::semaphore_base<1>; 122 | template 123 | using counting_semaphore = sync::semaphore_base; 124 | using unlimited_semaphore = sync::semaphore_base::max()>; 125 | 126 | }; // namespace asco 127 | -------------------------------------------------------------------------------- /asco/sync/channel.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace asco::sync { 18 | 19 | using namespace concepts; 20 | using namespace types; 21 | 22 | namespace cq = continuous_queue; 23 | 24 | template QSender = cq::sender> 25 | class sender { 26 | public: 27 | sender() = default; 28 | 29 | sender( 30 | QSender &&qsender, std::shared_ptr sem, 31 | std::shared_ptr backup_sem) 32 | : queue_sender{std::move(qsender)} 33 | , sem{std::move(sem)} 34 | , backup_sem{std::move(backup_sem)} {} 35 | 36 | sender(const sender &rhs) = default; 37 | sender &operator=(const sender &rhs) = default; 38 | 39 | sender(sender &&) = default; 40 | sender &operator=(sender &&) = default; 41 | 42 | [[nodiscard("[ASCO] sender::send() maybe returns a T value")]] auto send(passing val) 43 | -> future>> { 44 | if constexpr (QSender::max_volume != std::numeric_limits::max()) { 45 | co_await backup_sem->acquire(); 46 | } 47 | auto res = queue_sender.push(std::move(val)); 48 | if (!res.has_value()) { 49 | sem->release(); 50 | } 51 | co_return res; 52 | } 53 | 54 | void stop() noexcept { 55 | queue_sender.stop(); 56 | sem->release(); 57 | } 58 | 59 | bool is_stopped() const noexcept { return queue_sender.is_stopped(); } 60 | 61 | private: 62 | QSender queue_sender; 63 | std::shared_ptr sem; 64 | std::shared_ptr backup_sem; 65 | }; 66 | 67 | template QReceiver = cq::receiver> 68 | class receiver { 69 | public: 70 | receiver() = default; 71 | 72 | receiver( 73 | QReceiver &&qreceiver, std::shared_ptr sem, 74 | std::shared_ptr backup_sem) 75 | : queue_receiver{std::move(qreceiver)} 76 | , sem{std::move(sem)} 77 | , backup_sem{std::move(backup_sem)} {} 78 | 79 | receiver(const receiver &rhs) = default; 80 | receiver &operator=(const receiver &rhs) = default; 81 | 82 | receiver(receiver &&) = default; 83 | receiver &operator=(receiver &&) = default; 84 | 85 | std::expected try_recv() { 86 | if (!sem->try_acquire()) { 87 | return std::unexpected(queue::pop_fail::non_object); 88 | } else { 89 | auto res = queue_receiver.pop(); 90 | if constexpr (QReceiver::max_volume != std::numeric_limits::max()) { 91 | if (res.has_value()) { 92 | backup_sem->release(); 93 | } 94 | } 95 | return res; 96 | } 97 | } 98 | 99 | [[nodiscard("[ASCO] receiver::receive() maybe fails")]] future> recv() { 100 | co_await sem->acquire(); 101 | if (auto res = queue_receiver.pop(); res.has_value()) { 102 | if constexpr (QReceiver::max_volume != std::numeric_limits::max()) { 103 | backup_sem->release(); 104 | } 105 | co_return *res; 106 | } else { 107 | co_return std::nullopt; 108 | } 109 | } 110 | 111 | bool is_stopped() const noexcept { return queue_receiver.is_stopped(); } 112 | 113 | private: 114 | QReceiver queue_receiver; 115 | std::shared_ptr sem; 116 | std::shared_ptr backup_sem; 117 | }; 118 | 119 | template Creator> 120 | auto channel(Creator ctor) { 121 | auto [tx, rx] = ctor(); 122 | auto sem = std::make_shared(0); 123 | auto backup_sem = std::make_shared(Creator::max_volume); 124 | return std::tuple{ 125 | sender{std::move(tx), sem, backup_sem}, 126 | receiver{std::move(rx), sem, backup_sem}}; 127 | } 128 | 129 | template 130 | auto channel() { 131 | return channel(cq::create); 132 | } 133 | 134 | }; // namespace asco::sync 135 | 136 | namespace asco { 137 | 138 | using sync::channel; 139 | using sync::receiver; 140 | using sync::sender; 141 | 142 | }; // namespace asco 143 | -------------------------------------------------------------------------------- /asco/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 pointer-to-bios 2 | # SPDX-License-Identifier: MIT 3 | 4 | set(ASCO_SOURCES 5 | assert.cpp 6 | context.cpp 7 | core/daemon.cpp 8 | core/runtime.cpp 9 | core/time/high_resolution_timer.cpp 10 | core/time/timer.cpp 11 | core/wait_queue.cpp 12 | core/worker.cpp 13 | future.cpp 14 | panic/panic.cpp 15 | panic/unwind.cpp 16 | sync/notify.cpp 17 | time/interval.cpp 18 | ) 19 | 20 | set(PRECOMPILE_HEADERS 21 | assert.h 22 | compile_time/platform.h 23 | concurrency/concurrency.h 24 | concurrency/continuous_queue.h 25 | concurrency/queue.h 26 | context.h 27 | core/daemon.h 28 | core/pool_allocator.h 29 | core/runtime.h 30 | core/task.h 31 | core/time/high_resolution_timer.h 32 | core/time/timer_concept.h 33 | core/time/timer.h 34 | core/wait_queue.h 35 | core/worker.h 36 | future.h 37 | generator.h 38 | invoke.h 39 | join_set.h 40 | panic/color.h 41 | panic/panic.h 42 | panic/unwind.h 43 | sync/channel.h 44 | sync/notify.h 45 | sync/rwspin.h 46 | sync/semaphore.h 47 | sync/spin.h 48 | time/interval.h 49 | time/sleep.h 50 | utils/concepts.h 51 | utils/defines.h 52 | utils/erased.h 53 | utils/memory_slot.h 54 | utils/types.h 55 | yield.h 56 | ) 57 | 58 | set(MDEFS ASCO __ASCO__) 59 | set(DEPLIBS cpptrace::cpptrace) 60 | 61 | if (USING_LIBCXX) 62 | set(MDEFS USING_LIBCXX) 63 | endif() 64 | 65 | if (LINUX) 66 | if (NOT ASCO_IO_URING) 67 | set(ASCO_SOURCES ${ASCO_SOURCES} 68 | ) 69 | else() 70 | set(ASCO_SOURCES ${ASCO_SOURCES} 71 | ) 72 | endif() 73 | 74 | if (ASCO_IO_URING) 75 | set(DEPLIBS ${DEPLIBS} uring) 76 | endif() 77 | endif() 78 | 79 | if (ASCO_PERF_RECORD) 80 | set (MDEFS ${MDEFS} ASCO_PERF_RECORD) 81 | endif() 82 | if (ASCO_IO_URING) 83 | set (MDEFS ${MDEFS} ASCO_IO_URING) 84 | endif() 85 | if (ASCO_IOCP) 86 | set (MDEFS ${MDEFS} ASCO_IOCP) 87 | endif() 88 | 89 | set(COMPILE_OPTIONS 90 | -Wno-vla-cxx-extension 91 | -Wno-zero-length-array 92 | -Wno-gnu-zero-variadic-macro-arguments 93 | -Wno-sign-compare 94 | ) 95 | 96 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 97 | set(COMPILE_OPTIONS ${COMPILE_OPTIONS} 98 | -Wno-interference-size 99 | ) 100 | endif() 101 | 102 | add_library(asco_core ${ASCO_SOURCES}) 103 | target_include_directories(asco_core PUBLIC 104 | $ 105 | $ 106 | ) 107 | target_link_libraries(asco_core PUBLIC ${DEPLIBS}) 108 | target_compile_definitions(asco_core PUBLIC ${MDEFS}) 109 | target_compile_options(asco_core PUBLIC ${COMPILE_OPTIONS}) 110 | target_precompile_headers(asco_core PUBLIC ${PRECOMPILE_HEADERS}) 111 | add_library(asco::core ALIAS asco_core) 112 | 113 | add_library(asco_shared_core SHARED ${ASCO_SOURCES}) 114 | target_include_directories(asco_shared_core PUBLIC 115 | $ 116 | $ 117 | ) 118 | target_link_libraries(asco_shared_core PUBLIC ${DEPLIBS}) 119 | target_compile_definitions(asco_shared_core PUBLIC ${MDEFS}) 120 | target_compile_options(asco_shared_core PUBLIC ${COMPILE_OPTIONS}) 121 | target_precompile_headers(asco_shared_core PUBLIC ${PRECOMPILE_HEADERS}) 122 | if (NOT WIN32) 123 | set_target_properties(asco_shared_core PROPERTIES OUTPUT_NAME asco_core) 124 | endif() 125 | add_library(asco::shared::core ALIAS asco_shared_core) 126 | 127 | add_library(asco_main asco_main.cpp) 128 | target_link_libraries(asco_main PUBLIC asco_core) 129 | add_library(asco::main ALIAS asco_main) 130 | 131 | add_library(asco_base asco_main.cpp) 132 | target_link_libraries(asco_base PUBLIC asco_core) 133 | target_compile_definitions(asco_base PRIVATE __ASCORT__) 134 | add_library(asco::base ALIAS asco_base) 135 | 136 | add_library(asco_shared_main SHARED asco_main.cpp) 137 | target_link_libraries(asco_shared_main PUBLIC asco_shared_core) 138 | add_library(asco::shared::main ALIAS asco_shared_main) 139 | 140 | add_library(asco_shared_base SHARED asco_main.cpp) 141 | target_link_libraries(asco_shared_base PUBLIC asco_shared_core) 142 | target_compile_definitions(asco_shared_base PRIVATE __ASCORT__) 143 | add_library(asco::shared::base ALIAS asco_shared_base) 144 | 145 | option(ASCO_ENABLE_INSTALL "Enable install() rules for packaging" OFF) 146 | if(ASCO_ENABLE_INSTALL) 147 | include(GNUInstallDirs) 148 | # Install headers (public API) under include/asco 149 | install( 150 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ 151 | DESTINATION include/asco 152 | FILES_MATCHING 153 | PATTERN "*.h" 154 | ) 155 | 156 | # Install targets and export them for find_package 157 | install(TARGETS 158 | asco_core 159 | asco_shared_core 160 | asco_main 161 | asco_base 162 | asco_shared_main 163 | asco_shared_base 164 | EXPORT ascoTargets 165 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 166 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 167 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 168 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 169 | ) 170 | endif() 171 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 pointer-to-bios 2 | # SPDX-License-Identifier: MIT 3 | 4 | cmake_minimum_required(VERSION 3.20) 5 | project(asco VERSION 0.1.0 LANGUAGES CXX) 6 | 7 | # Compiler and linker config 8 | 9 | set(CMAKE_CXX_STANDARD 23) 10 | set(CMAKE_CXX_EXTENSIONS ON) 11 | 12 | include(CheckCXXCompilerFlag) 13 | include(CheckCXXSourceCompiles) 14 | include(ProcessorCount) 15 | 16 | ProcessorCount(CPUS) 17 | 18 | check_cxx_compiler_flag("-stdlib=libc++" COMPILER_SUPPORTS_LIBCXX) 19 | 20 | if (COMPILER_SUPPORTS_LIBCXX) 21 | set(_save_required_flags "${CMAKE_REQUIRED_FLAGS}") 22 | set(_save_required_includes "${CMAKE_REQUIRED_INCLUDES}") 23 | set(_save_required_libs "${CMAKE_REQUIRED_LIBRARIES}") 24 | 25 | set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++") 26 | if (LIBCXX_INCLUDE_DIR) 27 | set(CMAKE_REQUIRED_INCLUDES "${LIBCXX_INCLUDE_DIR}") 28 | endif() 29 | if (LIBCXX_LIB) 30 | list(APPEND CMAKE_REQUIRED_LIBRARIES "${LIBCXX_LIB}") 31 | endif() 32 | if (LIBCXXABI_LIB) 33 | list(APPEND CMAKE_REQUIRED_LIBRARIES "${LIBCXXABI_LIB}") 34 | endif() 35 | 36 | check_cxx_source_compiles(" 37 | #include 38 | #include 39 | int main(){ std::string s = \"ok\"; std::cout << s; return 0; } 40 | " LIBCXX_COMPILES) 41 | 42 | set(CMAKE_REQUIRED_FLAGS "${_save_required_flags}") 43 | set(CMAKE_REQUIRED_INCLUDES "${_save_required_includes}") 44 | set(CMAKE_REQUIRED_LIBRARIES "${_save_required_libs}") 45 | else() 46 | set(LIBCXX_COMPILES OFF) 47 | endif() 48 | 49 | set(USING_LIBCXX OFF) 50 | 51 | if (LIBCXX_COMPILES) 52 | set(USING_LIBCXX ON) 53 | message(STATUS "C++ stdlib: LLVM libc++") 54 | set(CMAKE_REQUIRED_FLAGS "-stdlib=libc++") 55 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 56 | else() 57 | message(STATUS "C++ stdlib: GNU libstdc++") 58 | message(STATUS "LLVM libc++ is recommended") 59 | endif() 60 | 61 | if (NOT WIN32) 62 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 63 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 64 | endif() 65 | add_compile_options(-Wall -Wextra -Wno-gnu-statement-expression -Wno-gnu-statement-expression-from-macro-expansion) 66 | endif() 67 | 68 | # add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer) 69 | # add_link_options(-fsanitize=undefined -fno-omit-frame-pointer) 70 | 71 | if (UNIX AND NOT APPLE AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")) 72 | find_program(LD_LLD NAMES ld.lld) 73 | find_program(LD_MOLD NAMES ld.mold) 74 | 75 | set(_picked_ld "") 76 | if (LD_LLD) 77 | set(_picked_ld "lld") 78 | message(STATUS "Linker: using lld (${LD_LLD})") 79 | elseif (LD_MOLD) 80 | set(_picked_ld "mold") 81 | message(STATUS "Linker: using mold (${LD_MOLD})") 82 | else() 83 | message(STATUS "Linker: using system ld") 84 | endif() 85 | 86 | if (_picked_ld) 87 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=${_picked_ld}") 88 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=${_picked_ld}") 89 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=${_picked_ld}") 90 | 91 | if (_picked_ld STREQUAL "mold" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND LD_MOLD) 92 | set(CMAKE_LINKER "${LD_MOLD}" CACHE FILEPATH "Path to linker" FORCE) 93 | endif() 94 | if (_picked_ld STREQUAL "lld" AND LD_LLD) 95 | set(CMAKE_LINKER "${LD_LLD}" CACHE FILEPATH "Path to linker" FORCE) 96 | endif() 97 | 98 | add_link_options("-Wl,--threads=${CPUS}") 99 | endif() 100 | else() 101 | message(STATUS "Linker: using system ld") 102 | endif() 103 | 104 | enable_testing() 105 | 106 | # Dependencies 107 | 108 | add_subdirectory(deps/cpptrace) 109 | 110 | # ASCO config 111 | 112 | option(ASCO_PERF_RECORD "ASCO Performance Recording" OFF) 113 | option(ASCO_IO_URING "io_uring support for linux" ON) 114 | option(ASCO_IOCP "IOCP support for windows" OFF) 115 | 116 | if (WIN32 AND ASCO_IO_URING) 117 | error("ASCO_IO_URING must be off on windows target.") 118 | endif() 119 | 120 | if (LINUX AND ASCO_IOCP) 121 | error("ASCO_IOCP must be off on linux target.") 122 | endif() 123 | 124 | add_subdirectory(asco) 125 | add_subdirectory(tests) 126 | 127 | # Packaging for build-tree find_package 128 | 129 | include(CMakePackageConfigHelpers) 130 | 131 | export( 132 | TARGETS 133 | asco_core 134 | asco_shared_core 135 | asco_main 136 | asco_base 137 | asco_shared_main 138 | asco_shared_base 139 | NAMESPACE asco:: 140 | FILE "${CMAKE_CURRENT_BINARY_DIR}/ascoTargets.cmake" 141 | ) 142 | 143 | # Generate build-tree package config files 144 | write_basic_package_version_file( 145 | "${CMAKE_CURRENT_BINARY_DIR}/ascoConfigVersion.cmake" 146 | VERSION ${PROJECT_VERSION} 147 | COMPATIBILITY SameMajorVersion 148 | ) 149 | 150 | configure_package_config_file( 151 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ascoConfig.cmake.in" 152 | "${CMAKE_CURRENT_BINARY_DIR}/ascoConfig.cmake" 153 | INSTALL_DESTINATION lib/cmake/asco 154 | ) 155 | 156 | # Register build tree in the User Package Registry so consumers can find it via find_package(asco) 157 | export(PACKAGE asco) 158 | -------------------------------------------------------------------------------- /tests/context/test_context.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace asco; 14 | using namespace std::chrono_literals; 15 | 16 | future async_main() { 17 | std::println("test_context: start"); 18 | 19 | // Test A: manual cancel wakes waiters and updates state 20 | { 21 | auto ctx = context::with_cancel(); 22 | if (ctx->is_cancelled()) { 23 | std::println("test_context: A FAILED - context should start non-cancelled"); 24 | co_return 1; 25 | } 26 | 27 | std::atomic resumed{false}; 28 | 29 | auto waiter = co_invoke([ctx, &resumed]() -> future_spawn { 30 | co_await ctx; 31 | resumed.store(true, std::memory_order_release); 32 | co_return; 33 | }); 34 | 35 | co_await sleep_for(5ms); 36 | if (resumed.load(std::memory_order_acquire)) { 37 | std::println("test_context: A FAILED - waiter resumed before cancel"); 38 | co_return 1; 39 | } 40 | 41 | co_await ctx->cancel(); 42 | co_await waiter; 43 | 44 | if (!ctx->is_cancelled()) { 45 | std::println("test_context: A FAILED - context did not transition to cancelled"); 46 | co_return 1; 47 | } 48 | if (!resumed.load(std::memory_order_acquire)) { 49 | std::println("test_context: A FAILED - waiter did not observe cancellation"); 50 | co_return 1; 51 | } 52 | std::println("test_context: A passed"); 53 | } 54 | 55 | // Test B: timeout-based cancellation occurs after delay 56 | { 57 | constexpr auto timeout = 40ms; 58 | auto ctx = context::with_timeout(timeout); 59 | if (ctx->is_cancelled()) { 60 | std::println("test_context: B FAILED - timeout context cancelled immediately"); 61 | co_return 1; 62 | } 63 | 64 | std::atomic resumed{false}; 65 | 66 | auto waiter = co_invoke([ctx, &resumed]() -> future_spawn { 67 | co_await ctx; 68 | resumed.store(true, std::memory_order_release); 69 | co_return; 70 | }); 71 | 72 | co_await sleep_for(timeout / 4); 73 | if (ctx->is_cancelled()) { 74 | std::println("test_context: B FAILED - timeout fired prematurely"); 75 | co_return 1; 76 | } 77 | 78 | co_await waiter; 79 | if (!resumed.load(std::memory_order_acquire)) { 80 | std::println("test_context: B FAILED - waiter did not resume after timeout"); 81 | co_return 1; 82 | } 83 | if (!ctx->is_cancelled()) { 84 | std::println("test_context: B FAILED - context not cancelled after timeout"); 85 | co_return 1; 86 | } 87 | std::println("test_context: B passed"); 88 | } 89 | 90 | // Test C: wait after cancellation resumes immediately 91 | { 92 | auto ctx = context::with_cancel(); 93 | co_await ctx->cancel(); 94 | std::atomic resumed{false}; 95 | auto waiter = co_invoke([ctx, &resumed]() -> future_spawn { 96 | co_await ctx; 97 | resumed.store(true, std::memory_order_release); 98 | co_return; 99 | }); 100 | co_await waiter; 101 | if (!resumed.load(std::memory_order_acquire)) { 102 | std::println("test_context: C FAILED - waiter did not resume immediately after cancellation"); 103 | co_return 1; 104 | } 105 | std::println("test_context: C passed"); 106 | } 107 | 108 | // Test D: cancellation callback tolerates concurrent cancel invocations 109 | { 110 | constexpr int canceller_count = 4; 111 | auto ctx = context::with_cancel(); 112 | std::atomic callback_count{0}; 113 | std::atomic ready{0}; 114 | 115 | co_await ctx->set_cancel_callback([&]() { callback_count.fetch_add(1, std::memory_order_acq_rel); }); 116 | 117 | std::array, canceller_count> cancellers{}; 118 | for (auto i = 0; i < canceller_count; ++i) { 119 | cancellers[i] = co_invoke([ctx, &ready]() -> future_spawn { 120 | ready.fetch_add(1, std::memory_order_acq_rel); 121 | while (ready.load(std::memory_order_acquire) < canceller_count) { co_await sleep_for(1us); } 122 | co_await ctx->cancel(); 123 | co_return; 124 | }); 125 | } 126 | 127 | for (auto &c : cancellers) { co_await c; } 128 | 129 | const auto observed = callback_count.load(std::memory_order_acquire); 130 | if (observed != canceller_count) { 131 | std::println( 132 | "test_context: D FAILED - cancel callback did not run for each canceller (observed = {}, expected = {})", 133 | observed, canceller_count); 134 | co_return 1; 135 | } 136 | std::println("test_context: D passed"); 137 | } 138 | 139 | std::println("test_context: all checks passed"); 140 | co_return 0; 141 | } 142 | -------------------------------------------------------------------------------- /tests/sync/test_mutex.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace asco; 16 | 17 | future async_main() { 18 | std::println("test_mutex: start"); 19 | 20 | // Test A: guard move semantics reflect ownership transfer 21 | { 22 | sync::mutex<> mtx; 23 | with(auto guard = co_await mtx.lock()) { 24 | auto moved = std::move(guard); 25 | if (guard) { 26 | std::println("test_mutex: move semantics FAILED - moved-from guard still true"); 27 | co_return 1; 28 | } 29 | if (!moved) { 30 | std::println("test_mutex: move semantics FAILED - moved-to guard false"); 31 | co_return 1; 32 | } 33 | } 34 | else { 35 | std::println("test_mutex: move semantics FAILED - initial guard invalid"); 36 | co_return 1; 37 | } 38 | std::println("test_mutex: move semantics passed"); 39 | } 40 | 41 | // Test B: lock releases on destruction and can be reacquired 42 | { 43 | sync::mutex<> mtx; 44 | with(auto guard = co_await mtx.lock()) { 45 | // Guard left intentionally unused; scope exit releases the lock. 46 | } 47 | else { 48 | std::println("test_mutex: reacquire FAILED - initial lock invalid"); 49 | co_return 1; 50 | } 51 | with(auto guard = co_await mtx.lock()) { 52 | // Successful reacquire ensures mutex unlocks on guard destruction. 53 | } 54 | else { 55 | std::println("test_mutex: reacquire FAILED - second lock invalid"); 56 | co_return 1; 57 | } 58 | std::println("test_mutex: reacquire passed"); 59 | } 60 | 61 | // Test C: mutual exclusion under contention 62 | { 63 | sync::mutex<> mtx; 64 | std::atomic active{0}; 65 | std::atomic violations{0}; 66 | int counter = 0; 67 | constexpr int worker_count = 8; 68 | constexpr int iterations = 200; 69 | std::vector> workers; 70 | workers.reserve(worker_count); 71 | 72 | for (int i = 0; i < worker_count; ++i) { 73 | workers.push_back(co_invoke([&]() -> future_spawn { 74 | for (int j = 0; j < iterations; ++j) { 75 | with(auto guard = co_await mtx.lock()) { 76 | auto prev = active.fetch_add(1, std::memory_order_acq_rel); 77 | if (prev != 0) { 78 | violations.fetch_add(1, std::memory_order_acq_rel); 79 | } 80 | ++counter; 81 | concurrency::withdraw<8>(); 82 | active.fetch_sub(1, std::memory_order_acq_rel); 83 | } 84 | else { 85 | violations.fetch_add(1, std::memory_order_acq_rel); 86 | } 87 | } 88 | co_return; 89 | })); 90 | } 91 | 92 | for (auto &worker : workers) { co_await worker; } 93 | 94 | auto v = violations.load(std::memory_order_acquire); 95 | if (v != 0) { 96 | std::println("test_mutex: mutual exclusion FAILED - violations {}", v); 97 | co_return 1; 98 | } 99 | if (counter != worker_count * iterations) { 100 | std::println("test_mutex: mutual exclusion FAILED - counter {}", counter); 101 | co_return 1; 102 | } 103 | std::println("test_mutex: mutual exclusion passed"); 104 | } 105 | 106 | // Test D: mutex guard exposes value and preserves move semantics 107 | { 108 | sync::mutex mtx{std::string("hello")}; 109 | with(auto guard = co_await mtx.lock()) { 110 | if (*guard != "hello") { 111 | std::println("test_mutex: value guard FAILED - initial state"); 112 | co_return 1; 113 | } 114 | *guard = "world"; 115 | auto moved = std::move(guard); 116 | if (guard || !moved || *moved != "world") { 117 | std::println("test_mutex: value guard FAILED - move semantics"); 118 | co_return 1; 119 | } 120 | } 121 | else { 122 | std::println("test_mutex: value guard FAILED - initial lock invalid"); 123 | co_return 1; 124 | } 125 | with(auto guard = co_await mtx.lock()) { 126 | if (*guard != "world") { 127 | std::println("test_mutex: value guard FAILED - persistence"); 128 | co_return 1; 129 | } 130 | } 131 | else { 132 | std::println("test_mutex: value guard FAILED - persistence lock invalid"); 133 | co_return 1; 134 | } 135 | std::println("test_mutex: value guard passed"); 136 | } 137 | 138 | std::println("test_mutex: all checks passed"); 139 | co_return 0; 140 | } 141 | -------------------------------------------------------------------------------- /tests/sync/test_semaphore.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace asco; 15 | using namespace std::chrono_literals; 16 | 17 | future async_main() { 18 | std::println("test_semaphore: start"); 19 | 20 | // Test A: notify (release) before wait 21 | { 22 | binary_semaphore sem{0}; 23 | sem.release(); // notify before wait 24 | co_await sem.acquire(); // should not suspend forever 25 | std::println("test_semaphore: notify-before-wait passed"); 26 | } 27 | 28 | // Test B: wait before notify 29 | { 30 | binary_semaphore sem{0}; 31 | std::atomic done{false}; 32 | 33 | auto waiter = co_invoke([&sem, &done] -> future_spawn { 34 | co_await sem.acquire(); 35 | done.store(true, std::memory_order::release); 36 | std::println("test_semaphore: waiter resumed"); 37 | co_return; 38 | }); 39 | 40 | // Give runtime a short spin to ensure task is scheduled if needed. 41 | // concurrency::withdraw<100>(); 42 | 43 | sem.release(); // wake the waiter 44 | co_await waiter; // wait for waiter to finish 45 | 46 | if (!done.load(std::memory_order::acquire)) { 47 | std::println("test_semaphore: wait-before-notify FAILED"); 48 | co_return 1; 49 | } 50 | std::println("test_semaphore: wait-before-notify passed"); 51 | } 52 | 53 | // Test C: Large quantity of concurrency acquire and release 54 | { 55 | constexpr size_t N = 1000; 56 | counting_semaphore sem{0}; 57 | std::atomic counter{0}; 58 | 59 | auto worker = co_invoke([&sem, &counter] -> future_spawn { 60 | for (size_t i{0}; i < N; ++i) { 61 | co_await sem.acquire(); 62 | counter.fetch_add(1, std::memory_order::release); 63 | } 64 | co_return; 65 | }); 66 | 67 | // Give runtime a short spin to ensure task is scheduled if needed. 68 | // concurrency::withdraw<100>(); 69 | 70 | std::println("test_semaphore: releasing {} times", N); 71 | 72 | sem.release(N); 73 | 74 | co_await worker; 75 | 76 | if (auto c = counter.load(std::memory_order::acquire); c != N) { 77 | std::println("test_semaphore: large concurrency acquire/release FAILED: {}", c); 78 | co_return 1; 79 | } 80 | std::println("test_semaphore: large concurrency acquire/release passed"); 81 | } 82 | 83 | // Test D: acquire_for timeout without release 84 | { 85 | binary_semaphore sem{0}; 86 | auto ok = co_await sem.acquire_for(50ms); 87 | if (ok) { 88 | std::println("test_semaphore: acquire_for timeout FAILED - expected false"); 89 | co_return 1; 90 | } 91 | std::println("test_semaphore: acquire_for timeout passed"); 92 | } 93 | 94 | // Test E: acquire_for success when release happens before timeout 95 | { 96 | binary_semaphore sem{0}; 97 | 98 | auto releaser = co_invoke([&sem] -> future_spawn { 99 | co_await sleep_for(20ms); 100 | sem.release(); 101 | co_return; 102 | }); 103 | 104 | auto ok = co_await sem.acquire_for(200ms); 105 | co_await releaser; 106 | if (!ok) { 107 | std::println("test_semaphore: acquire_for success FAILED - expected true"); 108 | co_return 1; 109 | } 110 | std::println("test_semaphore: acquire_for success passed"); 111 | } 112 | 113 | // Test F: acquire_until with absolute time (timeout and success) 114 | { 115 | // F1: timeout 116 | { 117 | binary_semaphore sem{0}; 118 | auto deadline = std::chrono::high_resolution_clock::now() + 50ms; 119 | auto ok = co_await sem.acquire_until(deadline); 120 | if (ok) { 121 | std::println("test_semaphore: acquire_until timeout FAILED - expected false"); 122 | co_return 1; 123 | } 124 | std::println("test_semaphore: acquire_until timeout passed"); 125 | } 126 | 127 | // F2: success before deadline 128 | { 129 | binary_semaphore sem{0}; 130 | auto deadline = std::chrono::high_resolution_clock::now() + 200ms; 131 | 132 | auto releaser = co_invoke([&sem] -> future_spawn { 133 | co_await sleep_for(20ms); 134 | sem.release(); 135 | co_return; 136 | }); 137 | 138 | auto ok = co_await sem.acquire_until(deadline); 139 | co_await releaser; 140 | if (!ok) { 141 | std::println("test_semaphore: acquire_until success FAILED - expected true"); 142 | co_return 1; 143 | } 144 | std::println("test_semaphore: acquire_until success passed"); 145 | } 146 | } 147 | 148 | std::println("test_semaphore: all checks passed"); 149 | co_return 0; 150 | } 151 | -------------------------------------------------------------------------------- /asco/sync/rwspin.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace asco::sync { 10 | 11 | using namespace types; 12 | 13 | template 14 | class rwspin; 15 | 16 | template<> 17 | class rwspin { 18 | public: 19 | rwspin() = default; 20 | rwspin(const rwspin &) = delete; 21 | rwspin(rwspin &&) = delete; 22 | 23 | class read_guard { 24 | friend rwspin; 25 | 26 | rwspin &rw; 27 | bool none{false}; 28 | 29 | read_guard(rwspin &rw) noexcept 30 | : rw{rw} { 31 | size_t expected; 32 | do { 33 | for (expected = rw.state.load(morder::relaxed); (expected & write_mask) != 0; 34 | expected = rw.state.load(morder::relaxed)) {} 35 | } while ( 36 | !rw.state.compare_exchange_weak(expected, expected + 1, morder::acq_rel, morder::relaxed)); 37 | } 38 | 39 | public: 40 | read_guard(read_guard &&rhs) noexcept 41 | : rw{rhs.rw} 42 | , none{rhs.none} { 43 | rhs.none = true; 44 | } 45 | 46 | ~read_guard() noexcept { rw.state.fetch_sub(1, morder::release); } 47 | 48 | operator bool() const noexcept { return !none; } 49 | }; 50 | 51 | class write_guard { 52 | friend rwspin; 53 | 54 | rwspin &rw; 55 | bool none{false}; 56 | 57 | write_guard(rwspin &rw) noexcept 58 | : rw{rw} { 59 | size_t withdraw_count{0}; 60 | for (size_t expected = 0; 61 | !rw.state.compare_exchange_weak(expected, write_mask, morder::acq_rel, morder::relaxed); 62 | expected = 0) { 63 | while (rw.state.load(morder::acquire)) { 64 | concurrency::exp_withdraw(withdraw_count); 65 | withdraw_count++; 66 | if (withdraw_count > 16) 67 | withdraw_count = 16; 68 | } 69 | } 70 | // We don't use std::this_thread::yield() because while we use spin locks, the competitors of this 71 | // lock are largely (almost 100%, because we have cpu affinity for worker threads and task 72 | // stealing) in different worker threads. There is no need to yield because either we yield or 73 | // not, the probability of competitors releasing this lock is the same. 74 | } 75 | 76 | public: 77 | write_guard(write_guard &&rhs) noexcept 78 | : rw{rhs.rw} 79 | , none{rhs.none} { 80 | rhs.none = true; 81 | } 82 | 83 | ~write_guard() noexcept { rw.state.store(0, morder::release); } 84 | 85 | operator bool() const noexcept { return !none; } 86 | }; 87 | 88 | read_guard read() noexcept { return {*this}; } 89 | write_guard write() noexcept { return {*this}; } 90 | 91 | private: 92 | static constexpr size_t write_mask = 1ull << (8 * sizeof(size_t) - 1); 93 | atomic_size_t state{0}; 94 | }; 95 | 96 | template 97 | class rwspin { 98 | public: 99 | rwspin() 100 | : value{} {} 101 | 102 | rwspin(const rwspin &) = delete; 103 | rwspin(rwspin &&) = delete; 104 | 105 | explicit rwspin(const T &val) 106 | requires std::copy_constructible 107 | : value{val} {} 108 | 109 | explicit rwspin(T &&val) 110 | requires std::move_constructible 111 | : value{std::move(val)} {} 112 | 113 | template 114 | explicit rwspin(Args &&...args) 115 | requires std::constructible_from 116 | : value(std::forward(args)...) {} 117 | 118 | class read_guard { 119 | friend rwspin; 120 | 121 | rwspin<>::read_guard g; 122 | rwspin &rw; 123 | bool none{false}; 124 | 125 | read_guard(rwspin &rw) noexcept 126 | : g{rw._lock.read()} 127 | , rw{rw} {} 128 | 129 | public: 130 | read_guard(read_guard &&rhs) noexcept 131 | : g{std::move(rhs.g)} 132 | , rw{rhs.rw} 133 | , none{rhs.none} { 134 | rhs.none = true; 135 | } 136 | 137 | operator bool() const noexcept { return !none; } 138 | 139 | const T &operator*() const noexcept { return rw.value; } 140 | const T *operator->() const noexcept { return &rw.value; } 141 | }; 142 | 143 | class write_guard { 144 | friend rwspin; 145 | 146 | rwspin<>::write_guard g; 147 | rwspin &rw; 148 | bool none{false}; 149 | 150 | write_guard(rwspin &rw) noexcept 151 | : g{rw._lock.write()} 152 | , rw{rw} {} 153 | 154 | public: 155 | write_guard(write_guard &&rhs) noexcept 156 | : g{std::move(rhs.g)} 157 | , rw{rhs.rw} 158 | , none{rhs.none} { 159 | rhs.none = true; 160 | } 161 | 162 | operator bool() const noexcept { return !none; } 163 | 164 | T &operator*() noexcept { return rw.value; } 165 | const T &operator*() const noexcept { return rw.value; } 166 | 167 | T *operator->() noexcept { return &rw.value; } 168 | const T *operator->() const noexcept { return &rw.value; } 169 | }; 170 | 171 | read_guard read() noexcept { return {*this}; } 172 | write_guard write() noexcept { return {*this}; } 173 | 174 | T &&get() noexcept 175 | requires std::move_constructible || std::is_move_assignable_v 176 | { 177 | value_moved.store(true, morder::relaxed); 178 | return std::move(value); 179 | } 180 | 181 | private: 182 | T value; 183 | atomic_bool value_moved{false}; 184 | rwspin<> _lock; 185 | }; 186 | 187 | }; // namespace asco::sync 188 | 189 | namespace asco { 190 | 191 | using sync::rwspin; 192 | 193 | }; // namespace asco 194 | -------------------------------------------------------------------------------- /asco/generator.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace asco::base { 15 | 16 | using namespace concepts; 17 | 18 | namespace cq = continuous_queue; 19 | 20 | template 21 | requires(!std::is_void_v && (bool)"Generator type T cannot be void.") 22 | struct generator_base : public future_base { 23 | using base_future = future_base; 24 | 25 | struct promise_type; 26 | using coroutine_handle_type = base_future::coroutine_handle_type; 27 | using deliver_type = typename base_future::deliver_type; 28 | 29 | struct promise_type : public base_future::promise_type { 30 | using promise_base = typename base_future::promise_type; 31 | 32 | cq::sender yield_tx; 33 | 34 | generator_base get_return_object( 35 | panic::coroutine_trace_handle caller_cthdl = 36 | *cpptrace::stacktrace::current(1, 1).begin()) noexcept { 37 | cq::receiver rx; 38 | std::tie(yield_tx, rx) = cq::create(); 39 | auto res = generator_base{promise_base::get_return_object(caller_cthdl), std::move(rx)}; 40 | res.this_task->extra_slot.emplace(false, 0); 41 | return std::move(res); 42 | } 43 | 44 | auto yield_value(passing value) { 45 | if (yield_tx.push(std::move(value))) [[unlikely]] { 46 | panic::panic("[ASCO] generator::promise_type::yield_value(): yield queue is closed."); 47 | } 48 | promise_base::this_task->extra_slot.get().release(); 49 | 50 | return std::suspend_never{}; 51 | } 52 | 53 | void unhandled_exception() noexcept { 54 | promise_base::unhandled_exception(); 55 | yield_tx.stop(); 56 | promise_base::this_task->extra_slot.get().release(); 57 | } 58 | 59 | void return_void() noexcept { 60 | yield_tx.stop(); 61 | promise_base::this_task->extra_slot.get().release(); 62 | promise_base::return_void(); 63 | } 64 | }; 65 | 66 | operator bool() const { 67 | if (base_future::none) 68 | return false; 69 | 70 | return !yield_rx.is_stopped(); 71 | } 72 | 73 | auto operator()() -> future> { 74 | auto &sem = base_future::this_task->extra_slot.get(); 75 | co_await sem.acquire(); 76 | 77 | if (base_future::this_task->e_thrown.load(morder::acquire) && sem.get_counter() == 0) { 78 | if (auto &e = base_future::this_task->e_ptr) { 79 | auto tmp = e; 80 | e = nullptr; 81 | base_future::this_task->e_rethrown.store(true, morder::release); 82 | std::rethrow_exception(tmp); 83 | } 84 | } 85 | 86 | for (size_t i{0}; true; ++i) { 87 | if (auto res = yield_rx.pop()) 88 | co_return std::move(*res); 89 | else if (res.error() == queue::pop_fail::closed) 90 | co_return std::nullopt; 91 | 92 | if (i > 100) 93 | co_await yield<>{}; 94 | } 95 | } 96 | 97 | bool await_ready() = delete; 98 | 99 | auto await_suspend(std::coroutine_handle<>) = delete; 100 | 101 | deliver_type await_resume() = delete; 102 | 103 | deliver_type await() = delete; 104 | 105 | generator_base() = default; 106 | 107 | generator_base(const generator_base &) = delete; 108 | 109 | generator_base(generator_base &&rhs) 110 | : base_future(std::move(rhs)) 111 | , yield_rx(std::move(rhs.yield_rx)) {} 112 | 113 | generator_base(base_future &&base_fut, cq::receiver &&rx) 114 | : base_future(std::move(base_fut)) 115 | , yield_rx(std::move(rx)) {} 116 | 117 | generator_base &operator=(generator_base &&rhs) { 118 | if (this == &rhs) 119 | return *this; 120 | 121 | this->~generator_base(); 122 | new (this) generator_base(std::move(rhs)); 123 | return *this; 124 | } 125 | 126 | private: 127 | cq::receiver yield_rx; 128 | }; 129 | 130 | }; // namespace asco::base 131 | 132 | namespace asco { 133 | 134 | template 135 | using generator = base::generator_base; 136 | template 137 | using generator_core = base::generator_base; 138 | 139 | namespace concepts { 140 | 141 | template 142 | struct is_specialization_of_generator_type { 143 | private: 144 | template 145 | static std::true_type test(base::generator_base *); 146 | 147 | template 148 | static std::false_type test(...); 149 | 150 | public: 151 | static constexpr bool value = decltype(test(std::declval()))::value; 152 | }; 153 | 154 | template 155 | constexpr bool is_specialization_of_generator_type_v = is_specialization_of_generator_type::value; 156 | 157 | template 158 | concept generator_type = is_specialization_of_generator_type_v>; 159 | 160 | template 161 | concept generator_function = 162 | std::invocable && generator_type, Args...>>; 163 | 164 | }; // namespace concepts 165 | 166 | namespace base { 167 | 168 | // Pipe operator 169 | template> Fn> 170 | constexpr auto operator|(G &&source, Fn &&consumer) { 171 | return co_invoke(consumer, std::forward>(source)); 172 | } 173 | 174 | }; // namespace base 175 | 176 | }; // namespace asco 177 | -------------------------------------------------------------------------------- /asco/panic/unwind.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace asco::panic { 11 | 12 | static std::string 13 | format_stack_entry(size_t i, const cpptrace::stacktrace_frame &e, bool color, bool is_coroutine = false) { 14 | std::string res; 15 | res.reserve(256); 16 | 17 | if (color) { 18 | res += number_color; 19 | } 20 | res += "·"; 21 | res += std::to_string(i); 22 | if (color) { 23 | res += reset_color; 24 | } 25 | res += " "; 26 | 27 | if (color) { 28 | res += address_color; 29 | } 30 | if (e.is_inline) 31 | res += " (inlined)"; 32 | else 33 | res += std::format("{:#016x}", reinterpret_cast(e.raw_address)); 34 | if (color) { 35 | res += reset_color; 36 | } 37 | res += " in "; 38 | 39 | if (color) { 40 | res += is_coroutine ? co_name_color : name_color; 41 | } 42 | res += e.symbol.ends_with("[clone .resume]") ? e.symbol.substr(0, e.symbol.size() - 16) 43 | : e.symbol.ends_with("[clone .actor]") ? e.symbol.substr(0, e.symbol.size() - 15) 44 | : e.symbol.ends_with("(.resume)") ? e.symbol.substr(0, e.symbol.size() - 10) 45 | : e.symbol.ends_with(".resume") ? e.symbol.substr(0, e.symbol.size() - 8) 46 | : e.symbol; 47 | if (color) { 48 | res += reset_color; 49 | } 50 | 51 | res += "\n at "; 52 | if (!e.filename.empty()) { 53 | std::string_view name = e.filename; 54 | std::vector path; 55 | size_t i{0}; 56 | while (i != name.size()) { 57 | if (name[i] != '/') { 58 | i++; 59 | if (i == name.size()) 60 | path.push_back(std::move(name)); 61 | continue; 62 | } 63 | auto sec = std::string_view{name.begin(), name.begin() + i}; 64 | if (sec == "..") { 65 | if (!path.empty()) 66 | path.pop_back(); 67 | } else if (sec != ".") { 68 | path.push_back(sec); 69 | } 70 | name = name.substr(i + 1); 71 | i = 0; 72 | } 73 | if (color) { 74 | res += file_color; 75 | } 76 | for (const auto &seg : path) { 77 | if (color && seg == path.back()) { 78 | res += file_tail_color; 79 | } 80 | res += seg; 81 | if (seg != path.back()) 82 | res += '/'; 83 | } 84 | if (color) { 85 | res += reset_color; 86 | } 87 | 88 | if (e.line.has_value() && e.line.value()) { 89 | res += ":"; 90 | if (color) { 91 | res += lineno_color; 92 | } 93 | res += std::to_string(e.line.value()); 94 | if (color) { 95 | res += reset_color; 96 | } 97 | if (e.column.has_value() && e.column.value()) { 98 | res += ":"; 99 | if (color) { 100 | res += lineno_color; 101 | } 102 | res += std::to_string(e.column.value()); 103 | if (color) { 104 | res += reset_color; 105 | } 106 | } 107 | } 108 | } else { 109 | if (color) { 110 | res += file_tail_color; 111 | } 112 | res += "??"; 113 | if (color) { 114 | res += reset_color; 115 | } 116 | } 117 | 118 | res += "\n"; 119 | 120 | return res; 121 | } 122 | 123 | std::string unwind(size_t skip, bool color) { 124 | std::string res; 125 | res.reserve(4096); 126 | 127 | auto st = cpptrace::stacktrace::current(skip); 128 | size_t i{0}; 129 | for (auto &e : st) { 130 | res += format_stack_entry(i, e, color); 131 | i++; 132 | } 133 | 134 | return res; 135 | } 136 | 137 | std::string co_unwind(size_t skip, bool color) { 138 | std::string res; 139 | res.reserve(4096); 140 | 141 | auto st = cpptrace::stacktrace::current(skip); 142 | size_t i{0}; 143 | for (auto &e : st) { 144 | bool is_coroutine = e.symbol.ends_with("[clone .resume]") || e.symbol.ends_with("[clone .actor]") 145 | || e.symbol.ends_with("(.resume)") || e.symbol.ends_with(".resume"); 146 | res += format_stack_entry(i, e, color, is_coroutine); 147 | i++; 148 | if (is_coroutine) 149 | break; 150 | } 151 | 152 | auto task = core::runtime::this_runtime().get_task_by(core::worker::this_worker().current_task()); 153 | for (; task; task = task->caller) { 154 | if (!task->caller) { 155 | for (const auto &e : task->raw_stacktrace) { 156 | res += format_stack_entry(i, e, color); 157 | i++; 158 | } 159 | break; 160 | } 161 | 162 | res += format_stack_entry(i, *task->caller_coroutine_trace, color, true); 163 | i++; 164 | } 165 | 166 | return res; 167 | } 168 | 169 | cpptrace::stacktrace co_stacktrace(size_t skip) { 170 | std::vector res; 171 | 172 | auto st = cpptrace::stacktrace::current(skip); 173 | for (auto &e : st) { 174 | bool is_coroutine = e.symbol.ends_with("[clone .resume]") || e.symbol.ends_with("[clone .actor]") 175 | || e.symbol.ends_with("(.resume)") || e.symbol.ends_with(".resume"); 176 | res.push_back(e); 177 | if (is_coroutine) 178 | break; 179 | } 180 | 181 | auto task = core::runtime::this_runtime().get_task_by(core::worker::this_worker().current_task()); 182 | for (; task; task = task->caller) { 183 | if (!task->caller) { 184 | for (const auto &e : task->raw_stacktrace) { res.push_back(e); } 185 | break; 186 | } 187 | 188 | res.push_back(*task->caller_coroutine_trace); 189 | } 190 | 191 | return cpptrace::stacktrace(std::move(res)); 192 | } 193 | 194 | }; // namespace asco::panic 195 | -------------------------------------------------------------------------------- /asco/core/worker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #ifdef __linux__ 10 | # include 11 | # include 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace asco::core { 20 | 21 | using namespace types; 22 | 23 | thread_local atomic worker::_this_worker{nullptr}; 24 | 25 | std::tuple>> worker::sched() { 26 | if (auto act_g = active_tasks.lock(); !act_g->empty()) { 27 | auto t = std::move(act_g->front()); 28 | act_g->pop_front(); 29 | act_g->push_back(t); 30 | return {t->id, std::move(t)}; 31 | } 32 | return {0, nullptr}; 33 | } 34 | 35 | worker::worker( 36 | size_t id, atomic_size_t &load_counter, awake_queue &awake_q, task_receiver &&task_recv, 37 | atomic_size_t &worker_count, atomic_bool &shutting_down) 38 | : daemon{std::format("asco::w{}", id)} 39 | , _id{id} 40 | , task_recv{std::move(task_recv)} 41 | , load_counter{load_counter} 42 | , worker_count{worker_count} 43 | , shutting_down{shutting_down} 44 | , awake_q{awake_q} { 45 | auto _ = daemon::start(); 46 | } 47 | 48 | worker::~worker() { _this_worker.store(nullptr, morder::release); } 49 | 50 | bool worker::init() { 51 | #ifdef __linux__ 52 | { 53 | ::cpu_set_t cpuset; 54 | CPU_ZERO(&cpuset); 55 | CPU_SET(_id, &cpuset); 56 | if (::pthread_setaffinity_np(::pthread_self(), sizeof(cpuset), &cpuset) == -1) { 57 | std::println(stderr, "[ASCO] worker::init(): Failed to set thread affinity."); 58 | return false; 59 | } 60 | } 61 | #endif 62 | 63 | _this_worker.store(this, morder::release); 64 | awake_q.lock()->push_back(_id); 65 | 66 | return true; 67 | } 68 | 69 | bool worker::run_once(std::stop_token &) { 70 | while (true) { 71 | if (auto res = task_recv.pop()) { 72 | auto [id, task] = std::move(*res); 73 | task->worker_ptr = this; 74 | task->scheduled.store(true, morder::release); 75 | register_task(id, std::move(task)); 76 | } else if (res.error() == pop_fail::non_object) { 77 | break; 78 | } else if (res.error() == pop_fail::closed) { 79 | return false; 80 | } 81 | } 82 | 83 | auto [id, task] = sched(); 84 | if (!id) { 85 | sleep_until_awake(); 86 | if (shutting_down.load(morder::acquire)) 87 | return true; // There may be tasks still in the queue. 88 | awake_q.lock()->push_back(_id); 89 | return true; 90 | } 91 | 92 | task_stack = std::move(task->call_chain); 93 | task->corohandle.resume(); 94 | tasks.lock()->at(task_stack.top())->call_chain = std::move(task_stack); 95 | 96 | if (task->spawn_task && task->corohandle.done()) { 97 | // The tasks always suspend themselves, so only unregister_task is OK. 98 | core::runtime::this_runtime().unregister_task(id); 99 | load_counter.fetch_sub(1, morder::acq_rel); 100 | unregister_task(id); 101 | task->corohandle.destroy(); 102 | } 103 | 104 | return true; 105 | } 106 | 107 | void worker::shutdown() { 108 | if (worker_count.fetch_sub(1, morder::release) == 1) 109 | worker_count.notify_one(); 110 | } 111 | 112 | bool worker::in_worker() noexcept { return _this_worker.load(morder::acquire) != nullptr; } 113 | 114 | worker &worker::this_worker() noexcept { return *_this_worker.load(morder::acquire); } 115 | 116 | void worker::register_task(task_id id, std::shared_ptr> task, bool non_spawn) { 117 | tasks.lock()->emplace(id, task); 118 | if (non_spawn) { 119 | suspended_tasks.lock()->emplace(id, std::move(task)); 120 | } else { 121 | active_tasks.lock()->emplace_back(std::move(task)); 122 | } 123 | } 124 | 125 | void worker::unregister_task(task_id id) { 126 | tasks.lock()->erase(id); 127 | if (auto g = suspended_tasks.lock()) { 128 | auto it = g->find(id); 129 | if (it != g->end()) { 130 | g->erase(it); 131 | } 132 | } 133 | } 134 | 135 | void worker::task_enter(task_id id) { task_stack.push(id); } 136 | 137 | task_id worker::task_exit() { 138 | auto id = task_stack.top(); 139 | task_stack.pop(); 140 | return id; 141 | } 142 | 143 | task_id worker::current_task() { return task_stack.top(); } 144 | 145 | bool worker::activate_task(task_id id) { 146 | std::shared_ptr> t; 147 | if (auto susp_g = suspended_tasks.lock()) { 148 | if (susp_g->empty()) { 149 | return false; 150 | } else if (auto it = susp_g->find(id); it != susp_g->end()) { 151 | t = std::move(it->second); 152 | susp_g->erase(it); 153 | } 154 | } 155 | 156 | if (!t) 157 | return false; 158 | 159 | active_tasks.lock()->push_back(std::move(t)); 160 | awake(); 161 | return true; 162 | } 163 | 164 | bool worker::suspend_task(task_id id) { 165 | std::shared_ptr> t; 166 | if (auto act_g = active_tasks.lock()) { 167 | for (auto it = act_g->rbegin(); it != act_g->rend(); ++it) { 168 | if ((*it)->id == id) { 169 | t = std::move(*it); 170 | act_g->erase(act_g->begin() + (act_g->rend() - it - 1)); 171 | break; 172 | } 173 | } 174 | } 175 | if (!t) 176 | return false; 177 | 178 | suspended_tasks.lock()->emplace(id, std::move(t)); 179 | return true; 180 | } 181 | 182 | std::shared_ptr> worker::move_out_suspended_task(task_id id) { 183 | std::shared_ptr> t; 184 | if (auto susp_g = suspended_tasks.lock()) { 185 | auto it = susp_g->find(id); 186 | if (it != susp_g->end()) { 187 | t = std::move(it->second); 188 | susp_g->erase(it); 189 | } 190 | } 191 | if (t) { 192 | t->worker_ptr = nullptr; 193 | t->scheduled.store(false, morder::release); 194 | tasks.lock()->erase(id); 195 | } 196 | return t; 197 | } 198 | 199 | void worker::move_in_suspended_task(task_id id, std::shared_ptr> t) { 200 | t->worker_ptr = this; 201 | t->scheduled.store(true, morder::release); 202 | suspended_tasks.lock()->emplace(id, t); 203 | tasks.lock()->emplace(id, std::move(t)); 204 | } 205 | 206 | }; // namespace asco::core 207 | -------------------------------------------------------------------------------- /docs/zh-cn/src/future.md: -------------------------------------------------------------------------------- 1 | # Future 类型系统 2 | 3 | ASCO 提供了三种核心的 Future 类型,用于不同的协程执行模式:`future`、`future_spawn` 和 `future_core`。本文档将详细介绍这些类型的用法、生命周期与最佳实践。 4 | 5 | ## 类型概览 6 | 7 | ### `future` 8 | 9 | 最基础的协程返回类型,用于同步执行模式。 10 | 11 | ```cpp 12 | template 13 | using future = base::future_base; 14 | ``` 15 | 16 | - 特点 17 | - 同步执行:协程在被 `co_await` 时继承调用者的工作线程 18 | - 直接控制流:不会自动调度到其他线程 19 | - 适用于需要线程亲和性的场景 20 | 21 | ### `future_spawn` 22 | 23 | 用于异步执行的协程返回类型。 24 | 25 | ```cpp 26 | template 27 | using future_spawn = base::future_base; 28 | ``` 29 | 30 | - 特点 31 | - 异步执行:协程会被调度到工作线程池中执行 32 | - 自动负载均衡:runtime 会选择合适的工作线程 33 | - 支持同步等待(`.await()`)和异步等待(`co_await`) 34 | 35 | ### `future_core` 36 | 37 | 核心任务的协程返回类型,用于运行时关键任务。当前行为与 `future_spawn` 相同,但在未来的任务窃取(work stealing)机制中将获得特殊处理。 38 | 39 | ```cpp 40 | template 41 | using future_core = base::future_base; 42 | ``` 43 | 44 | - 特点 45 | - 异步执行:与 `future_spawn` 类似,在工作线程池中执行 46 | - 核心标记:为即将引入的任务窃取机制预留 47 | - 规划特性:未来将不可被其他工作线程窃取,保证在固定线程执行 48 | 49 | ## 通用功能 50 | 51 | 所有 Future 类型都支持以下基本特性: 52 | 53 | ### 移动语义 54 | 55 | - Future 只支持移动,禁止拷贝 56 | - 确保任务的所有权清晰,避免资源泄漏 57 | 58 | ```cpp 59 | future f1 = foo(); // OK:移动构造 60 | auto f2 = std::move(f1); // OK:移动赋值 61 | future f3 = f1; // 错误:禁止拷贝 62 | ``` 63 | 64 | ### 异常传播 65 | 66 | - 自动捕获并传播协程中的异常 67 | - 在 `co_await` 或 `.await()` 时重新抛出 68 | 69 | ```cpp 70 | future may_throw() { 71 | if (error_condition) 72 | throw my_error{}; 73 | co_return; 74 | } 75 | 76 | try { 77 | co_await may_throw(); // 异常会在这里被重新抛出 78 | } catch (const my_error& e) { 79 | // 处理异常 80 | } 81 | ``` 82 | 83 | ### co_invoke 84 | 85 | ```cpp 86 | auto task = co_invoke([] -> future_spawn { 87 | // 耗时任务 88 | co_return; 89 | }); 90 | co_await task; // lambda 表达式随协程一起销毁,此处安全 91 | ``` 92 | 93 | `co_invoke` 会将以右值(包括纯右值和将亡值)传递的可调用对象移动到协程内部,确保其生命周期覆盖整个协程执行期间。 94 | 95 | - 常用于延长 lambda 表达式的生命周期,避免 use-after-free 96 | 97 | ### 值类型约束 98 | 99 | - 要求 T 满足 `move_secure` 概念(可移动) 100 | - 支持 void 类型(`future`) 101 | 102 | ## 执行模型 103 | 104 | ### 同步执行(`future`) 105 | 106 | ```cpp 107 | future compute() { 108 | co_return 42; 109 | } 110 | 111 | // 在调用者线程中同步执行 112 | future caller() { 113 | int value = co_await compute(); // 不会发生异步调度 114 | } 115 | ``` 116 | 117 | ### 异步执行(`future_spawn`) 118 | 119 | ```cpp 120 | future_spawn async_compute() { 121 | co_return 42; 122 | } 123 | 124 | // 两种等待方式 125 | future_spawn async_caller() { 126 | // 1. 异步等待(推荐) 127 | int value = co_await async_compute(); 128 | 129 | // 2. 同步等待(仅在非工作线程中使用) 130 | // int value = async_compute().await(); 131 | } 132 | ``` 133 | 134 | ## 进阶功能 135 | 136 | ### 任务转换 137 | 138 | `future` 可以转换为 `future_spawn` 或 `future_core`: 139 | 140 | ```cpp 141 | future normal() { 142 | co_return 42; 143 | } 144 | 145 | future_spawn to_spawn(future f) { 146 | return f.spawn(); // 转换为异步任务 147 | } 148 | 149 | future_core to_core(future f) { 150 | return f.spawn_core(); // 转换为核心任务 151 | } 152 | ``` 153 | 154 | #### `transport()`:在 worker 之间迁移 `future` 155 | 156 | `transport()` 用于**不改变 Future 类型(仍是 `future`)**的前提下,将一个 `future` 所属的底层任务从“原 worker 的挂起任务集合”迁移到**当前 worker**,从而允许你在另一个 worker 上安全地 `co_await` 它。 157 | 158 | 这在以下场景非常常见: 159 | 160 | - 你把一个 `future` 作为值传递/移动到了另一个以 `future_spawn` 运行的协程里(例如 `select` 内部为每个分支启动的任务)。 161 | - 该 `future` 可能已经在某个 worker 上注册并进入挂起状态;此时直接在另一个 worker 上等待它,会导致任务仍挂在旧 worker 的内部容器里,从而出现“在错误的 worker 上管理挂起/唤醒”的问题。 162 | 163 | `transport()` 的核心效果: 164 | 165 | - 若任务已被调度并处于某个 worker 管理之下,会先从原 worker 的挂起任务集合中移出,再迁入到当前 worker。 166 | - 随后返回一个新的 `future`,其行为等价于等待原 future:返回值与异常传播规则完全一致。 167 | 168 | 约束与注意事项: 169 | 170 | - 仅适用于 `future`(非 spawn 模式的 future)。 171 | - 需要在运行时 worker 线程上下文中调用(因为它依赖 `core::worker::this_worker()` 取得“当前 worker”)。 172 | - 它**不会**把任务变成异步调度任务;如果你的目的是让任务进入线程池异步执行,请使用 `.spawn()`/`.spawn_core()`。 173 | 174 | 示例:在 `future_spawn` 中等待一个来自外部的 `future`: 175 | 176 | ```cpp 177 | future make_sync_work(); 178 | 179 | future_spawn async_main() { 180 | auto f = make_sync_work(); 181 | 182 | // 假设这里之后的执行发生在某个 worker 上,且 f 可能来自/挂起于其它 worker 183 | int v = co_await std::move(f).transport(); 184 | co_return v; 185 | } 186 | ``` 187 | 188 | ### 异常处理与忽略 189 | 190 | `ignore()` 用于忽略对应 `future` 对象所代表的协程的返回值,并吞掉其抛出的异常(可选提供回调用于观测)。 191 | 192 | - 返回值:`ignore()` 返回一个 `future`(spawn 模式),当底层协程完成时该 future 也完成。 193 | - 行为:调用 `ignore()` 会丢弃原协程的返回值;若协程抛出异常,默认不向上抛出;若提供了回调,会在捕获到异常时调用该回调并传入 `std::exception_ptr`。 194 | 195 | 常见用法:在后台启动任务但不关心返回值与错误,或者只想在异常发生时记录/观测但不传播它们。 196 | 197 | ```cpp 198 | // 最常见:fire-and-forget(不等待,不传播异常) 199 | background_task().ignore(); 200 | 201 | // 提供回调以记录异常(回调接受 std::exception_ptr) 202 | cleanup_task().ignore([](std::exception_ptr e) { 203 | try { 204 | std::rethrow_exception(e); 205 | } catch (const std::exception &ex) { 206 | std::cerr << "Cleanup failed: " << ex.what() << '\n'; 207 | } 208 | }); 209 | 210 | // 如果需要等待完成但仍丢弃返回值与异常,可以 co_await 211 | co_await some_future().ignore(); 212 | ``` 213 | 214 | ## 最佳实践 215 | 216 | ### 选择合适的 Future 类型 217 | 218 | 1. 默认使用 `future` 219 | - 适用于大多数异步操作 220 | - 需要低异步调度开销 221 | - 无并发 222 | 223 | 2. 使用 `future_spawn` 当: 224 | - 需要并发 225 | - 自动负载均衡 226 | - 良好的并发性能 227 | 228 | 3. 使用 `future_core` 当: 229 | - 自动负载均衡 230 | - 需要线程亲和、保持不可窃取 231 | 232 | ### 异步编程指南 233 | 234 | - 注意 lambda 表达式的生命周期 235 | 236 | ```cpp 237 | // 错误示例:lambda 生命周期短于异步任务生命周期 238 | 239 | auto task1 = []() -> future { 240 | // 任务逻辑 241 | co_return; 242 | }(); 243 | co_await task1; // 任务启动,但是前面的 lambda 表达式已经销毁,产生 use-after-free 244 | 245 | auto task2 = []() -> future_spawn { 246 | // 耗时任务逻辑 247 | co_return; 248 | }(); 249 | co_await task2; // 任务在刚启动时行为正常,但是很快 lambda 表达式就将被销毁,产生 use-after-free 250 | 251 | // 正确示例:使用 co_invoke 延长 lambda 表达式的生命周期 252 | 253 | auto task = co_invoke([]() -> future { 254 | // 任务逻辑 255 | co_return; 256 | }); 257 | co_await task; // 任务安全执行 258 | ``` 259 | 260 | - 避免在工作线程中使用 `.await()` 261 | 262 | ```cpp 263 | // 错误:在工作线程中同步等待 264 | void wrong() { 265 | auto value = async_task().await(); // 会 panic 266 | } 267 | 268 | // 正确:使用 co_await 269 | future_spawn correct() { 270 | auto value = co_await async_task(); 271 | } 272 | ``` 273 | 274 | - 正确处理异常 275 | 276 | ```cpp 277 | future_spawn robust() { 278 | try { 279 | co_await risky_operation(); 280 | } catch (const std::exception& e) { 281 | // 处理异常 282 | co_return; 283 | } 284 | // 继续执行 285 | } 286 | ``` 287 | 288 | - 合理使用 `ignore()` 289 | 290 | ```cpp 291 | // 适用于确实可以忽略返回值和异常的场景 292 | background_task().ignore(); 293 | 294 | // 需要记录异常时提供回调 295 | cleanup_task().ignore([](auto e) { 296 | log_error("Cleanup failed", e); 297 | }); 298 | ``` 299 | 300 | ### 性能考虑 301 | 302 | 1. 避免不必要的任务转换 303 | - `spawn()` 和 `spawn_core()` 会创建新的协程对象 304 | - 如果最终要异步执行,直接返回对应类型 305 | 306 | 2. 合理使用同步/异步模式 307 | - 短小操作使用 `future` 避免调度开销 308 | - IO 密集型操作使用 `future_spawn` 提高并发度 309 | 310 | ## 调试技巧 311 | 312 | ### 异常追踪 313 | 314 | - ASCO 会自动记录协程创建和异常发生的调用栈 315 | - 异常会保留原始抛出点的上下文 316 | 317 | ### 任务状态检查 318 | 319 | - 可通过 task_id 在运行时中查找任务 320 | - 支持检查任务是否已完成、是否发生异常 321 | 322 | ## 注意事项 323 | 324 | - 使用 `ignore()` 时要谨慎,确保返回值和异常可以被安全忽略 325 | -------------------------------------------------------------------------------- /asco/core/runtime.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace asco::core { 15 | 16 | using compile_time::platform::os; 17 | using compile_time::platform::platform; 18 | 19 | runtime &runtime::init(runtime_builder &&builder) noexcept { 20 | static runtime instance{builder.parallel, std::move(builder.timer)}; 21 | return instance; 22 | } 23 | 24 | runtime &runtime::this_runtime() noexcept { return *_this_runtime; } 25 | 26 | void runtime::register_task(task_id id, std::shared_ptr> task) { 27 | tasks_by_handle.write()->emplace(task->corohandle, task); 28 | task_ids_by_handle.write()->emplace(task->corohandle, id); 29 | tasks_by_id.write()->emplace(id, std::move(task)); 30 | } 31 | 32 | time::timer_concept &runtime::timer() noexcept { return *_timer; } 33 | 34 | void runtime::unregister_task(task_id id) { 35 | auto tasks_by_handle_g = tasks_by_handle.write(); 36 | auto task_ids_by_handle_g = task_ids_by_handle.write(); 37 | auto tasks_by_id_g = tasks_by_id.write(); 38 | auto it = tasks_by_id_g->find(id); 39 | if (it != tasks_by_id_g->end()) { 40 | auto handle = it->second->corohandle; 41 | tasks_by_handle_g->erase(handle); 42 | task_ids_by_handle_g->erase(handle); 43 | tasks_by_id_g->erase(it); 44 | } 45 | } 46 | 47 | std::shared_ptr> runtime::get_task_by(std::coroutine_handle<> handle) { 48 | auto tasks_by_handle_g = tasks_by_handle.read(); 49 | auto it = tasks_by_handle_g->find(handle); 50 | if (it != tasks_by_handle_g->end()) { 51 | return it->second; 52 | } 53 | return nullptr; 54 | } 55 | 56 | std::shared_ptr> runtime::get_task_by(task_id id) { 57 | auto tasks_by_id_g = tasks_by_id.read(); 58 | auto it = tasks_by_id_g->find(id); 59 | if (it != tasks_by_id_g->end()) { 60 | return it->second; 61 | } 62 | return nullptr; 63 | } 64 | 65 | task_id runtime::get_task_id_by(std::coroutine_handle<> handle) { 66 | auto task_ids_by_handle_g = task_ids_by_handle.read(); 67 | auto it = task_ids_by_handle_g->find(handle); 68 | if (it != task_ids_by_handle_g->end()) { 69 | return it->second; 70 | } 71 | return task_id{}; 72 | } 73 | 74 | void runtime::spawn_task(task_id id, std::shared_ptr> task) { 75 | auto pan = [] [[noreturn]] { 76 | panic::co_panic( 77 | "[ASCO] runtime::spawn_task(): Failed to send task. The task queue unexpectedly closed."); 78 | }; 79 | 80 | if (io_worker_count && io_worker_count * calcu_worker_load <= calcu_worker_count * io_worker_load) { 81 | io_worker_load.fetch_add(1, morder::acq_rel); 82 | if (io_task_tx.push({id, std::move(task)})) 83 | pan(); 84 | awake_io_worker_once(); 85 | } else { 86 | calcu_worker_load.fetch_add(1, morder::acq_rel); 87 | if (calcu_task_tx.push({id, std::move(task)})) 88 | pan(); 89 | awake_calcu_worker_once(); 90 | } 91 | } 92 | 93 | void runtime::spawn_core_task(task_id id, std::shared_ptr> task) { 94 | auto pan = [] [[noreturn]] { 95 | panic::co_panic( 96 | "[ASCO] runtime::spawn_core_task(): Failed to send task. The task queue unexpectedly closed."); 97 | }; 98 | 99 | if (calcu_worker_count && io_worker_count * calcu_worker_load <= calcu_worker_count * io_worker_load) { 100 | calcu_worker_load.fetch_add(1, morder::acq_rel); 101 | if (calcu_task_tx.push({id, std::move(task)})) 102 | pan(); 103 | awake_calcu_worker_once(); 104 | } else { 105 | io_worker_load.fetch_add(1, morder::acq_rel); 106 | if (io_task_tx.push({id, std::move(task)})) 107 | pan(); 108 | awake_io_worker_once(); 109 | } 110 | } 111 | 112 | void runtime::awake_all() { 113 | for (auto &w : workers) { w->awake(); } 114 | } 115 | 116 | void runtime::awake_io_worker_once() { 117 | size_t wid{0}; 118 | bool found{false}; 119 | if (auto g = io_worker_queue.lock(); !g->empty()) { 120 | wid = g->front(); 121 | g->pop_front(); 122 | found = true; 123 | } 124 | if (found) 125 | workers[wid]->awake(); 126 | else 127 | awake_all(); 128 | } 129 | 130 | void runtime::awake_calcu_worker_once() { 131 | size_t wid{0}; 132 | bool found{false}; 133 | if (auto g = calcu_worker_queue.lock(); !g->empty()) { 134 | wid = g->front(); 135 | g->pop_front(); 136 | found = true; 137 | } 138 | if (found) 139 | workers[wid]->awake(); 140 | else 141 | awake_all(); 142 | } 143 | 144 | runtime::runtime(size_t parallel, std::unique_ptr &&timer_ptr) 145 | : _timer{std::move(timer_ptr)} { 146 | if (parallel == 0) { 147 | parallel = std::thread::hardware_concurrency(); 148 | if (parallel == 0) 149 | parallel = 4; 150 | } 151 | 152 | _this_runtime = this; 153 | 154 | workers.reserve(parallel); 155 | worker_count.store(parallel, morder::release); 156 | 157 | auto [iotx, iorx] = cq::create(); 158 | auto [caltx, calrx] = cq::create(); 159 | 160 | io_task_tx = std::move(iotx); 161 | calcu_task_tx = std::move(caltx); 162 | 163 | for (size_t i = 0; i < parallel; ++i) { 164 | bool is_calculator{false}; 165 | // The hyper thread cores are usually the high frequency cores Use them as calculator workers 166 | if constexpr (platform::os_is(os::linux)) { 167 | try { 168 | std::string path = 169 | std::format("/sys/devices/system/cpu/cpu{}/topology/thread_siblings_list", i); 170 | std::ifstream f(path); 171 | if (!f.is_open()) // If failed, use this worker as IO worker (is_calculator == false) 172 | goto hyperthreading_detected; 173 | std::string buf; 174 | std::vector siblings; 175 | while (std::getline(f, buf, '-')) { siblings.push_back(std::atoi(buf.c_str())); } 176 | f.close(); 177 | if (siblings.size() > 1) { 178 | is_calculator = true; 179 | } 180 | } catch (...) { 181 | // If failed, use this worker as IO worker (is_calculator == false) 182 | goto hyperthreading_detected; 183 | } 184 | } 185 | 186 | hyperthreading_detected: 187 | 188 | task_receiver rx = is_calculator ? calrx : iorx; 189 | awake_queue &wq = is_calculator ? calcu_worker_queue : io_worker_queue; 190 | atomic_size_t &lc = is_calculator ? calcu_worker_load : io_worker_load; 191 | 192 | if (is_calculator) { 193 | calcu_worker_count++; 194 | } else { 195 | io_worker_count++; 196 | } 197 | 198 | workers.push_back(std::make_unique(i, lc, wq, std::move(rx), worker_count, shutting_down)); 199 | } 200 | } 201 | 202 | runtime::~runtime() { 203 | shutting_down.store(true, morder::release); 204 | io_task_tx.stop(); 205 | calcu_task_tx.stop(); 206 | awake_all(); 207 | worker_count.wait(0); 208 | } 209 | 210 | }; // namespace asco::core 211 | -------------------------------------------------------------------------------- /asco/concurrency/concurrency.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 pointer-to-bios 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(_MSC_VER) 11 | # if defined(_M_IX86) || defined(_M_X64) 12 | # include // _mm_pause 13 | # elif defined(_M_ARM) || defined(_M_ARM64) 14 | # include // __yield 15 | # endif 16 | #endif 17 | 18 | namespace asco::concurrency { 19 | 20 | using namespace types; 21 | using namespace concepts; 22 | 23 | inline void cpu_relax() noexcept { 24 | #if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86) 25 | # if defined(_MSC_VER) 26 | _mm_pause(); 27 | # elif defined(__GNUC__) || defined(__clang__) 28 | __builtin_ia32_pause(); 29 | # else 30 | asm volatile("pause"); 31 | # endif 32 | #elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM) 33 | # if defined(_MSC_VER) 34 | __yield(); 35 | # elif defined(__GNUC__) || defined(__clang__) 36 | asm volatile("yield"); 37 | # endif 38 | #endif 39 | } 40 | 41 | template 42 | void withdraw() noexcept { 43 | for (size_t i{0}; i < N; ++i) concurrency::cpu_relax(); 44 | } 45 | 46 | inline void exp_withdraw(size_t i) noexcept { 47 | switch (1 << i) { 48 | case 1: 49 | withdraw<1>(); 50 | break; 51 | case 2: 52 | withdraw<2>(); 53 | break; 54 | case 4: 55 | withdraw<4>(); 56 | break; 57 | case 8: 58 | withdraw<8>(); 59 | break; 60 | case 16: 61 | withdraw<16>(); 62 | break; 63 | case 32: 64 | withdraw<32>(); 65 | break; 66 | case 64: 67 | withdraw<64>(); 68 | break; 69 | default: 70 | for (size_t j{0}; j < (1 << i) / 64; ++j) withdraw<64>(); 71 | break; 72 | } 73 | } 74 | 75 | template 76 | class atomic_ptr { 77 | public: 78 | struct versioned_ptr { 79 | T *ptr; 80 | size_t version{0}; 81 | 82 | T *get_ptr() noexcept { return ptr; } 83 | 84 | operator T *() noexcept { return ptr; } 85 | operator const T *() const noexcept { return ptr; } 86 | 87 | T *operator->() noexcept { return ptr; } 88 | const T *operator->() const noexcept { return ptr; } 89 | 90 | T &operator*() noexcept { return *ptr; } 91 | const T &operator*() const noexcept { return *ptr; } 92 | 93 | bool operator==(const versioned_ptr &rhs) const noexcept { 94 | return ptr == rhs.ptr && version == rhs.version; 95 | } 96 | 97 | bool operator==(T *rhs) const noexcept { return ptr == rhs; } 98 | 99 | friend bool operator==(T *lhs, const versioned_ptr &rhs) noexcept { return lhs == rhs.ptr; } 100 | }; 101 | 102 | atomic_ptr() noexcept = default; 103 | ~atomic_ptr() noexcept = default; 104 | 105 | atomic_ptr(T *ptr) noexcept 106 | : ptr{{ptr}} {} 107 | 108 | atomic_ptr(const atomic_ptr &) = delete; 109 | atomic_ptr &operator=(const atomic_ptr &) = delete; 110 | 111 | atomic_ptr(atomic_ptr &&other) noexcept { 112 | ptr.store(other.ptr, morder::release); 113 | other.ptr.store({nullptr}, morder::release); 114 | } 115 | 116 | atomic_ptr &operator=(atomic_ptr &&other) noexcept { 117 | if (this != &other) { 118 | ptr.store(other.ptr, morder::release); 119 | other.ptr.store({nullptr}, morder::release); 120 | } 121 | return *this; 122 | } 123 | 124 | versioned_ptr load(morder mo) const noexcept { return ptr.load(mo); } 125 | 126 | void store(T *new_ptr, morder mo) noexcept { 127 | versioned_ptr curr_vp; 128 | do { 129 | curr_vp = ptr.load(mo); 130 | } while (!ptr.compare_exchange_weak(curr_vp, {new_ptr, curr_vp.version + 1}, mo, morder::relaxed)); 131 | } 132 | 133 | bool compare_exchange_weak(versioned_ptr &expected, T *new_ptr, morder mo = morder::seq_cst) noexcept { 134 | return ptr.compare_exchange_weak(expected, {new_ptr, expected.version + 1}, mo); 135 | } 136 | 137 | bool compare_exchange_weak(versioned_ptr &expected, T *new_ptr, morder mo1, morder mo2) noexcept { 138 | return ptr.compare_exchange_weak(expected, {new_ptr, expected.version + 1}, mo1, mo2); 139 | } 140 | 141 | bool compare_exchange_strong(versioned_ptr &expected, T *new_ptr, morder mo = morder::seq_cst) noexcept { 142 | return ptr.compare_exchange_strong(expected, {new_ptr, expected.version + 1}, mo); 143 | } 144 | 145 | bool compare_exchange_strong(versioned_ptr &expected, T *new_ptr, morder mo1, morder mo2) noexcept { 146 | return ptr.compare_exchange_strong(expected, {new_ptr, expected.version + 1}, mo1, mo2); 147 | } 148 | 149 | private: 150 | atomic ptr{{nullptr}}; 151 | }; 152 | 153 | template<> 154 | class atomic_ptr { 155 | public: 156 | struct versioned_ptr { 157 | void *ptr; 158 | size_t version{0}; 159 | 160 | void *get_ptr() noexcept { return ptr; } 161 | 162 | operator void *() noexcept { return ptr; } 163 | operator const void *() const noexcept { return ptr; } 164 | 165 | bool operator==(const versioned_ptr &rhs) const noexcept { 166 | return ptr == rhs.ptr && version == rhs.version; 167 | } 168 | 169 | bool operator==(void *rhs) const noexcept { return ptr == rhs; } 170 | 171 | friend bool operator==(void *lhs, const versioned_ptr &rhs) noexcept { return lhs == rhs.ptr; } 172 | }; 173 | 174 | atomic_ptr() noexcept = default; 175 | ~atomic_ptr() noexcept = default; 176 | 177 | atomic_ptr(void *ptr) noexcept 178 | : ptr{{ptr}} {} 179 | 180 | atomic_ptr(const atomic_ptr &) = delete; 181 | atomic_ptr &operator=(const atomic_ptr &) = delete; 182 | 183 | atomic_ptr(atomic_ptr &&other) noexcept { 184 | ptr.store(other.ptr, morder::release); 185 | other.ptr.store({nullptr}, morder::release); 186 | } 187 | 188 | atomic_ptr &operator=(atomic_ptr &&other) noexcept { 189 | if (this != &other) { 190 | ptr.store(other.ptr, morder::release); 191 | other.ptr.store({nullptr}, morder::release); 192 | } 193 | return *this; 194 | } 195 | 196 | versioned_ptr load(morder mo) const noexcept { return ptr.load(mo); } 197 | 198 | void store(void *new_ptr, morder mo) noexcept { 199 | versioned_ptr curr_vp; 200 | do { 201 | curr_vp = ptr.load(mo); 202 | } while (!ptr.compare_exchange_weak(curr_vp, {new_ptr, curr_vp.version + 1}, mo, morder::relaxed)); 203 | } 204 | 205 | bool compare_exchange_weak(versioned_ptr &expected, void *new_ptr, morder mo = morder::seq_cst) noexcept { 206 | return ptr.compare_exchange_weak(expected, {new_ptr, expected.version + 1}, mo); 207 | } 208 | 209 | bool compare_exchange_weak(versioned_ptr &expected, void *new_ptr, morder mo1, morder mo2) noexcept { 210 | return ptr.compare_exchange_weak(expected, {new_ptr, expected.version + 1}, mo1, mo2); 211 | } 212 | 213 | bool 214 | compare_exchange_strong(versioned_ptr &expected, void *new_ptr, morder mo = morder::seq_cst) noexcept { 215 | return ptr.compare_exchange_strong(expected, {new_ptr, expected.version + 1}, mo); 216 | } 217 | 218 | bool compare_exchange_strong(versioned_ptr &expected, void *new_ptr, morder mo1, morder mo2) noexcept { 219 | return ptr.compare_exchange_strong(expected, {new_ptr, expected.version + 1}, mo1, mo2); 220 | } 221 | 222 | private: 223 | atomic ptr{{nullptr}}; 224 | }; 225 | 226 | }; // namespace asco::concurrency 227 | --------------------------------------------------------------------------------