== Custom Executors

One of the reasons cobalt defaults to https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio/reference/any_io_executor.html::[`asio::any_io_executor`]
is that it is a type-erased executor, i.e. you can provide your own event-loop without needing to recompile `cobalt`.

However, during the development of the Executor TS, the executor concepts got a bit unintuitive, to put it mildly.

Ruben Perez wrote an excellent https://anarthal.github.io/cppblog/asio-props.html::[blog post], which I am shamelessly going to draw from.

=== Definition

An executor is a type that points to the actual event loop and is (cheaply) copyable,
which supports properties (see below) is equality comparable and has an `execute` function.

==== `execute`

[source,cpp]
----
struct example_executor
{
  template<typename Fn>
  void execute(Fn && fn) const;
};
----

The above function executes `fn` in accordance with its properties.

==== Properties

A property can be queried, preferred or required, e.g.:

[source,cpp]
----
struct example_executor
{
  // get a property by querying it.
  asio::execution::relationship_t &query(asio::execution::relationship_t) const
  {
    return asio::execution::relationship.fork;
  }

  // require an executor with a new property
  never_blocking_executor require(const execution::blocking_t::never_t);

  // prefer an executor with a new property. the executor may or may not support it.
  never_blocking_executor prefer(const execution::blocking_t::never_t);
  // not supported
  example_executor prefer(const execution::blocking_t::always_t);
};
----

==== Properties of the `asio::any_io_executor`

In order to wrap an executor in an `asio::any_io_executor` two properties are required:

 - `execution::context_t
 - `execution::blocking_t::never_t`

That means we need to either make them require-able (which makes no sense for context) or return the expected value
from `query`.

The `execution::context_t` query should return `asio::execution_context&` like so:

[source,cpp]
----
struct example_executor
{
  asio::execution_context &query(asio::execution::context_t) const;
};
----

The execution context is used to manage lifetimes of services that manage lifetimes io-objects,
such as asio's timers & sockets. That is to say, by providing this context, all of asio's io works with it.

NOTE: The `execution_context` must remain alive after the executor gets destroyed.

The following may be preferred:

 - `execution::blocking_t::possibly_t`
 - `execution::outstanding_work_t::tracked_t`
 - `execution::outstanding_work_t::untracked_t`
 - `execution::relationship_t::fork_t`
 - `execution::relationship_t::continuation_`

That means you might want to support them in your executor for optimizations.

// thanks @anarthal

==== The `blocking` property

As we've seen before, this property controls whether the function passed to `execute()`
can be run immediately, as part of `execute()`, or must be queued for later execution.
Possible values are:

* `asio::execution::blocking.never`: never run the function as part of `execute()`.
This is what `asio::post()` does.
* `asio::execution::blocking.possibly`: the function may or may not be run as part of `execute()`.
This is the default (what you get when calling `io_context::get_executor`).
* `asio::execution::blocking.always`: the function is always run as part of `execute()`.
This is not supported by `io_context::executor`.

==== The `relationship` property

`relationship` can take two values:

* `asio::execution::relationship.continuation`: indicates that the function passed to `execute()`
is a continuation of the function calling `execute()`.
* `asio::execution::relationship.fork`: the opposite of the above. This is the default
(what you get when calling `io_context::get_executor()`).

Setting this property to `continuation` enables some optimizations
in how the function gets scheduled. It only has effect if the function
is queued (as opposed to run immediately). For `io_context`, when set, the function
is scheduled to run in a faster, thread-local queue, rather than the context-global one.

==== The `outstanding_work_t` property

`outstanding_work` can take two values:

* `asio::execution::outstanding_work.tracked`: indicates that while the executor is alive, there's still work to do.
* `asio::execution::outstanding_work.untracked`: the opposite of the above. This is the default
(what you get when calling `io_context::get_executor()`).

Setting this property to `tracked` means that the event loop will not return as long as the `executor` is alive.

=== A minimal executor

With this, let's look at the interface of a minimal executor.

[source,cpp]
----
struct minimal_executor
{
  minimal_executor() noexcept;

  asio::execution_context &query(asio::execution::context_t) const;

  static constexpr asio::execution::blocking_t
  query(asio::execution::blocking_t) noexcept
  {
    return asio::execution::blocking.never;
  }

  template<class F>
  void execute(F && f) const;

  bool operator==(minimal_executor const &other) const noexcept;
  bool operator!=(minimal_executor const &other) const noexcept;
};
----

NOTE: See https://github.com/boostorg/cobalt/tree/master/example/python.cpp[example/python.cpp]
for an implementation using python's `asyncio` event-loop.

=== Adding a work guard.

Now, let's add in a `require` function for the `outstanding_work` property, that uses multiple types.

[source,cpp]
----
struct untracked_executor : minimal_executor
{
  untracked_executor() noexcept;

  constexpr   tracked_executor require(asio::execution::outstanding_work::  tracked_t) const;
  constexpr untracked_executor require(asio::execution::outstanding_work::untracked_t) const {return *this; }
};

struct untracked_executor : minimal_executor
{
  untracked_executor() noexcept;

  constexpr   tracked_executor require(asio::execution::outstanding_work::  tracked_t) const {return *this;}
  constexpr untracked_executor require(asio::execution::outstanding_work::untracked_t) const;
};
----

Note that it is not necessary to return a different type from the `require` function, it can also be done like this:

[source,cpp]
----
struct trackable_executor : minimal_executor
{
  trackable_executor() noexcept;

  constexpr trackable_executor require(asio::execution::outstanding_work::  tracked_t) const;
  constexpr trackable_executor require(asio::execution::outstanding_work::untracked_t) const;
};
----

If we wanted to use `prefer` it would look as shown below:

[source,cpp]
----
struct trackable_executor : minimal_executor
{
  trackable_executor() noexcept;

  constexpr trackable_executor prefer(asio::execution::outstanding_work::  tracked_t) const;
  constexpr trackable_executor prefer(asio::execution::outstanding_work::untracked_t) const;
};
----

=== Summary

As you can see, the property system is not trivial, but quite powerful.
Implementing a custom executor is a problem category of its own, which is why this documentation doesn't do that.
Rather, there is an example of how to wrap a python event loop in an executor.

Below are some reading recommendations.

 - https://cppalliance.org/richard/2020/10/31/RichardsOctoberUpdate.html[Richards October 2020 Update - container a qt-executor]
 - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0443r13.html[A Unified Executors Proposal for C++ | P0443R13]
 - https://www.boost.org/doc/libs/master/doc/html/boost_asio/std_executors.html[Asio's documentation on std executors]

