libcoro
repository·main·Indexed 21 days ago
https://github.com/jbaldwin/libcoroA C++20 coroutine library providing low-level, high-performance constructs and networking primitives for asynchronous applications. It includes execution control primitives like sync_wait, when_all, and when_any; coroutine types such as task<T> and generator<T>; and synchronization tools including mutex, shared_mutex, semaphore, event, and latch. The library also features executors like thread_pool and scheduler, as well as asynchronous networking support for DNS, TCP, UDP, and TLS.
What's inside libcoro
- libcoro is a C++20 coroutine library providing low-level constructs for building larger applications. It offers a modern, safe API with several high-level primitives for managing asynchronous execution and synchronization.
Handle thread switching and thread_local with co_await
mainIn
libcoro, anyco_awaitoperation has the potential to switch the underlying thread executing the coroutine if the scheduler has more than one thread.Warning: Do not use
thread_localacross aco_awaitboundary. This will lead to bugs due to thread switching and work stealing inlibcoroschedulers.Safe usage: Only use
thread_localsafely if you are using acoro::thread_poolwith exactly 1 thread or an inlineschedulerwith 1 thread.How coro::shared_mutex handles scoped locking
mainThe
coro::shared_mutexprovides asynchronous shared (reader) and exclusive (writer) locking.Important Note on Scoped Locks: Because
coro::shared_mutex.unlock()andunlock_shared()are coroutines, they cannot be called inside a standard RAII destructor. Therefore, you cannot use a standard RAII-style scoped lock. Instead, you must use thescoped_lockorscoped_lock_sharedmethods, which take a lambda (a coroutine) that defines the work to be done while the lock is held. The mutex is automatically released when the provided coroutine completes.// Exclusive lock usage co_await mutex.scoped_lock([](auto& tp) -> coro::task<void> { // Do exclusive work here co_await tp->yield(); }(tp)); // Shared lock usage co_await mutex.scoped_lock_shared([](auto& tp, uint64_t i) -> coro::task<void> { // Do shared work here co_await tp->yield(); }(tp, i));Executors and Schedulers in libcoro
mainlibcoro uses executors to drive coroutine execution and I/O events:
coro::thread_pool: Used for cooperative multitasking of coroutines. Ideal for latency-sensitive or long-lived tasks.coro::scheduler: Used for driving I/O events. It can use inline task processing for thread-per-core or short-lived tasks. Note that using the scheduler requiresLIBCORO_FEATURE_NETWORKINGto be supported.coro::task_group: Used for grouping tasks with dynamic lifetimes.
Avoid use-after-free with lambda captures in coroutines
mainFollowing C++ Core Guidelines (CP.51), it is recommended not to use capturing lambdas that are coroutines. Captures are destroyed at the coroutine's first suspension point, which can lead to use-after-free bugs if accessed after suspension.
Best Practices:
- Pass data into coroutines via function arguments by value to guarantee lifetime.
- If you must use lambda captures, use
coro::invoketo create a stable coroutine frame that holds the captures for the duration of the coroutine.
Coroutine Networking in libcoro
mainlibcoro provides asynchronous networking primitives. Some features require specific dependencies:
- DNS:
coro::net::dns::resolverprovides asynchronous DNS resolution usinglibc-ares. - TCP:
coro::net::tcp::clientandcoro::net::tcp::server. - TLS:
coro::net::tls::clientandcoro::net::tls::server(requires OpenSSL). - UDP:
coro::net::udp::peer.
- DNS:
High-level coroutine primitives in libcoro
mainlibcoro provides the following core coroutine and synchronization primitives:
Execution Control
coro::sync_wait(awaitable): Blocks the current thread until the provided awaitable completes.coro::when_all(awaitable...): Returns an awaitable that completes when all provided awaitables have completed.coro::when_any(awaitable...): Returns an awaitable that completes when any of the provided awaitables completes.coro::invoke(functor, args...): Returns an awaitable that invokes the provided functor with the given arguments.
Coroutine Types
coro::task<T>: A core coroutine task type.coro::generator<T>: A coroutine type used for producing a sequence of values.
Synchronization and Data Structures
coro::event: A synchronization primitive for signaling.coro::latch: A synchronization point for multiple threads/coroutines.coro::mutex/coro::shared_mutex: Mutual exclusion primitives.coro::semaphore: A semaphore for controlling access to a resource.coro::ring_buffer<element, num_elements>: A fixed-size circular buffer.coro::queue: A queue for asynchronous communication.coro::condition_variable: A condition variable for coroutine synchronization.
Avoid blocking coroutines with yield()
mainWhen working within a coroutine, never use
std::this_thread::sleep_for(). Doing so blocks the entire thread, preventing other ready coroutines from executing. Instead, use cooperative yielding to allow other tasks to run:- Use
co_await tp->yield()if you have access to a thread pool. - Use
co_await scheduler->yield()to place the current task at the back of the scheduler's queue. - Use
co_await scheduler->yield_for(duration)to yield for a specific amount of time. - Use
co_await scheduler->yield_until(timepoint)to yield until a specific time point.
// Instead of std::this_thread::sleep_for(ms), // use yield to let other coroutines run if (condition) { co_await tp->yield(); }- Use
Integrate libcoro into an Android CMake project
mainTo use libcoro in an Android native application, add it as a subdirectory in your CMake configuration and link against the
libcorotarget. If you require networking or TLS features, you must explicitly define the corresponding macros. For TLS, ensure you provide OpenSSL for the target ABI and configureOPENSSL_ROOT_DIRandOPENSSL_USE_STATIC_LIBS.# Add libcoro as a subdirectory in your native CMake add_subdirectory(${CMAKE_SOURCE_DIR}/path/to/libcoro libcoro_build) # Link to your library target target_link_libraries(your-lib PRIVATE libcoro log) # Enable networking and TLS features target_compile_definitions(your-lib PRIVATE LIBCORO_FEATURE_NETWORKING LIBCORO_FEATURE_TLS)Configure Android test execution via properties file
mainWhen running the libcoro test app on an Android emulator, you can pass test filters and timeouts by placing a properties file at
/data/local/tmp/coro_test_config.propertiesor within the app sandbox atfiles/coro_test_config.properties.filter=~[benchmark] ~[bench] ~[semaphore] ~[scheduler] timeout=600Build the Android test APK locally
mainTo build the Android test harness for validating coroutine primitives and networking/TLS on Android devices or emulators, follow these steps.
Prerequisites:
- Android SDK + NDK r29
- CMake 3.22.1
- JDK 17
Steps:
- Clone the repository with submodules:
git clone --recurse-submodules <libcoro-url> - Navigate to the Android directory:
cd test/android - (Optional) Build OpenSSL for required ABIs:
bash scripts/build_openssl.sh --abis arm64-v8a,armeabi-v7a,x86_64,x86 --api 24 - Build a single-ABI debug version:
gradle assembleDebug -PciAbi=x86_64 -PcustomBuildDir=build-x86_64 - Build a multi-ABI fat APK:
gradle assembleDebug
cd test/android # Optionally build OpenSSL for required ABIs bash scripts/build_openssl.sh --abis arm64-v8a,armeabi-v7a,x86_64,x86 --api 24 # Single-ABI debug build gradle assembleDebug -PciAbi=x86_64 -PcustomBuildDir=build-x86_64 # Multi-ABI build gradle assembleDebugConfigure libcoro build options
mainWhen building libcoro from source using CMake, you can use the following options to customize the build:
| Name | Default | Description | |:---|:---|:---| | LIBCORO_EXTERNAL_DEPENDENCIES | OFF | Use CMake find_package to resolve dependencies instead of embedded libraries. | | LIBCORO_BUILD_TESTS | ON | Should the tests be built? Note this is only default ON if libcoro is the root CMakeLists.txt | | LIBCORO_CODE_COVERAGE | OFF | Should code coverage be enabled? Requires tests to be enabled. | | LIBCORO_BUILD_EXAMPLES | ON | Should the examples be built? This is only default ON if libcoro is the root CMakeLists.txt | | LIBCORO_FEATURE_NETWORKING | ON | Include networking features. MSVC not currently supported | | LIBCORO_FEATURE_TLS | ON | Include TLS features. Requires networking to be enabled. MSVC not currently supported. |