Skip to content

Build error when starting exec::task in some schedulers, because they miss the scheduler_concept typedef #2134

Description

@miwechner

Hi,

we recently noticed that you cannot build a program where an exec::task is started in some schedulers. In particular exec::static_thread_pool and exec::timed_thread_context are affecting us.

This was the most minimal example that i could find:

#include <exec/static_thread_pool.hpp>
#include <exec/task.hpp>
#include <stdexec/execution.hpp>

auto main() -> int
{
  exec::static_thread_pool ctx;
  stdexec::sync_wait(stdexec::starts_on(ctx.get_scheduler(), [&]()-> exec::task<void>{
    co_return;
  }()));
  return 0;
}
Compile error
[main] Building folder: /home/coder/stdexec/build/base-debug example.hello_world
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /home/coder/stdexec/build/base-debug --target example.hello_world --
[build] [0/2] Re-checking globbed directories...
[build] [1/3] Building CXX object examples/CMakeFiles/example.hello_world.dir/hello_world.cpp.o
[build] FAILED: examples/CMakeFiles/example.hello_world.dir/hello_world.cpp.o 
[build] /usr/bin/g++ -DTBB_USE_DEBUG -isystem /home/coder/stdexec/include -isystem /home/coder/stdexec/build/base-debug/include -g -std=gnu++20 -Wno-maybe-uninitialized -fcoroutines -fconcepts-diagnostics-depth=10 -Wall -Werror=unused-parameter -Wno-non-template-friend -MD -MT examples/CMakeFiles/example.hello_world.dir/hello_world.cpp.o -MF examples/CMakeFiles/example.hello_world.dir/hello_world.cpp.o.d -o examples/CMakeFiles/example.hello_world.dir/hello_world.cpp.o -c /home/coder/stdexec/examples/hello_world.cpp
[build] In file included from /home/coder/stdexec/examples/hello_world.cpp:19:
[build] /home/coder/stdexec/include/exec/task.hpp: In instantiation of ‘constexpr experimental::execution::__task::__default_task_context_impl<_SchedulerAffinity>::__default_task_context_impl(_ParentPromise&) [with _ParentPromise = stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >; experimental::execution::__task::__scheduler_affinity _SchedulerAffinity = experimental::execution::__task::__scheduler_affinity::__sticky]’:
[build] /home/coder/stdexec/include/stdexec/__detail/__manual_lifetime.hpp:52:28:   required from ‘constexpr _Ty& stdexec::__manual_lifetime<_Ty>::__construct(_Args&& ...) [with _Args = {stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >&}; _Ty = experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky>]’
[build]    52 |       return *std::launder(::new (static_cast<void*>(__buffer_))
[build]       |                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[build]    53 |                              _Ty{static_cast<_Args&&>(__args)...});
[build]       |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[build] /home/coder/stdexec/include/stdexec/__detail/__optional.hpp:110:41:   required from ‘constexpr _Tp& stdexec::__opt::__optional<_Tp>::emplace(_Us&& ...) [with _Us = {stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >&}; _Tp = experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky>]’
[build]   110 |         _Tp& __t  = __value_.__construct(static_cast<_Us&&>(__us)...);
[build]       |                     ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[build] /home/coder/stdexec/include/exec/task.hpp:676:47:   required from ‘constexpr std::__n4861::coroutine_handle<void> experimental::execution::__task::basic_task<_Ty, _Context>::__task_awaiter<_ParentPromise>::await_suspend(std::__n4861::coroutine_handle<_ParentPromise2>) [with _ParentPromise2 = stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >; _ParentPromise = stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >; _Ty = void; _Context = experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky>]’
[build]   676 |           __coro_.promise().__context_.emplace(__parent.promise());
[build]       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
[build] /home/coder/stdexec/include/stdexec/__detail/__connect_awaitable.hpp:136:41:   required from ‘constexpr auto stdexec::__connect_await::__awaitable_wrapper<_Derived>::await_suspend(std::__n4861::coroutine_handle<_Promise>) [with _Promise = stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >; _Derived = stdexec::__connect_await::__awaitable_state<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > > >::__state]’
[build]   136 |         return __awaiter().await_suspend(__h);
[build]       |                ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
[build] /home/coder/stdexec/include/stdexec/__detail/__connect_awaitable.hpp:134:52:   required from ‘constexpr auto stdexec::__connect_await::__awaitable_wrapper<_Derived>::await_suspend(std::__n4861::coroutine_handle<_Promise>) [with _Promise = stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >; _Derived = stdexec::__connect_await::__awaitable_state<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__connect_await::__promise<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > > >]’
[build]   134 |         noexcept(noexcept(__awaiter().await_suspend(__h)))
[build]       |                           ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
[build] /home/coder/stdexec/include/stdexec/__detail/__connect_awaitable.hpp:408:73:   required from ‘void stdexec::__connect_await::__opstate<_Awaiter, _Receiver>::start() & [with _Awaitable = experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >; _Receiver = stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > >]’
[build]   408 |             using __suspend_result_t = decltype(__awaiter_.await_suspend(__coro));
[build]       |                                                 ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
[build] /home/coder/stdexec/include/stdexec/__detail/__operation_states.hpp:133:17:   required from ‘constexpr void stdexec::start_t::operator()(_Op&) const [with _Op = stdexec::__connect_await::__opstate<experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >, stdexec::__seq::__rcvr2<stdexec::__sync_wait::__receiver<>, stdexec::__sched_env<experimental::execution::_pool_::_static_thread_pool::scheduler, stdexec::__env::__fwd<const stdexec::__sync_wait::__env&> > > >]’
[build]   133 |       __op.start();
[build]       |       ~~~~~~~~~~^~
[build] /home/coder/stdexec/include/stdexec/__detail/__sequence.hpp:290:25:   required from ‘constexpr void stdexec::__seq::__opstate<_CvSender1, _Sender2, _Receiver>::__start_next() [with _CvSender1 = stdexec::{anonymous}::__sexpr<<lambda closure object>stdexec::<lambda()>()>; _Sender2 = experimental::execution::__task::basic_task<void, experimental::execution::__task::__default_task_context_impl<experimental::execution::__task::__scheduler_affinity::__sticky> >; _Receiver = stdexec::__sync_wait::__receiver<>]’
[build]   290 |           STDEXEC::start(__op);
[build]       |                         ^
[build] /home/coder/stdexec/include/stdexec/__detail/__sequence.hpp:284:22:   required from here
[build]   284 |       constexpr void __start_next() noexcept
[build]       |                      ^~~~~~~~~~~~
[build] /home/coder/stdexec/include/exec/task.hpp:163:33: error: no match for ‘operator=’ (operand types are ‘experimental::execution::__task::__any_scheduler’ and ‘experimental::execution::_pool_::_static_thread_pool::scheduler’)
[build]   163 |             this->__scheduler_  = __parent_sched;
[build]       |             ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
[build] /home/coder/stdexec/include/exec/task.hpp:163:33: note: there are 2 candidates
[build] /home/coder/stdexec/include/exec/task.hpp:64:12: note: candidate 1: ‘constexpr experimental::execution::__task::__any_scheduler& experimental::execution::__task::__any_scheduler::operator=(const experimental::execution::__task::__any_scheduler&)’
[build]    64 |     struct __any_scheduler
[build]       |            ^~~~~~~~~~~~~~~
[build] /home/coder/stdexec/include/exec/task.hpp:64:12: note: no known conversion for argument 1 from ‘experimental::execution::_pool_::_static_thread_pool::scheduler’ to ‘const experimental::execution::__task::__any_scheduler&’
[build] /home/coder/stdexec/include/exec/task.hpp:64:12: note: candidate 2: ‘constexpr experimental::execution::__task::__any_scheduler& experimental::execution::__task::__any_scheduler::operator=(experimental::execution::__task::__any_scheduler&&)’
[build] /home/coder/stdexec/include/exec/task.hpp:64:12: note: no known conversion for argument 1 from ‘experimental::execution::_pool_::_static_thread_pool::scheduler’ to ‘experimental::execution::__task::__any_scheduler&&’
[build] ninja: build stopped: subcommand failed.
[proc] The command: /usr/bin/cmake --build /home/coder/stdexec/build/base-debug --target example.hello_world -- exited with code: 1
[driver] Build completed: 00:00:02.833
[build] Build finished with exit code 1

With the scheduler from exec::timed_thread_context the same problem occurs. For exec::single_thread_context, which returns the scheduler for stdexec::run_loop, it compiles.

I guess it comes from the __semi_scheduler concept not being fulfilled:

// A scheduler concept that does not check for copyability since that creates a cycle
// in the type system.
template <class _Scheduler>
concept __semi_scheduler = requires(_Scheduler& __sched) {
typename _Scheduler::scheduler_concept;
requires __std::derived_from<typename _Scheduler::scheduler_concept, scheduler_tag>;
{ schedule(__sched) } -> sender;
};

The affected schedulers do not have this scheduler_concept typedef - hence the concept is not fulfilled. When the scheduler_concept typedef is added, the build error is also fixed.

Have these particular scheduler implementations just not been updated or is it a problem of the task-implementation that those schedulers cannot be used?

Thanks in advance for any answers.

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