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:
-
To utilize this waiting time as efficiently as possible, processing other tasks.
-
To avoid any concurrency or lifetime management bugs.
-
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:
-
Schedule in the I/O runtime an operation to be performed.
-
Register a piece of code to be invoked when the I/O operation finishes.
-
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.