@@ -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
697831Use ` callback_awaiter ` to convert callback-style APIs to coroutines:
0 commit comments