Skip to content

Commit

Permalink
coroutine: add buffer support to async generator
Browse files Browse the repository at this point in the history
before this change, the design of generator (quoting Avi's comment):

> This effectively forces a ping-pong between the generator and its user.
> The generator will have to yield each time it produces a value.

after this change, we have two variants of generator

- one which always suspends itself so its caller can consume the
  produced value.
- one which buffers the yielded values until the buffer is full,
  by then, it suspends itself, and wait for the consumer to grab
  a value from it.

please note, due to llvm/llvm-project#49689,
the new generator does not compile with clang-15 + {debug,sanitize} mode.
but the old version compiles and runs fine with the same combinatino.

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
  • Loading branch information
tchaikov committed Jul 3, 2022
1 parent 707f46b commit a2f6416
Show file tree
Hide file tree
Showing 4 changed files with 561 additions and 93 deletions.
10 changes: 8 additions & 2 deletions doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,17 @@ seastar::future<Preprocessed> prepare_ingredients(Ingredients&&);
seastar::future<Dish> cook_a_dish(Preprocessed&&);
seastar::future<> consume_a_dish(Dish&&);
seastar::coroutine::experimental::generator<Dish> make_dishes(Ingredients&& ingredients) {
seastar::coroutine::experimental::generator<Dish>
make_dishes(coroutine::experimental::buffer_size_t max_dishes_on_table,
Ingredients&& ingredients) {
while (ingredients) {
auto some_ingredients = ingredients.alloc();
auto preprocessed = co_await prepare_ingredients(std::move(some_ingredients));
co_yield co_await cook_a_dish(std::move(preprocessed));
}
}
seastar::future<> have_a_dinner() {
seastar::future<> have_a_dinner(unsigned max_dishes_on_table) {
Ingredients ingredients;
auto dishes = make_dishes(std::move(ingredients));
while (auto dish = co_await dishes()) {
Expand All @@ -412,6 +414,10 @@ seastar::future<> have_a_dinner() {

In this hypothetical kitchen, a chef and a diner are working in parallel. Instead of preparing
all dishes beforehand, the chef cooks the dishes while the diner is consuming them one after another.
Under most circumstances, neither the chef or the diner is blocked by its peer. But if the diner
is too slow so that there are `max_dishes_on_table` dishes left on the table, the chef would wait
until the number of dishes is less than this setting. And, apparently, if there is no dishes on the
table, the diner would wait for new ones to be prepared by the chef.

## Exceptions in coroutines

Expand Down
47 changes: 47 additions & 0 deletions include/seastar/core/std-coroutine.hh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,53 @@ public:
bool done() const noexcept { return __builtin_coro_done(_pointer); }
};

struct noop_coroutine_promise { };

template<>
struct coroutine_handle<noop_coroutine_promise> {
constexpr operator coroutine_handle<>() const noexcept {
return coroutine_handle<>::from_address(address());
}

constexpr explicit operator bool() const noexcept { return true; }

constexpr void* address() const noexcept { return _pointer; }

constexpr bool done() const noexcept { return false; }

void operator()() const noexcept { }

void resume() const noexcept { }

void destroy() const noexcept { }

noop_coroutine_promise& promise() const noexcept {
auto* p = __builtin_coro_promise(_pointer, alignof(noop_coroutine_promise), false);
return *static_cast<noop_coroutine_promise*>(p);
}

private:
friend coroutine_handle<noop_coroutine_promise> noop_coroutine() noexcept;

coroutine_handle() noexcept = default;

static struct {
private:
static void dummy_resume_destroy() { }
public:
void (*resume)() = dummy_resume_destroy;
void (*destroy)() = dummy_resume_destroy;
struct noop_coroutine_promise _p;
} _frame;
void *_pointer = &_frame;
};

using noop_coroutine_handle = coroutine_handle<noop_coroutine_promise>;

inline noop_coroutine_handle noop_coroutine() noexcept {
return {};
}

struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
template<typename T>
Expand Down
Loading

0 comments on commit a2f6416

Please sign in to comment.