diff --git a/doc/tutorial.md b/doc/tutorial.md index 1646dd08b79..57f9ca45485 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -393,7 +393,9 @@ seastar::future prepare_ingredients(Ingredients&&); seastar::future cook_a_dish(Preprocessed&&); seastar::future<> consume_a_dish(Dish&&); -seastar::coroutine::experimental::generator make_dishes(Ingredients&& ingredients) { +seastar::coroutine::experimental::generator +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)); @@ -401,7 +403,7 @@ seastar::coroutine::experimental::generator make_dishes(Ingredients&& ingr } } -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()) { @@ -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 diff --git a/include/seastar/core/std-coroutine.hh b/include/seastar/core/std-coroutine.hh index 9b4189841ea..c11628a8ce7 100644 --- a/include/seastar/core/std-coroutine.hh +++ b/include/seastar/core/std-coroutine.hh @@ -101,6 +101,53 @@ public: bool done() const noexcept { return __builtin_coro_done(_pointer); } }; +struct noop_coroutine_promise { }; + +template<> +struct coroutine_handle { + 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(p); + } + +private: + friend coroutine_handle 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; + +inline noop_coroutine_handle noop_coroutine() noexcept { + return {}; +} + struct suspend_never { constexpr bool await_ready() const noexcept { return true; } template diff --git a/include/seastar/coroutine/generator.hh b/include/seastar/coroutine/generator.hh index d27b9a50338..34068baacfe 100644 --- a/include/seastar/coroutine/generator.hh +++ b/include/seastar/coroutine/generator.hh @@ -30,95 +30,258 @@ namespace seastar::coroutine::experimental { -template +template class Container> class generator; +/// `seastar::coroutine::experimental` is used as the type of the first +/// parameter of a buffered generator coroutine. +/// +/// the value of a `buffer_size_t` specifies the size of the buffer holding the +/// values produced by the generator coroutine. Unlike its unbuffered variant, +/// the bufferred generator does not wait for its caller to consume every single +/// produced values. Instead, it puts the produced values into an internal +/// buffer, before the buffer is full or the generator is suspended. This helps +/// to alleviate the problem of pingpong between the generator coroutine and +/// its caller. +enum class buffer_size_t : size_t; + namespace internal { +using SEASTAR_INTERNAL_COROUTINE_NAMESPACE::coroutine_handle; +using SEASTAR_INTERNAL_COROUTINE_NAMESPACE::suspend_never; +using SEASTAR_INTERNAL_COROUTINE_NAMESPACE::suspend_always; +using SEASTAR_INTERNAL_COROUTINE_NAMESPACE::suspend_never; +using SEASTAR_INTERNAL_COROUTINE_NAMESPACE::noop_coroutine; + template using next_value_t = std::optional; +template