Crouton is a C++20 coroutine runtime library that provides some general purpose concurrency utilities, as well as cross-platform event loops, I/O and networking that work the same way on Mac, Linux, Windows and ESP32 microcontrollers.
The cross-platform support is based on the widely-used libuv, mbedTLS and llhttp libraries. (On Apple platforms it can also use the system Network.framework. On ESP32, where libuv is not supported, it uses lwip and FreeRTOS APIs instead.)
A Coroutine is a function that can return partway through, and then later be resumed where it left off. Knuth wrote about them in the 1960s and they remained a curiosity for a long time, but they've since become widely used under the hood of the "async / await" concurrency model used in languages like JavaScript, C#, Rust, Nim, and Swift. Crouton brings this to C++.
Async/await gives you concurrency without the pitfalls of multithreading. You can write code in a linear fashion, "blocking" on slow operations like I/O (see the example below), but the thread doesn't really block: instead your (coroutine) function is suspended and some other function that's finished blocking can resume. When the I/O completes, your function is resumed transparently at the next opportunity.
How is that better than threads? It's safer and easier to reason about. The only places where concurrency happens are well-marked by the co_await
and co_yield
keywords. You don't need mutexes or atomic variables, and there are far fewer opportunities for race conditions or deadlocks. (There are performance benefits too: no expensive context switches, less stack usage.)
Detailed documentation is being written.
-
Coroutine library:
- Useful base classes for implementing new coroutine types
Future
, an asynchronous promise typeGenerator
, an iterator implementation based onco_yield
-ing valuesCoMutex
,CoCondition
,Blocker
: cooperative equivalents of common sync primitivesAsyncQueue
, a producer/consumer queueSelect
, a way to await multiple things in parallel- Optional coroutine lifecycle tracking, with debugging utilities to dump all existing coroutines and their "call stacks".
-
Event loops:
Scheduler
, which manages multiple active coroutines on a thread- Round-robin scheduling of multiple active coroutines
- Suspending a coroutine, then waking it when it's ready
- Scheduling a function to run on the next event-loop iteration, even on a different thread
- Scheduling a function to run on a background thread-pool
Task
, an independently-running coroutine that canco_yield
to give time to othersTimer
, repeating or one-shot
-
Reactive publish/subscribe framework
- Enables building complex networked data flows out of modular components
- Intrinsically supports backpressure to manage flow control on sockets
- Loosely inspired by Apple's Combine framework
- Double-plus experimental ⚗️
-
Asynchronous I/O classes:
- DNS lookup
- File I/O
- Filesystem APIs like
mkdir
andstat
- Abstract asynchronous stream interface
- In-process pipes
- TCP sockets, with or without TLS
- A TCP listener (no TLS support yet)
- URL parser
- HTTP client
- HTTP server (very basic so far)
- WebSocket client and server
- BLIP RPC protocol (optional; under separate license.)
-
Core classes & APIs:
- General-purpose
Error
andResult<T>
types - Logging, a very compact library with an API inspired by spdlog.
- Type-safe string formatting, similar to
std::format
but with a much lower code footprint.
- General-purpose
-
Cross-Platform:
- macOS (builds and passes tests)
- iOS? ("It's still Darwin…")
- Linux (builds and passes test)
- Android? ("It's still Linux…")
- ESP32 embedded CPUs (builds and passes tests. Networking works, but filesystem APIs aren't implemented yet.)
- Windows (builds, but I don't have any Windows machines to test on. Help wanted!)
- macOS (builds and passes tests)
// Simple HTTP client request:
HTTPConnection client("https://example.com");
HTTPRequest req;
HTTPResponse resp = co_await req.sendRequest(req);
cout << int(resp.status()) << " " << resp.statusMessage() << endl;
for (auto &header : resp.headers())
cout << header.first << " = " << header.second << endl;
ConstBuf body;
do {
body = co_await resp.readNoCopy();
cout << string_view(body);
} while (body.len > 0);
cout << endl;
See also demo_server.cc, a simple HTTP and WebSocket server.
An example embedded app is at tests/ESP32.
This is new code, under heavy development! So far, it builds with Clang (Xcode 15) on macOS, GCC 12 on Ubuntu, Visual Studio 17 2022 on Windows, and ESP-IDF 5.1.
The tests run regularly on macOS, and occasionally on Ubuntu (though not in CI.) Test coverage is very limited.
APIs are still in flux. Things get refactored a lot.
Important: Make sure you checked out the submodules!
git submodule update --init --recursive
- CMake
- Clang 15, Xcode 15, or GCC 12
- zlib (aka libz)
- Install Xcode 15 or later, or at least the command-line tools.
- Install CMake; this is most easily done with HomeBrew, by running
brew install cmake
sudo apt-get install g++ cmake cmake-data zlib1g-dev
make
make test
The library is libCrouton
, in either the build_cmake/debug/
or build_cmake/release/
directory.
Before first building with Xcode, you must use CMake to build libuv and mbedTLS:
make xcode_deps
You only need to do this on initial setup, or after those submodules are updated.
Then:
- open crouton.xcodeproj
- Select the
Tests
scheme and Run. - To locate the binaries, choose Product > Show Build Folder In Finder
The ESP-IDF component is at src/io/esp32
; please see the README for details. A demo app is at tests/ESP32.
- Crouton itself is licensed under the Apache 2 license.
- The files in the subdirectory
src/io/blip
, however, are under the Business Software License since they are adapted from existing BSL-licensed code. These source files are optional and are by default not compiled into the Crouton library. See their README for details and the full license. - The source file
src/io/URL.cc
contains some code adapted from tlsuv, which is Apache 2 licensed.
- The files in the subdirectory
- Of the third party code in
vendor/
:- Catch2 is licensed under the Boost Software License (but is only linked into the tests)
- libuv is licensed under the MIT license.
- llhttp is licensed under the MIT license.
- mbedtls is licensed under the Apache 2 license.
- spdlog is licensed under the MIT license.
- Crouton code by Jens Alfke (@snej)
- Initial inspiration, coroutine knowledge, starting code: Simon Tatham's brilliant tutorial. Lewis Baker's blog posts have also been helpful.
- Event loops, I/O, networking: libuv
- TLS engine: mbedTLS
- HTTP parser: llhttp
- Logging: spdlog
- Unit tests: Catch2
- Extra inspiration and URL parser: tlsuv