facebookexperimental / libunifex

Unified Executors
Other
1.48k stars 188 forks source link

Question: why does my scheduler fail the scheduler concept? #478

Closed aaron-michaux closed 1 year ago

aaron-michaux commented 1 year ago

Going from Eric's presentations, we have the very simple concept for scheduler:

concept scheduler:
    schedule(scheduler) -> sender

I've got a super bare-bones implementation here:


class Scheduler {
public:
  constexpr explicit Scheduler(ExecutionContext& context) noexcept : context_(context) {}

  constexpr bool operator==(const Scheduler& other) const noexcept {
    return &context_ == &other.context_;
  }
  constexpr bool operator!=(const Scheduler& other) const noexcept { return !(*this == other); }

  constexpr SchedulerSender schedule() const noexcept { return {context_}; }
  friend SchedulerSender schedule(Scheduler self) noexcept { return self.schedule(); }

  constexpr ExecutionContext& context() const noexcept { return context_; }

private:
  ExecutionContext& context_; 
};

In my main function:

...
  ExecutionContext ctx{2, 1};
  unifex::execute(Scheduler{ctx}, []() { fmt::print("Hello World!\n"); });
...

This fails to compile: gcc12.2.0 tells me that Scheduler doesn't match the Scheduler concept:

src/main.cpp:37:18: error: no match for call to ‘(const unifex::_execute::_cpo::_fn) (sgrpc::Scheduler, main(int, char**)::<lambda()>)’
   37 |   unifex::execute(sgrpc::Scheduler{ctx}, []() { fmt::print("Hello World!\n"); });
      |   ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from src/main.cpp:11:
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/execute.hpp:57:10: note: candidate: ‘template<class Scheduler, class Fn>  requires (_lvalue_callable<Fn>) && (scheduler<Scheduler>) && (tag_invocable<unifex::_execute::_cpo::_fn, Scheduler, Fn>) void unifex::_execute::_cpo::_fn::operator()(Scheduler&&, Fn&&) const’
   57 |     void operator()(Scheduler&& sched, Fn&& fn) const
      |          ^~~~~~~~
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/execute.hpp:57:10: note:   template argument deduction/substitution failed:
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/execute.hpp:57:10: note: constraints not satisfied
In file included from src/sgrpc/scheduler.hpp:6,
                 from src/main.cpp:8:
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/scheduler_concepts.hpp: In substitution of ‘template<class Scheduler, class Fn>  requires (_lvalue_callable<Fn>) && (scheduler<Scheduler>) && (tag_invocable<unifex::_execute::_cpo::_fn, Scheduler, Fn>) void unifex::_execute::_cpo::_fn::operator()(Scheduler&&, Fn&&) const [with Scheduler = sgrpc::Scheduler; Fn = main(int, char**)::<lambda()>]’:
src/main.cpp:37:18:   required from here
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/scheduler_concepts.hpp:101:3:   required for the satisfaction of ‘scheduler<Scheduler>’ [with Scheduler = sgrpc::Scheduler]
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/scheduler_concepts.hpp:102:5:   in requirements with ‘S&& s’ [with S = sgrpc::Scheduler]
/opt/arch/x86_64-linux-gnu_gcc-12.2.0_stdcxx/include/unifex/scheduler_concepts.hpp:103:15: note: the required expression ‘unifex::_schedule::schedule((S&&)(s))’ is invalid
  103 |       schedule((S&&) s);
      |       ~~~~~~~~^~~~~~~~~

But I have a friend function that does just this! I've spent all day trying to build this up from scratch (so that I eventually understand p2300 thoroughly)... what have I done wrong here?

Thanks in advance for your time.

lewissbaker commented 1 year ago

When you say you have declared a friend function for schedule, what exactly does that look like?

You customise the schedule() operation by providing an overload of tag_invoke(tag_t<schedule>, your_type), rahter than schedule(your_type).

aaron-michaux commented 1 year ago

Hi, thank you for pointing me in the right direction. I spent the morning reading about tag_invoke. For anybody interested, here are the sources that I found relevant:

aaron-michaux commented 1 year ago

I'm feeling pretty confident with the tag_invoke mechanism; however, I cannot figure out the magic key-strokes to make it work with unifex.

class Scheduler {
public:
  constexpr explicit Scheduler(ExecutionContext& context) noexcept : context_(context) {}
  constexpr bool operator==(const Scheduler& other) const noexcept;
  constexpr bool operator!=(const Scheduler& other) const noexcept;

  constexpr SchedulerSender schedule() const noexcept { return {context_}; }

  friend SchedulerSender tag_invoke(unifex::tag_t<unifex::schedule>, const Scheduler& self) noexcept {
    return self.schedule();
  }

private:
  ExecutionContext& context_; // not-owned
};

Still getting the same compile error. I found several example uses in the libunifex source code that look just like what I've written: for example, in unifex/static_thread_pool.hpp we have unifex::_static_thread_pool::context::scheduler using the schedule CPO like so:

      friend schedule_sender
      tag_invoke(tag_t<schedule>, const scheduler& s) noexcept {
        return s.make_sender_();
      }

What's going on?

aaron-michaux commented 1 year ago

Hi, I figured it out as follows:

I got the working code for the inline_scheduler, and slowly added/changed it so that it worked as the scheduler I needed. The compiler diagnostics weren't helpful.

I've written my first scheduler, that schedules on a thread-pool. Next, how to schedule grpc calls.

Thanks.