Skip to content

exec::task coroutine gets rescheduled when resuming #1634

@kyusic

Description

@kyusic

Consider the following code using exec::task:

exec::task<void> h1() {
  std::cerr << "entered h1\n";
  co_return;
}

exec::task<void> h2() {
  co_await h1();
  std::cerr << "left h1\n";
}

When h1() completes and h2() resumes, I expected execution to continue on the same execution resource that was running h1(). However, it looks like exec::task reschedules onto its stored scheduler.
You can verify this by using a scheduler that prints a message when schedule() is called:

template <stdexec::scheduler BaseScheduler>
struct printing_scheduler {
  BaseScheduler base_sched;

  struct sender {
    using sender_concept = stdexec::sender_t;

    BaseScheduler base_sched;

    using base_sender_t = stdexec::schedule_result_t<BaseScheduler>;

    template <class Env>
    auto get_completion_signatures(Env &&) const noexcept
      -> stdexec::completion_signatures_of_t<base_sender_t, Env> {
      return {};
    }

    template <class Receiver>
    auto connect(Receiver &&rcvr) && {
      return stdexec::connect(
        stdexec::schedule(base_sched) | stdexec::then([] { std::cerr << "scheduled\n"; }),
        static_cast<Receiver &&>(rcvr));
    }

    constexpr auto get_env() const noexcept {
      return stdexec::env{
        stdexec::prop{
                      stdexec::get_completion_scheduler<stdexec::set_value_t>, printing_scheduler{base_sched}},
        stdexec::get_env(stdexec::schedule(base_sched))
      };
    }
  };

  auto schedule() const {
    return sender{base_sched};
  }

  bool operator==(const printing_scheduler &other) const = default;

  auto query(stdexec::get_forward_progress_guarantee_t q) const noexcept {
    return q(base_sched);
  }
};

static_assert(stdexec::scheduler<printing_scheduler<stdexec::inline_scheduler>>);
static_assert(stdexec::sender<printing_scheduler<stdexec::inline_scheduler>::sender>);

int main() {
  auto ctx = exec::single_thread_context{};
  stdexec::sync_wait(stdexec::starts_on(printing_scheduler{ctx.get_scheduler()}, h2()));
}

The output is:

scheduled
entered h1
scheduled   <- here you can see it gets rescheduled
left h1

I tried to track down the cause. When co_awaiting another exec::task inside an exec::task, it appears to run the following code:

template <sender _Awaitable>
requires __scheduler_provider<_Context>
auto await_transform(_Awaitable&& __awaitable) noexcept -> decltype(auto) {
// TODO: If we have a complete-where-it-starts query then we can optimize
// this to avoid the reschedule
return stdexec::as_awaitable(
continues_on(static_cast<_Awaitable&&>(__awaitable), get_scheduler(*__context_)),
*this);
}

I suspect the rescheduling happens because continues_on() is used here.
Should the following overload be selected instead?

template <class _Awaitable>
auto await_transform(_Awaitable&& __awaitable) noexcept -> decltype(auto) {
return with_awaitable_senders<__promise>::await_transform(
static_cast<_Awaitable&&>(__awaitable));
}

Thank you for your time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions