Address review comments and fix serious bug

This commit is contained in:
Markus Pilman 2023-11-17 11:08:34 +01:00
parent f38ae571b0
commit fd4a300d4c
4 changed files with 29 additions and 28 deletions

View File

@ -1,6 +1,7 @@
# Coroutines in Flow
* [Introduction](#Introduction)
* [Coroutines vs ACTORs](#coroutines-vs-actors)
* [Basic Types](#basic-types)
* [Choose-When](#choose-when)
* [Execution in when-expressions](#execution-in-when-expressions)
@ -38,6 +39,13 @@ Future<double> simpleCoroutine() {
This document assumes some familiarity with Flow. As of today, actors and coroutines
can be freely mixed, but new code should be written using coroutines.
## Coroutines vs ACTORs
It is important to understand that C++ coroutine support doesn't change anything in Flow: they are not a replacement
of Flow but they replace the actor compiler with a C++ compiler. This means, that the network loop, all Flow types,
the RPC layer, and the simulator all remain unchanged. A coroutine simply returns a special `SAV<T>` which has handle
to a coroutine.
## Basic Types
As defined in the C++20 standard, a function is a coroutine if its body contains at least one `co_await`, `co_yield`,
@ -624,8 +632,8 @@ struct Foo : IFoo {
};
```
This boilerplate is necessary, because `ACTOR`s can't be class members since the actual code is moved into a different
struct which.
This boilerplate is necessary, because `ACTOR`s can't be class members: the actor compiler will generate another
`struct` and move the code there -- so `this` will point to the actor state and not to the class instance.
With C++ coroutines, this limitation goes away. So a cleaner (and slightly more efficient) implementation of the above
is:
@ -685,4 +693,8 @@ Future<Void> someActor() {
If the struct `SomeStruct` would initialize its primitive members explicitly (for example by using `int a = 0;` and
`bool b = false`) this would be a non-issue. And explicit initialization is probably the right fix here. Sadly, it
doesn't seem like UBSAN finds these kind of subtle bugs.
doesn't seem like UBSAN finds these kind of subtle bugs.
Another difference is, that if a `state` variables might be initialized twice: once at the creation of the actor using
the default constructor and a second time at the point where the variable is initialized in the code. With C++
coroutines we now get the expected behavior, which is better, but nonetheless a potential behavior change.

View File

@ -31,6 +31,9 @@
#include <memory>
#include <iostream>
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
NetworkAddress serverAddress;
enum TutorialWellKnownEndpoints {
@ -44,12 +47,6 @@ enum TutorialWellKnownEndpoints {
Future<Void> simpleTimer() {
// we need to remember the time when we first
// started.
// This needs to be a state-variable because
// we will use it in different parts of the
// actor. If you don't understand how state
// variables work, it is a good idea to remove
// the state keyword here and look at the
// generated C++ code from the actor compiler.
double start_time = g_network->now();
loop {
co_await delay(1.0);
@ -432,12 +429,8 @@ Future<Void> fdbClientStream() {
Key next;
int64_t bytes = 0;
Future<Void> logFuture = logThroughput(&bytes, &next);
Future<Void> onError;
loop {
if (onError.isValid()) {
co_await onError;
onError = Future<Void>();
}
Future<Void> onError;
PromiseStream<Standalone<RangeResultRef>> results;
try {
Future<Void> stream = tx.getRangeStream(results,
@ -457,6 +450,7 @@ Future<Void> fdbClientStream() {
}
onError = tx.onError(e);
}
co_await onError;
}
}
@ -470,13 +464,9 @@ bool transaction_done(void) {
template <class DB, class Fun>
Future<Void> runTransactionWhile(DB const& db, Fun f) {
Future<Void> onError;
Transaction tr(db);
loop {
if (onError.isValid()) {
co_await onError;
onError = Future<Void>();
}
Future<Void> onError;
try {
if (transactionDone(co_await f(&tr))) {
co_return;
@ -484,6 +474,7 @@ Future<Void> runTransactionWhile(DB const& db, Fun f) {
} catch (Error& e) {
onError = tr.onError(e);
}
co_await onError;
}
}
@ -603,6 +594,7 @@ AsyncGenerator<Optional<StringRef>> readLines(Reference<IAsyncFile> file) {
co_yield lastLine;
lastLine = {};
arena = Arena();
co_return;
}
}
StringRef block = optionalBlock.get();
@ -625,13 +617,10 @@ AsyncGenerator<Optional<StringRef>> readLines(Reference<IAsyncFile> file) {
}
}
}
if (!lastLine.empty()) {
co_yield lastLine;
}
}
Future<Void> testReadLines() {
auto path = "/etc/hosts"s;
auto file = co_await IAsyncFileSystem::filesystem()->open(
"/Users/mpilman/Projects/frostdb/flow/include/flow/flow.h", IAsyncFile::OPEN_READWRITE, 0640);
auto lines = readLines(file);

View File

@ -1351,6 +1351,8 @@ Future<Void> actor_throw_test(std::stringstream& ss) {
LifetimeLogger ll(ss, 0);
co_await delay(0);
throw io_error();
ss << "after throw. ";

View File

@ -163,12 +163,10 @@ template <class F>
struct AwaitableResume<F, Void, false> {
[[maybe_unused]] void await_resume() {
auto self = static_cast<F*>(this);
if (self->resumeImpl()) {
if (self->future.isError()) {
throw self->future.getError();
}
self->resumeImpl();
if (self->future.isError()) {
throw self->future.getError();
}
return;
}
};