facebookexperimental / facebookexperimental/libunifex
Add an easy example so people can write their own schedulers.
- Dominant language
- C++
- Stars
- 1.7k
- Forks
- 210
- PR merge metrics
- No merged PRs in 30d
Description
For people wanting to write their own schedulers, can you please add an easy example? Most of the code I dug through is either too-complex or too tightly coupled. Here is something I came up with - A scheduler running on the same thread which produces values in a loop with a sleep interval. However -
* I am not really sure that the code is in-line with how schedulers should be written in unifex.
* I am doing std::move() on the same object multiple times withing the loop - Should not be legal.
* Also, the code is still a bit long / complex - Can this be trimmed down? Do I really need task_base in the code?
* Is this code ok, if we use sync_wait(when_all(...)) with Senders made from other schedulers?
Out-of-context: Is there a convenience function to create Receivers? I am not able to handle the errors the sender is producing.
The sleepy_scheduler code:
```cpp
#include
#include
#include
#include
#include
class sleepy_ctx;
struct task_base {};
template
struct _op {
class type;
};
template
class _op::type final : task_base {
using stop_token_type = unifex::stop_token_type_t;
public:
template
explicit type(Receiver2&& receiver, sleepy_ctx* ctx)
: receiver_((Receiver2 &&) receiver),
ctx_(ctx)
{}
void start() noexcept;
private:
static void start_impl(task_base* t, int iters, int sleeps) noexcept {
auto& self = *static_cast(t);
for (int i = 0; i < iters; ++i) {
int ret = sleep(sleeps);
if (ret != 0) {
//unifex::set_error(std::move(self.receiver_), ret);
unifex::set_value(std::move(self.receiver_), -ret); // TODO: set error
} else {
unifex::set_value(std::move(self.receiver_), i);
}
}
unifex::set_done(std::move(self.receiver_));
}
UNIFEX_NO_UNIQUE_ADDRESS Receiver receiver_;
sleepy_ctx* const ctx_;
};
template
using sleepy_operation = typename _op>::type;
class sleepy_sender {
// blocking is a customisation point which takes Sender sleepy_sender as parameter
// TODO: Understand how this works
friend constexpr unifex::blocking_kind
tag_invoke(unifex::tag_t, const sleepy_sender&) noexcept {
// Since sleepy_operation.start is guaranteed to call reveiver (via set_value etc)
// in same thread before start returns.
return unifex::blocking_kind::always_inline;
}
// SenderOf
public:
template <
template class Variant,
template class Tuple>
using value_types = Variant>;
// int error types
template class Variant>
using error_types = Variant;
static constexpr bool sends_done = true;
explicit sleepy_sender(sleepy_ctx* ctx) noexcept
: ctx_(ctx)
{}
template
sleepy_operation connect(Receiver&& receiver) const& {
std::cout << "?? thread=" << std::this_thread::get_id() << ": sleepy_sender::connect\n";
return sleepy_operation{(Receiver &&) receiver, ctx_};
}
private:
sleepy_ctx* const ctx_;
};
struct sleepy_sched {
explicit sleepy_sched(sleepy_ctx* ctx) noexcept
: ctx_(ctx)
{}
sleepy_sender schedule() const noexcept {
return sleepy_sender{ctx_};
}
friend bool operator==(sleepy_sched a, sleepy_sched b) noexcept {
return a.ctx_ == b.ctx_;
}
friend bool operator!=(sleepy_sched a, sleepy_sched b) noexcept {
return a.ctx_ != b.ctx_;
}
private:
sleepy_ctx* ctx_;
};
struct sleepy_ctx {
sleepy_ctx(int counts, int sleeps)
: counts_{counts}, sleeps_{sleeps}
{}
sleepy_sched get_scheduler() {
return sleepy_sched{this};
}
int counts_;
int sleeps_;
};
template
inline void _op::type::start() noexcept {
std::cout << "?? thread=" << std::this_thread::get_id() << ": sleepy_operation::start\n";
_op::type::start_impl(this, ctx_->counts_, ctx_->sleeps_);
}
#include
#include
void signal_handler(int signal) {} // do nothing on sigint
using namespace unifex;
int main() {
// The signal handler does not do anything. However SIGINT interrupts
// our sleep call, and causes set_error calls
std::signal(SIGINT, signal_handler);
sleepy_ctx ioctx{5, 2}; // iterate 5 time, with 2 second sleep
auto sched = ioctx.get_scheduler();
std::cout << "main thread=" << std::this_thread::get_id() << '\n';
auto begin_sender = schedule(sched);
std::cout << "main thread=" << std::this_thread::get_id() << ": after schedule\n";
// TODO: how to hook a Receiver here which also handles the set_error calls?
auto x1 = transform(begin_sender, [](int sval) {
std::cout << "?? thread=" << std::this_thread::get_id() << " value=" << sval << '\n';
return 1;
});
std::cout << "main thread=" << std::this_thread::get_id() << ": after transform\n";
auto out = sync_wait(x1);
std::cout << "main thread=" << std::this_thread::get_id() << ": after sync_wait\n";
return 0;
}
```
Contributor guide
Assessment
This issue has not been assessed yet.