Scheduler and Coroutine Operation

poco uses a event broadcasting system to propagate signals between coroutines. The scheduler acts as the broker and passes all queued signals it has between coroutine execution.

Note

These signals are distinct from OS signals.

This messaging between coroutines is how poco implements coroutine suspension.

Coroutine Operational States

Each coroutine maintains its own internal read-only state. This indicates to the scheduler which coroutines are ready to execute and which ones are waiting on an event.

@startuml

state Ready
state Running
state Waiting
state Finished

[*] --> Ready : create

Ready --> Running : coro_resume()

Running --> Ready : coro_yield()
Running --> Waiting : coro_yield(SIG_WAIT)

Waiting --> Ready: coro_notify(), if event matches

Running --> Finished : return

Finished --> [*]

@enduml

Possible coroutine states and transitions.

Communication with the Scheduler

When a coroutine yields back control to the scheduler, it returns with a particular signal the scheduler should action. These actions are found in coro_signal.

Event Sinks and Sources

Internally, coroutines contain a single event source, and up to two event sinks. This ensures the coroutine can suspend on an event, as well as an optional timeout value (if using timeout aware APIs).

If the coroutine yields with CoroSignal::CORO_SIG_NOTIFY or CoroSignal::CORO_SIG_NOTIFY_AND_WAIT, the scheduler must add the notified event to its list of pending events.

Matching between event sinks and sources is done via the signal type. Most events have a 1:1 relationship between the event source and the corresponding event sink.

A typical sequence is shown below for a coroutine informing the scheduler that it is waiting for an event. Here we show coroutine A waiting on the CoroEventSinkType::CORO_EVTSINK_DELAY.

@startuml

Scheduler -> CoroutineA: coro_resume()
activate CoroutineA
    ...
    CoroutineA -> CoroutineA: setup event sink (CORO_EVTSRC_EVENT_GET)
    Scheduler <-- CoroutineA: coro_yield(CORO_SIG_WAIT)
deactivate CoroutineA

Scheduler -> CoroutineB: coro_resume()
activate CoroutineB
    ...
    CoroutineB -> CoroutineB: setup event source (CORO_EVTSRC_EVENT_SET)
    Scheduler <-- CoroutineB: coro_yield(CORO_SIG_NOTIFY)
deactivate CoroutineB

Scheduler -> CoroutineA: coro_notify(CORO_EVTSRC_EVENT_SET)

@enduml

Example of coroutine suspension and unblock via the event primitive.