Skip to content

Commit 303f8be

Browse files
committed
Update readme with new examples and contributing
Closes #182
1 parent 93d9be5 commit 303f8be

File tree

9 files changed

+301
-42
lines changed

9 files changed

+301
-42
lines changed

.githooks/pre-commit

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,12 @@ template_contents=$(cat 'README.md')
7878
example_contents=$(cat 'examples/coro_shared_mutex.cpp')
7979
echo "${template_contents/\$\{EXAMPLE_CORO_SHARED_MUTEX_CPP\}/$example_contents}" > README.md
8080

81+
template_contents=$(cat 'README.md')
82+
example_contents=$(cat 'examples/coro_sync_wait.cpp')
83+
echo "${template_contents/\$\{EXAMPLE_CORO_SYNC_WAIT\}/$example_contents}" > README.md
84+
85+
template_contents=$(cat 'README.md')
86+
example_contents=$(cat 'examples/coro_when_all.cpp')
87+
echo "${template_contents/\$\{EXAMPLE_CORO_WHEN_ALL\}/$example_contents}" > README.md
88+
8189
git add README.md

.githooks/readme-template.md

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* C++20 coroutines!
1515
* Modern Safe C++20 API
1616
* Higher level coroutine constructs
17+
- [coro::sync_wait(awaitable)](#sync_wait)
18+
- [coro::when_all(awaitable...) -> awaitable](#when_all)
1719
- [coro::task<T>](#task)
1820
- [coro::generator<T>](#generator)
1921
- [coro::event](#event)
@@ -22,37 +24,67 @@
2224
- [coro::shared_mutex](#shared_mutex)
2325
- [coro::semaphore](#semaphore)
2426
- [coro::ring_buffer<element, num_elements>](#ring_buffer)
25-
- coro::sync_wait(awaitable)
26-
- coro::when_all(awaitable...) -> awaitable
2727
* Schedulers
2828
- [coro::thread_pool](#thread_pool) for coroutine cooperative multitasking
2929
- [coro::io_scheduler](#io_scheduler) for driving i/o events
30-
- Can use coro::thread_pool for latency sensitive or long lived tasks.
30+
- Can use `coro::thread_pool` for latency sensitive or long lived tasks.
3131
- Can use inline task processing for thread per core or short lived tasks.
32-
- Currently uses an epoll driver
32+
- Currently uses an epoll driver, only supported on linux.
3333
- [coro::task_container](#task_container) for dynamic task lifetimes
3434
* Coroutine Networking
3535
- coro::net::dns::resolver for async dns
3636
- Uses libc-ares
3737
- [coro::net::tcp::client](#io_scheduler)
3838
- [coro::net::tcp::server](#io_scheduler)
39+
* [Example TCP/HTTP Echo Server](#tcp_echo_server)
3940
- coro::net::tls::client (OpenSSL)
4041
- coro::net::tls::server (OpenSSL)
4142
- coro::net::udp::peer
42-
* [Example TCP/HTTP Echo Server](#tcp_echo_server)
43-
43+
*
4444
* [Requirements](#requirements)
4545
* [Build Instructions](#build-instructions)
46-
* [Testing](#tests)
46+
* [Contributing](#contributing)
4747
* [Support](#support)
4848

4949
## Usage
5050

51-
### A note on co_await
52-
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the potential to switch the thread that is executing the currently running coroutine. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on thread pools.
51+
### A note on co_await and threads
52+
Its important to note with coroutines that _any_ `co_await` has the potential to switch the underyling thread that is executing the currently executing coroutine if the scheduler used has more than 1 thread. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on libcoro's schedulers. The only way this is safe is by using a `coro::thread_pool` with 1 thread or an inline `io_scheduler` which also only has 1 thread.
53+
54+
### sync_wait
55+
The `sync_wait` construct is meant to be used outside of a coroutine context to block the calling thread until the coroutine has completed. The coroutine can be executed on the calling thread or scheduled on one of libcoro's schedulers.
56+
57+
```C++
58+
${EXAMPLE_CORO_SYNC_WAIT}
59+
```
60+
61+
Expected output:
62+
```bash
63+
$ ./examples/coro_sync_wait
64+
Inline Result = 10
65+
Offload Result = 20
66+
```
67+
68+
### when_all
69+
The `when_all` construct can be used within coroutines to await a set of tasks, or it can be used outside coroutinne context in conjunction with `sync_wait` to await multiple tasks. Each task passed into `when_all` will initially be executed serially by the calling thread so it is recommended to offload the tasks onto a scheduler like `coro::thread_pool` or `coro::io_scheduler` so they can execute in parallel.
70+
71+
```C++
72+
${EXAMPLE_CORO_WHEN_ALL}
73+
```
74+
75+
Expected output:
76+
```bash
77+
$ ./examples/coro_when_all
78+
2
79+
4
80+
6
81+
8
82+
10
83+
first: 1.21 second: 20
84+
```
5385

5486
### task
55-
The `coro::task<T>` is the main coroutine building block within `libcoro`. Use task to create your coroutines and `co_await` or `co_yield` tasks within tasks to perform asynchronous operations, lazily evaluation or even spreading work out across a `coro::thread_pool`. Tasks are lightweight and only begin execution upon awaiting them. If their return type is not `void` then the value can be returned by const reference or by moving (r-value reference).
87+
The `coro::task<T>` is the main coroutine building block within `libcoro`. Use task to create your coroutines and `co_await` or `co_yield` tasks within tasks to perform asynchronous operations, lazily evaluation or even spreading work out across a `coro::thread_pool`. Tasks are lightweight and only begin execution upon awaiting them.
5688

5789

5890
```C++
@@ -474,21 +506,27 @@ target_link_libraries(${PROJECT_NAME} PUBLIC libcoro)
474506
##### Package managers
475507
libcoro is available via package managers [Conan](https://conan.io/center/libcoro) and [vcpkg](https://vcpkg.io/).
476508

509+
### Contributing
510+
511+
Contributing is welcome, if you have ideas or bugs please open an issue. If you want to open a PR they are also welcome, if you are adding a bugfix or a feature please include tests to verify the feature or bugfix is working properly. If it isn't included I will be asking for you to add some!
512+
477513
#### Tests
478-
The tests will automatically be run by github actions on creating a pull request. They can also be ran locally:
514+
The tests will automatically be run by github actions on creating a pull request. They can also be ran locally after building from the build directory:
479515
480516
# Invoke via cmake with all output from the tests displayed to console:
481517
ctest -VV
482518
483519
# Or invoke directly, can pass the name of tests to execute, the framework used is catch2.
484-
# Tests are tagged with their group, below is howt o run all of the coro::net::tcp::server tests:
485-
./Debug/test/libcoro_test "[tcp_server]"
520+
# Tests are tagged with their group, below is how to run all of the coro::net::tcp::server tests:
521+
./test/libcoro_test "[tcp_server]"
522+
523+
If you open a PR for a bugfix or new feature please include tests to verify that the change is working as intended. If your PR doesn't include tests I will ask you to add them and won't merge until they are added and working properly. Tests are found in the `/test` directory and are organized by object type.
486524
487525
### Support
488526
489527
File bug reports, feature requests and questions using [GitHub libcoro Issues](https://github.com/jbaldwin/libcoro/issues)
490528
491-
Copyright © 2020-2023 Josh Baldwin
529+
Copyright © 2020-2024 Josh Baldwin
492530
493531
[badge.language]: https://img.shields.io/badge/language-C%2B%2B20-yellow.svg
494532
[badge.license]: https://img.shields.io/badge/license-Apache--2.0-blue

README.md

Lines changed: 139 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* C++20 coroutines!
1515
* Modern Safe C++20 API
1616
* Higher level coroutine constructs
17+
- [coro::sync_wait(awaitable)](#sync_wait)
18+
- [coro::when_all(awaitable...) -> awaitable](#when_all)
1719
- [coro::task<T>](#task)
1820
- [coro::generator<T>](#generator)
1921
- [coro::event](#event)
@@ -22,37 +24,148 @@
2224
- [coro::shared_mutex](#shared_mutex)
2325
- [coro::semaphore](#semaphore)
2426
- [coro::ring_buffer<element, num_elements>](#ring_buffer)
25-
- coro::sync_wait(awaitable)
26-
- coro::when_all(awaitable...) -> awaitable
2727
* Schedulers
2828
- [coro::thread_pool](#thread_pool) for coroutine cooperative multitasking
2929
- [coro::io_scheduler](#io_scheduler) for driving i/o events
30-
- Can use coro::thread_pool for latency sensitive or long lived tasks.
30+
- Can use `coro::thread_pool` for latency sensitive or long lived tasks.
3131
- Can use inline task processing for thread per core or short lived tasks.
32-
- Currently uses an epoll driver
32+
- Currently uses an epoll driver, only supported on linux.
3333
- [coro::task_container](#task_container) for dynamic task lifetimes
3434
* Coroutine Networking
3535
- coro::net::dns::resolver for async dns
3636
- Uses libc-ares
3737
- [coro::net::tcp::client](#io_scheduler)
3838
- [coro::net::tcp::server](#io_scheduler)
39+
* [Example TCP/HTTP Echo Server](#tcp_echo_server)
3940
- coro::net::tls::client (OpenSSL)
4041
- coro::net::tls::server (OpenSSL)
4142
- coro::net::udp::peer
42-
* [Example TCP/HTTP Echo Server](#tcp_echo_server)
43-
43+
*
4444
* [Requirements](#requirements)
4545
* [Build Instructions](#build-instructions)
46-
* [Testing](#tests)
46+
* [Contributing](#contributing)
4747
* [Support](#support)
4848

4949
## Usage
5050

51-
### A note on co_await
52-
Its important to note with coroutines that depending on the construct used _any_ `co_await` has the potential to switch the thread that is executing the currently running coroutine. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on thread pools.
51+
### A note on co_await and threads
52+
Its important to note with coroutines that _any_ `co_await` has the potential to switch the underyling thread that is executing the currently executing coroutine if the scheduler used has more than 1 thread. In general this shouldn't affect the way any user of the library would write code except for `thread_local`. Usage of `thread_local` should be extremely careful and _never_ used across any `co_await` boundary do to thread switching and work stealing on libcoro's schedulers. The only way this is safe is by using a `coro::thread_pool` with 1 thread or an inline `io_scheduler` which also only has 1 thread.
53+
54+
### sync_wait
55+
The `sync_wait` construct is meant to be used outside of a coroutine context to block the calling thread until the coroutine has completed. The coroutine can be executed on the calling thread or scheduled on one of libcoro's schedulers.
56+
57+
```C++
58+
#include <coro/coro.hpp>
59+
#include <iostream>
60+
61+
int main()
62+
{
63+
// This lambda will create a coro::task that returns a unit64_t.
64+
// It can be invoked many times with different arguments.
65+
auto make_task_inline = [](uint64_t x) -> coro::task<uint64_t> { co_return x + x; };
66+
67+
// This will block the calling thread until the created task completes.
68+
// Since this task isn't scheduled on any coro::thread_pool or coro::io_scheduler
69+
// it will execute directly on the calling thread.
70+
auto result = coro::sync_wait(make_task_inline(5));
71+
std::cout << "Inline Result = " << result << "\n";
72+
73+
// We'll make a 1 thread coro::thread_pool to demonstrate offloading the task's
74+
// execution to another thread. We'll capture the thread pool in the lambda,
75+
// note that you will need to guarantee the thread pool outlives the coroutine.
76+
coro::thread_pool tp{coro::thread_pool::options{.thread_count = 1}};
77+
78+
auto make_task_offload = [&tp](uint64_t x) -> coro::task<uint64_t>
79+
{
80+
co_await tp.schedule(); // Schedules execution on the thread pool.
81+
co_return x + x; // This will execute on the thread pool.
82+
};
83+
84+
// This will still block the calling thread, but it will now offload to the
85+
// coro::thread_pool since the coroutine task is immediately scheduled.
86+
result = coro::sync_wait(make_task_offload(10));
87+
std::cout << "Offload Result = " << result << "\n";
88+
}
89+
```
90+
91+
Expected output:
92+
```bash
93+
$ ./examples/coro_sync_wait
94+
Inline Result = 10
95+
Offload Result = 20
96+
```
97+
98+
### when_all
99+
The `when_all` construct can be used within coroutines to await a set of tasks, or it can be used outside coroutinne context in conjunction with `sync_wait` to await multiple tasks. Each task passed into `when_all` will initially be executed serially by the calling thread so it is recommended to offload the tasks onto a scheduler like `coro::thread_pool` or `coro::io_scheduler` so they can execute in parallel.
100+
101+
```C++
102+
#include <coro/coro.hpp>
103+
#include <iostream>
104+
105+
int main()
106+
{
107+
// Create a thread pool to execute all the tasks in parallel.
108+
coro::thread_pool tp{coro::thread_pool::options{.thread_count = 4}};
109+
// Create the task we want to invoke multiple times and execute in parallel on the thread pool.
110+
auto twice = [&](uint64_t x) -> coro::task<uint64_t>
111+
{
112+
co_await tp.schedule(); // Schedule onto the thread pool.
113+
co_return x + x; // Executed on the thread pool.
114+
};
115+
116+
// Make our tasks to execute, tasks can be passed in via a std::ranges::range type or var args.
117+
std::vector<coro::task<uint64_t>> tasks{};
118+
for (std::size_t i = 0; i < 5; ++i)
119+
{
120+
tasks.emplace_back(twice(i + 1));
121+
}
122+
123+
// Synchronously wait on this thread for the thread pool to finish executing all the tasks in parallel.
124+
auto results = coro::sync_wait(coro::when_all(std::move(tasks)));
125+
for (auto& result : results)
126+
{
127+
// If your task can throw calling return_value() will either return the result or re-throw the exception.
128+
try
129+
{
130+
std::cout << result.return_value() << "\n";
131+
}
132+
catch (const std::exception& e)
133+
{
134+
std::cerr << e.what() << '\n';
135+
}
136+
}
137+
138+
// Use var args instead of a container as input to coro::when_all.
139+
auto square = [&](double x) -> coro::task<double>
140+
{
141+
co_await tp.schedule();
142+
co_return x* x;
143+
};
144+
145+
// Var args allows you to pass in tasks with different return types and returns
146+
// the result as a std::tuple.
147+
auto tuple_results = coro::sync_wait(coro::when_all(square(1.1), twice(10)));
148+
149+
auto first = std::get<0>(tuple_results).return_value();
150+
auto second = std::get<1>(tuple_results).return_value();
151+
152+
std::cout << "first: " << first << " second: " << second << "\n";
153+
}
154+
```
155+
156+
Expected output:
157+
```bash
158+
$ ./examples/coro_when_all
159+
2
160+
4
161+
6
162+
8
163+
10
164+
first: 1.21 second: 20
165+
```
53166

54167
### task
55-
The `coro::task<T>` is the main coroutine building block within `libcoro`. Use task to create your coroutines and `co_await` or `co_yield` tasks within tasks to perform asynchronous operations, lazily evaluation or even spreading work out across a `coro::thread_pool`. Tasks are lightweight and only begin execution upon awaiting them. If their return type is not `void` then the value can be returned by const reference or by moving (r-value reference).
168+
The `coro::task<T>` is the main coroutine building block within `libcoro`. Use task to create your coroutines and `co_await` or `co_yield` tasks within tasks to perform asynchronous operations, lazily evaluation or even spreading work out across a `coro::thread_pool`. Tasks are lightweight and only begin execution upon awaiting them.
56169

57170

58171
```C++
@@ -62,11 +175,12 @@ The `coro::task<T>` is the main coroutine building block within `libcoro`. Use
62175
int main()
63176
{
64177
// Task that takes a value and doubles it.
65-
auto double_task = [](uint64_t x) -> coro::task<uint64_t> { co_return x* x; };
178+
auto double_task = [](uint64_t x) -> coro::task<uint64_t> { co_return x * 2; };
66179

67180
// Create a task that awaits the doubling of its given value and
68181
// then returns the result after adding 5.
69-
auto double_and_add_5_task = [&](uint64_t input) -> coro::task<uint64_t> {
182+
auto double_and_add_5_task = [&](uint64_t input) -> coro::task<uint64_t>
183+
{
70184
auto doubled = co_await double_task(input);
71185
co_return doubled + 5;
72186
};
@@ -86,7 +200,7 @@ int main()
86200
// While the default move constructors will work for this struct the example
87201
// inserts explicit print statements to show the task is moving the value
88202
// out correctly.
89-
expensive_struct(const expensive_struct&) = delete;
203+
expensive_struct(const expensive_struct&) = delete;
90204
auto operator=(const expensive_struct&) -> expensive_struct& = delete;
91205

92206
expensive_struct(expensive_struct&& other) : id(std::move(other.id)), records(std::move(other.records))
@@ -107,7 +221,8 @@ int main()
107221

108222
// Create a very large object and return it by moving the value so the
109223
// contents do not have to be copied out.
110-
auto move_output_task = []() -> coro::task<expensive_struct> {
224+
auto move_output_task = []() -> coro::task<expensive_struct>
225+
{
111226
expensive_struct data{};
112227
data.id = "12345678-1234-5678-9012-123456781234";
113228
for (size_t i = 10'000; i < 100'000; ++i)
@@ -1177,21 +1292,27 @@ target_link_libraries(${PROJECT_NAME} PUBLIC libcoro)
11771292
##### Package managers
11781293
libcoro is available via package managers [Conan](https://conan.io/center/libcoro) and [vcpkg](https://vcpkg.io/).
11791294

1295+
### Contributing
1296+
1297+
Contributing is welcome, if you have ideas or bugs please open an issue. If you want to open a PR they are also welcome, if you are adding a bugfix or a feature please include tests to verify the feature or bugfix is working properly. If it isn't included I will be asking for you to add some!
1298+
11801299
#### Tests
1181-
The tests will automatically be run by github actions on creating a pull request. They can also be ran locally:
1300+
The tests will automatically be run by github actions on creating a pull request. They can also be ran locally after building from the build directory:
11821301
11831302
# Invoke via cmake with all output from the tests displayed to console:
11841303
ctest -VV
11851304
11861305
# Or invoke directly, can pass the name of tests to execute, the framework used is catch2.
1187-
# Tests are tagged with their group, below is howt o run all of the coro::net::tcp::server tests:
1188-
./Debug/test/libcoro_test "[tcp_server]"
1306+
# Tests are tagged with their group, below is how to run all of the coro::net::tcp::server tests:
1307+
./test/libcoro_test "[tcp_server]"
1308+
1309+
If you open a PR for a bugfix or new feature please include tests to verify that the change is working as intended. If your PR doesn't include tests I will ask you to add them and won't merge until they are added and working properly. Tests are found in the `/test` directory and are organized by object type.
11891310
11901311
### Support
11911312
11921313
File bug reports, feature requests and questions using [GitHub libcoro Issues](https://github.com/jbaldwin/libcoro/issues)
11931314
1194-
Copyright © 2020-2023 Josh Baldwin
1315+
Copyright © 2020-2024 Josh Baldwin
11951316
11961317
[badge.language]: https://img.shields.io/badge/language-C%2B%2B20-yellow.svg
11971318
[badge.license]: https://img.shields.io/badge/license-Apache--2.0-blue

examples/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ add_executable(coro_shared_mutex coro_shared_mutex.cpp)
4949
target_link_libraries(coro_shared_mutex PUBLIC libcoro)
5050
target_compile_options(coro_shared_mutex PUBLIC ${LIBCORO_EXAMPLE_OPTIONS})
5151

52+
add_executable(coro_sync_wait coro_sync_wait.cpp)
53+
target_link_libraries(coro_sync_wait PUBLIC libcoro)
54+
target_compile_options(coro_sync_wait PUBLIC ${LIBCORO_EXAMPLE_OPTIONS})
55+
56+
add_executable(coro_when_all coro_when_all.cpp)
57+
target_link_libraries(coro_when_all PUBLIC libcoro)
58+
target_compile_options(coro_when_all PUBLIC ${LIBCORO_EXAMPLE_OPTIONS})
59+
5260
if(LIBCORO_FEATURE_PLATFORM AND LIBCORO_FEATURE_NETWORKING)
5361
add_executable(coro_task_container coro_task_container.cpp)
5462
target_link_libraries(coro_task_container PUBLIC libcoro)

examples/coro_http_200_ok_server.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ Connection: keep-alive
4646
{
4747
case coro::poll_status::event:
4848
{
49-
// Coroutines in the scheduler are cleaned up when another once replaces them, but sockets need
50-
// to be manually "closed" otherwise we could run out of file descriptors if the ulimit isn't high
51-
// enough. Calling garbage collect here will destroy completed coroutine frames.
52-
scheduler->garbage_collect();
53-
5449
auto client = server.accept();
5550
if (client.socket().is_valid())
5651
{

0 commit comments

Comments
 (0)