boost.png (6897 bytes) Home Libraries People FAQ More

PrevUpHomeNext

Design Rationale

Reference counting and movability

The initial version of Boost.Coroutine reference counted the coroutine class template. Also the coroutine::self type was an alias for the coroutine class itself. The rationale was that, when used in a symmetric coroutine design it would be easy for a coroutine to pass a copy of itself to other coroutines without needing any explicit memory management. When all other coroutines dropped all references to a specific coroutine that was deleted. Unfortunately this same desirable behavior could backfire horribly if a cycle of coroutines where to be formed.

In the end reference counting behavior was removed from the coroutine interface andcoroutine where made movable. The same change lead to the creation of coroutine::self to segregate coroutine body specific operations (like yield and yield_to). Internally reference counting is still used to manage coroutine lifetime when future are used. While this can still lead to cycles if a coroutine stores the result of coro::make_callback() in a local, this is explicitly prohibited in the interface, and should look suspiciously wrong in code.

Futures were made movable for similar reasons.

No current_coroutine

Boost.Coroutine provides no way to retrieve a reference to the current coroutine. This is first of all for reasons of type safety. Every coroutine is typed on its signature, so would be current pointer. The user of an hypothetical current_coroutine would need to pass to this function, as a template parameter, the signature of the coroutine that should be extracted. This signature would be checked at run time with the signature of the current coroutine. Given that current_coroutine would be most useful in generic code, the signature would need to be passed down to the to the function that need to access the current coroutine. At this point there is little benefit on passing only the signature instead of a reference to self.

The second reason is that current_coroutine is a global object in disguise. Global objects lead often to non scalable code. During the development of the library and during testing, is has always been possible to do away with the need for such a global by exploring other solutions. The Win32 fiber API provides a symmetric coroutine interface with such a global object. Coding around the interface mismatch between the Boost.Coroutine API and the fiber API has been difficult and a potential source of inefficiency.

The last reason for not providing a current_coroutine is that this could be used to yield. Suppose a coroutine that is manipulating some shared data calls a seemingly innocuous function; this coroutine might invoke current_coroutine().yield(), thus relinquishing control and leaving the shared state with an invalid invariant. Functions that may cause a coroutine to yield should documented as such. With the current interface, these functions need a reference to self. Passing such a reference is a strong hint that the function might yield.

Main context is not a coroutine

The main context is the flow of control outside of any coroutine body. It is the flow of control started by main() or from the startup of any threads. Some coroutine APIs treat the main context itself as a coroutine. Such libraries usually provide symmetric coroutines, and treating main() as a coroutine is the only way to return to the main context. Boost.Coroutine is mostly designed around asymmetric coroutines, so a normal yield() can be used to return to the main context.

Treating main() as a coroutine also opens many problems:

Symmetric and asymmetric coroutines

It has been argued [Moura04] that asymmetric coroutines are the best coroutine abstraction, because are simpler and safer than symmetric coroutines, while having the same expressiveness. We agree with that and the library has been developed around an asymmetric design.

During development was apparent that symmetric functionality could be added without compromising the API, thus yield_to was implemented. While yield_to shouldn't be abused, it might simplify some scenarios. It might also be a performance optimization.

"Premature optimization is the root of all evil" -- C. A. R. Hoare.
While working on the Boost.Asio integration, the author thought that the only way to get good dispatching performance would be to use a specialized scheduler that used yield_to to go from coroutine to coroutine. In the end the performance of invoke/yield + invoke/yield was so close to that of invoke/yield_to/yield that the need of a separate scheduler disappeared greatly simplifying performance as an asio::io_service works perfectly as a scheduler.

Asynchronous functions are not wrapped

Most cooperative threading libraries (for example the Pth library) deal with blocking behavior by wrapping asynchronous call behind a synchronous interface in the belief that asynchronous calls are a source of problems. Your author instead believes that are not the asynchronous calls themselves that complicate code, but the need to divide related code into multiple independent callback functions. Thus Boost.Coroutine doesn't try to hide the powerful Boost.Asio asynchronous interface behind a synchronous one, but simply helps dealing with the control inversion complication caused by the proliferation of small callbacks.

In fact _coroutines_ are not meant to be the silver bullet. Sometimes separated callbacks (maybe even defined in line with the help of Boost.Bind or Boost.Lambda) might be the right solution. One can even mix both styles together and use the best tool for each specific job.

Multi-argument wait

It follows from the previous point that Boost.Coroutine is not a generalized asynchronous framework. Do not confuse wait as a general purpose demultiplexer. The ability to wait for multiple futures is provided to simplify some scenarios, like performing an operation while waiting for a timer to expire, or reading and writing from two different pipes. A coroutine that routinely waits for more that two or three futures, should probably refactored in multiple coroutines.

Copyright © 2006 Giovanni P. Deretta

PrevUpHomeNext