Skip to content

Commit da38cfe

Browse files
committed
feat: add coro_local
1 parent 6a85f24 commit da38cfe

File tree

7 files changed

+978
-0
lines changed

7 files changed

+978
-0
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,9 @@ jobs:
200200
working-directory: build
201201
run: |
202202
./coro_multi_thread_st${{ matrix.env.BIN_SUFFIX }}
203+
204+
- name: Test coro_local
205+
if: always()
206+
working-directory: build
207+
run: |
208+
./coro_coro_local${{ matrix.env.BIN_SUFFIX }}

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,8 @@ if (CORO_BUILD_TEST)
124124
set(TARGET_NAME ${PROJECT_NAME}_multi_thread_st)
125125
add_executable(${TARGET_NAME} test/coro_multi_thread.cpp)
126126
target_compile_definitions(${TARGET_NAME} PRIVATE -DCORO_USE_SINGLE_THREAD)
127+
128+
# test coro_local
129+
set(TARGET_NAME ${PROJECT_NAME}_coro_local)
130+
add_executable(${TARGET_NAME} test/coro_local.cpp)
127131
endif ()

README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ A lightweight C++20 coroutine library with async tasks, concurrency control, and
2727
- [wait_group](#wait_group)
2828
- [latch](#latch)
2929
- [event](#event)
30+
- [Coroutine-Local Storage](#coroutine-local-storage)
3031
- [Callback to Coroutine](#callback-to-coroutine)
3132
- [Configuration Options](#configuration-options)
3233
- [Building Tests](#building-tests)
@@ -122,6 +123,8 @@ I have also learned about some well-known C++20 coroutine open-source libraries,
122123
| `coro::executor_poll` | Polling based executor |
123124
| `coro::current_executor()` | Get current executor |
124125
| `coro::callback_awaiter<T>` | Convert callback-style APIs to coroutines |
126+
| `coro::coro_local<T>` | Coroutine-local storage (like thread_local) |
127+
| `coro::inherit_coro_local()` | Inherit parent coroutine's local storage |
125128

126129
## Features
127130

@@ -139,6 +142,7 @@ I have also learned about some well-known C++20 coroutine open-source libraries,
139142
- 📦 **Embedded Support**: Compatible with MCU and embedded platforms
140143
- 🧩 **Extended Synchronization Primitives**: Additional synchronization tools including condition variables, events,
141144
latches, semaphores, and wait groups
145+
- 🗂️ **Coroutine-Local Storage**: Thread-local-like storage scoped to coroutines with inheritance support
142146

143147
## Requirements
144148

@@ -692,6 +696,136 @@ async<void> setter() {
692696
}
693697
```
694698

699+
## Coroutine-Local Storage
700+
701+
Coroutine-local storage provides thread-local-like storage scoped to coroutines. Each `coro_local<T>` instance acts as a
702+
unique key for storing values, similar to `thread_local` but for coroutines.
703+
704+
### Basic Usage
705+
706+
```cpp
707+
#include "coro/coro_local.hpp"
708+
709+
// Define a storage key (like thread_local, typically static)
710+
static coro::coro_local<int> request_id;
711+
static coro::coro_local<std::string> user_name;
712+
713+
async<void> process_request() {
714+
// Set value for current coroutine
715+
co_await request_id.set(42);
716+
co_await user_name.set("Alice");
717+
718+
// Get value (returns default-constructed T if not set)
719+
int id = co_await request_id.get();
720+
std::string name = co_await user_name.get();
721+
722+
std::cout << "Processing request " << id << " for " << name << std::endl;
723+
}
724+
```
725+
726+
### API Reference
727+
728+
```cpp
729+
coro::coro_local<T> storage;
730+
731+
// Set value for current coroutine
732+
co_await storage.set(value);
733+
734+
// Get value (returns default T{} if not set)
735+
T value = co_await storage.get();
736+
737+
// Get as optional (returns std::nullopt if not set)
738+
std::optional<T> opt = co_await storage.get_optional();
739+
740+
// Get pointer (returns nullptr if not set)
741+
T* ptr = co_await storage.get_ptr();
742+
743+
// Check if value exists
744+
bool exists = co_await storage.has();
745+
746+
// Erase the value
747+
co_await storage.erase();
748+
```
749+
750+
### Inheritance from Parent Coroutine
751+
752+
Child coroutines can inherit values from their parent coroutine. By default, child coroutines can read parent values.
753+
Use `inherit_coro_local()` to enable copy-on-write semantics where modifications are local to the child.
754+
755+
```cpp
756+
static coro::coro_local<int> context_id;
757+
758+
async<void> parent_coro() {
759+
co_await context_id.set(100);
760+
761+
// Child coroutine can read parent's value
762+
auto child = []() -> async<void> {
763+
int id = co_await context_id.get();
764+
std::cout << "Child sees: " << id << std::endl; // Prints: 100
765+
co_return;
766+
};
767+
768+
co_await child();
769+
}
770+
```
771+
772+
### Copy-on-Write Semantics
773+
774+
Use `inherit_coro_local()` when you want the child to inherit values but have its own copy for modifications:
775+
776+
```cpp
777+
async<void> parent_coro() {
778+
co_await context_id.set(100);
779+
780+
auto child = []() -> async<void> {
781+
// Enable copy-on-write inheritance
782+
co_await coro::inherit_coro_local();
783+
784+
// Read parent's value
785+
int id = co_await context_id.get(); // 100
786+
787+
// Modify locally (doesn't affect parent)
788+
co_await context_id.set(200);
789+
790+
id = co_await context_id.get(); // 200
791+
co_return;
792+
};
793+
794+
co_await child();
795+
796+
// Parent's value is unchanged
797+
int id = co_await context_id.get(); // Still 100
798+
}
799+
```
800+
801+
### Isolation Between Concurrent Coroutines
802+
803+
Each coroutine has its own isolated storage. Concurrent coroutines do not interfere with each other:
804+
805+
```cpp
806+
async<void> concurrent_example() {
807+
static coro::coro_local<int> task_id;
808+
809+
auto task_a = []() -> async<void> {
810+
co_await task_id.set(111);
811+
co_await coro::sleep(50ms);
812+
int id = co_await task_id.get(); // Still 111
813+
co_return;
814+
};
815+
816+
auto task_b = []() -> async<void> {
817+
co_await task_id.set(222);
818+
co_await coro::sleep(50ms);
819+
int id = co_await task_id.get(); // Still 222
820+
co_return;
821+
};
822+
823+
// Both tasks run concurrently with isolated storage
824+
co_await coro::spawn_local(task_a());
825+
co_await coro::spawn_local(task_b());
826+
}
827+
```
828+
695829
## Callback to Coroutine
696830

697831
Use `callback_awaiter` to convert callback-style APIs to coroutines:

README_CN.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- [wait_group](#wait_group)
2828
- [latch](#latch)
2929
- [event](#event)
30+
- [协程本地存储](#协程本地存储)
3031
- [回调转协程](#回调转协程)
3132
- [配置选项](#配置选项)
3233
- [构建测试](#构建测试)
@@ -107,6 +108,8 @@
107108
| `coro::executor_poll` | 基于轮询的执行器 |
108109
| `coro::current_executor()` | 获取当前执行器 |
109110
| `coro::callback_awaiter<T>` | 将回调式 API 转换为协程 |
111+
| `coro::coro_local<T>` | 协程本地存储(类似 thread_local) |
112+
| `coro::inherit_coro_local()` | 继承父协程的本地存储 |
110113

111114
## 特性
112115

@@ -123,6 +126,7 @@
123126
- 🔍 **单元测试**:完善的单元测试和集成测试
124127
- 📦 **嵌入式支持**:支持 MCU 和嵌入式平台
125128
- 🧩 **扩展同步原语**:提供额外的同步工具,包括条件变量、事件、门闩、信号量和等待组
129+
- 🗂️ **协程本地存储**:类似 thread_local 的协程作用域存储,支持继承
126130

127131
## 要求
128132

@@ -676,6 +680,135 @@ async<void> setter() {
676680
}
677681
```
678682

683+
## 协程本地存储
684+
685+
协程本地存储提供了类似 `thread_local` 的存储机制,但作用域是协程。每个 `coro_local<T>` 实例作为一个唯一的键来存储值,类似于
686+
`thread_local`,但针对协程。
687+
688+
### 基本用法
689+
690+
```cpp
691+
#include "coro/coro_local.hpp"
692+
693+
// 定义存储键(类似 thread_local,通常为 static)
694+
static coro::coro_local<int> request_id;
695+
static coro::coro_local<std::string> user_name;
696+
697+
async<void> process_request() {
698+
// 为当前协程设置值
699+
co_await request_id.set(42);
700+
co_await user_name.set("Alice");
701+
702+
// 获取值(如果未设置则返回默认构造的 T)
703+
int id = co_await request_id.get();
704+
std::string name = co_await user_name.get();
705+
706+
std::cout << "处理请求 " << id << ",用户 " << name << std::endl;
707+
}
708+
```
709+
710+
### API 参考
711+
712+
```cpp
713+
coro::coro_local<T> storage;
714+
715+
// 为当前协程设置值
716+
co_await storage.set(value);
717+
718+
// 获取值(如果未设置则返回默认值 T{})
719+
T value = co_await storage.get();
720+
721+
// 获取 optional(如果未设置则返回 std::nullopt)
722+
std::optional<T> opt = co_await storage.get_optional();
723+
724+
// 获取指针(如果未设置则返回 nullptr)
725+
T* ptr = co_await storage.get_ptr();
726+
727+
// 检查值是否存在
728+
bool exists = co_await storage.has();
729+
730+
// 删除值
731+
co_await storage.erase();
732+
```
733+
734+
### 从父协程继承
735+
736+
子协程可以继承父协程的值。默认情况下,子协程可以读取父协程的值。使用 `inherit_coro_local()` 启用写时复制语义,使修改仅对子协程生效。
737+
738+
```cpp
739+
static coro::coro_local<int> context_id;
740+
741+
async<void> parent_coro() {
742+
co_await context_id.set(100);
743+
744+
// 子协程可以读取父协程的值
745+
auto child = []() -> async<void> {
746+
int id = co_await context_id.get();
747+
std::cout << "子协程看到: " << id << std::endl; // 输出: 100
748+
co_return;
749+
};
750+
751+
co_await child();
752+
}
753+
```
754+
755+
### 写时复制语义
756+
757+
当你希望子协程继承值但拥有自己的副本进行修改时,使用 `inherit_coro_local()`
758+
759+
```cpp
760+
async<void> parent_coro() {
761+
co_await context_id.set(100);
762+
763+
auto child = []() -> async<void> {
764+
// 启用写时复制继承
765+
co_await coro::inherit_coro_local();
766+
767+
// 读取父协程的值
768+
int id = co_await context_id.get(); // 100
769+
770+
// 本地修改(不影响父协程)
771+
co_await context_id.set(200);
772+
773+
id = co_await context_id.get(); // 200
774+
co_return;
775+
};
776+
777+
co_await child();
778+
779+
// 父协程的值保持不变
780+
int id = co_await context_id.get(); // 仍然是 100
781+
}
782+
```
783+
784+
### 并发协程之间的隔离
785+
786+
每个协程都有自己独立的存储空间。并发协程之间互不干扰:
787+
788+
```cpp
789+
async<void> concurrent_example() {
790+
static coro::coro_local<int> task_id;
791+
792+
auto task_a = []() -> async<void> {
793+
co_await task_id.set(111);
794+
co_await coro::sleep(50ms);
795+
int id = co_await task_id.get(); // 仍然是 111
796+
co_return;
797+
};
798+
799+
auto task_b = []() -> async<void> {
800+
co_await task_id.set(222);
801+
co_await coro::sleep(50ms);
802+
int id = co_await task_id.get(); // 仍然是 222
803+
co_return;
804+
};
805+
806+
// 两个任务并发运行,存储空间相互隔离
807+
co_await coro::spawn_local(task_a());
808+
co_await coro::spawn_local(task_b());
809+
}
810+
```
811+
679812
## 回调转协程
680813

681814
使用 `callback_awaiter` 将回调式 API 转换为协程:

include/coro/coro.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
#include "executor.hpp"
1313

14+
// Forward declaration for coro_local support
15+
namespace coro::detail {
16+
struct coro_local_map;
17+
}
18+
1419
/// options
1520
// #define CORO_DISABLE_EXCEPTION
1621

@@ -177,6 +182,10 @@ struct awaitable_promise : awaitable_promise_value<T>, debug_coro_promise {
177182
std::coroutine_handle<> parent_handle_{};
178183
executor* executor_ = nullptr; // from bind_executor or inherit from caller
179184
awaitable<T>* awaitable_ = nullptr; // have awaitable lived or detached
185+
186+
// Local storage support
187+
std::shared_ptr<detail::coro_local_map> local_storage_;
188+
std::shared_ptr<detail::coro_local_map> parent_local_storage_; // for inheritance
180189
};
181190

182191
template <typename T>
@@ -239,6 +248,8 @@ struct awaitable {
239248
if (!current_coro_handle_.promise().executor_) {
240249
current_coro_handle_.promise().executor_ = h.promise().executor_;
241250
}
251+
// inherit local storage from parent coroutine
252+
current_coro_handle_.promise().parent_local_storage_ = h.promise().local_storage_;
242253
current_coro_handle_.promise().parent_handle_ = h;
243254
return current_coro_handle_;
244255
}

0 commit comments

Comments
 (0)