Flow control

Capy’s design is driven by the needs of network I/O processing, where a piece of the program, in order to continue, needs to wait for an I/O operation to finish, but when this happens is unpredictable. The challenge here is:

  1. To utilize this waiting time as efficiently as possible, processing other tasks.

  2. To avoid any concurrency or lifetime management bugs.

  3. To have the user code be simple and intuitive.

To achieve this, Capy utilizes the proactor pattern hidden behind the coroutine co_await mechanism. In this pattern, when the user needs an I/O operation to be performed to see its results, they:

  1. Schedule in the I/O runtime an operation to be performed.

  2. Register a piece of code to be invoked when the I/O operation finishes.

  3. Return control, so that other tasks can make progress.

This is exactly what happens when you write:

process(co_await stream.read_some(buffer));
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/  (1)
/*~~~*/                                    (2)
1 The co_await-expression schedules an I/O operation to be called in the execution context, and registers the resumption of the coroutine after the I/O operation finishes. Once the scheduling and registering is done, the coroutine is suspended, and the program thread switches to performing other tasks.
2 When the execution context finishes performing the scheduled I/O operation, it resumes the coroutine, and the next instruction following the co_await-expression.

While all this registering happens hidden in the coroutine mechanism, the coroutine body reads as sequential business logic.