blob: f9887d57f48f9dbaa81c6b4db18785fdcc603b44 [file] [log] [blame] [view]
# Coroutine library
## Description
This coroutine library can help you write highly asynchronous code, and avoid
"callback hell". It does so by allowing a function (or method) to be paused and
resumed by swapping [execution contexts](context/context.h). It makes the code
*look* synchronous, while remaining asynchronous under the hood, with all the
usual concurrency pitfalls, such as race conditions and deadlocks.
Coroutines are different than multithreading, but both can be used together.
See [CoroutineHandler](coroutine.h) for details.
## Usage
Here are some tips on good use of the Coroutine library.
### Use CoroutineManager in classes
Usually, coroutines created within a class object should not survive its
destruction, whether because continuing the processing didn't make sense, or
because resources captured by the coroutine would be destroyed (such as `this`).
[CoroutineManager](coroutine_manager.h) is a proxy class for
[CoroutineService](coroutine.h). `CoroutineManager` interrupts the coroutine it
created when destroyed, and can be created using the `CoroutineService` vended
by an `Environment` object.
You should consider using `CoroutineManager` if you use coroutines in your
class.
Free-standing functions probably don't need `CoroutineManager` and can use
`CoroutineService` directly.
### When receiving INTERRUPTED, return
`coroutine::ContinuationStatus::INTERRUPTED` means another part of your code
requested the coroutine to terminate gracefully. This would be the case if the
`CoroutineManager` or `CoroutineService` who created this coroutine are
destroyed.
This mechanism is needed because other parts of the program dont know the heap
allocations made inside the coroutine, as well as other cleanup performed by
the destructors of objects created or owned by the coroutine. When a coroutine
destruction is needed, it is resumed with an `ContinuationStatus::INTERRUPTED`
and it is the coroutine's job to unwind its call stack.
Usually, the only thing you need to do when receiving a
`ContinuationStatus::INTERRUPTED` is to return immediately. Doing more work is
dangerous as some objects you rely on may be destroyed already.
### Don’t use Yield and Resume
You probably don’t need to use `Yield()` and `Resume()` directly.
[SyncCall](coroutine.h) is a utility function that can be used to wrap any
asynchronous call, so that you don't have to use `CoroutineHandler` methods
directly.
If you have an asynchronous function with the signature
`AsynchronousCall(Argument, fit::function<void(Status, Result)>)`, then you can
wrap it such as:
``` cpp
Argument argument(...)
Status status;
Result value;
if (coroutine::SyncCall(handler,
[argument](fit::function<void(Status, Result)> cb) {
AsynchronousCall(argument, std::move(cb));
}, &status, &value) == coroutine::ContinuationStatus::INTERRUPTED) {
return Status::INTERRUPTED;
}
if (status != Status::OK)
return status;
Process(value);
```
`SyncCall` will ensure the asynchronous call is made and the coroutine paused,
and then resumed when the asynchronous callback is executed.
### Use coroutine::Wait with for loops
Coroutines make it very easy to write asynchronous code, but the execution of
the coroutine itself remains sequential. In particular, `for` loops are not run
in parallel. If you can, avoid the following pattern:
``` cpp
std::vector<Result> results;
for (auto& obj : objects_) {
Result result;
// Don't do that! SynchronousFrobinate does not need to wait for the previous
// call to finish.
if (SynchronousFrobinate(handler, obj, &result) ==
coroutine::ContinuationStatus::INTERRUPTED) {
return;
}
results.push_back(std::move(result));
}
```
Instead, make the asynchronous calls directly and use
[coroutine::Waiter](coroutine_waiter.h) to collate the results:
``` cpp
auto waiter = fxl::MakeRefCounted<
callback::Waiter<Status, std::unique_ptr<Result>>>(Status::OK);
for (auto& obj : objects_) {
AsyncFrobinate(obj, waiter->NewCallback());
}
Status status; std::vector<std::unique_ptr<Result>> result;
if (coroutine::Wait(handler, std::move(waiter), &s, &result) ==
coroutine::ContinuationStatus::INTERRUPTED) {
return Status::INTERRUPTED;
}
```