asynchronousrepeatscyllaseastar

seastar::repeat does not run for consistent number of times


I am learning seastar async framework. I am trying to understand the seastar::repeat method for looping. Consider the following snippet,

int main(int argc, char **argv) {
  app_template app;
  return app.run(argc, argv, [] {

    int count = 0;

    return seastar::repeat(
        [&count]() -> seastar::future<seastar::stop_iteration> {
          if (count >= 100) {
            return seastar::make_ready_future<seastar::stop_iteration>(
                seastar::stop_iteration::yes);
          }
          std::cout << "Count " << count << std::endl;
          count++;
          return seastar::make_ready_future<seastar::stop_iteration>(
              seastar::stop_iteration::no);
        });
  });
}

I am expecting the program to print 1 to 100 and then return. However, the program terminates at random iterations [ quits at Count 2/35/78/.. ]

Can someone clarify Why seastar::repeat is behaving oddly?

TIA


Solution

  • Your bug is (I think) the lifetime of the variable count. It is a local variable of the lambda function you introduce, but you immediately return from that function, returning the future which repeat() returned. Remember that the whole point of asynchronous functions is that they often continue doing things after having returned (they return a future, which doesn't just yet have a ready value).

    In your case, after the function returns, something else may overwrite the memory that used to hold the variable "count" and ruin your count.

    The whole idea of asynchronous functions and how to correctly handle the lifetime of variables that need to live while they execute is described in depth https://github.com/scylladb/seastar/blob/master/doc/tutorial.md. In your case the "classic" solution to keeping count alive was to use do_with(), as in:

        return seastar::do_with(0, [](int& count) {
            seastar::repeat([&count]() -> seastar::future<seastar::stop_iteration> {
               if (count >= 100) {
                  ...
    

    Here the do_with() allocates memory for the counter, and holds it alive until the asynchronous function inside it (the repeat call) is done, not just returns.

    A more "modern" alternative is to use coroutines instead of continuations, and "co_await" the repeat() call instead of "return"ing it. This keeps everything on the stack (including the count variable) alive.