facebookexperimental / facebookexperimental/libunifex

Add an easy example so people can write their own schedulers.

Open
#188 2 comments 0 reactions 0 assignees View on GitHub
documentation
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.