mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-25 19:27:33 +00:00
134 lines
4.8 KiB
C++
134 lines
4.8 KiB
C++
//
|
|
// coroutine.h
|
|
// ~~~~~~~~~~~
|
|
//
|
|
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
|
|
#ifndef COROUTINE_HPP
|
|
#define COROUTINE_HPP
|
|
|
|
|
|
// \brief Coroutine object
|
|
//
|
|
// A coroutine object maintains the state of a re-enterable routine. It
|
|
// is assignable and copy-constructable, and can be used as a base class
|
|
// for a class that uses it, or as a data member. The copy overhead is
|
|
// a single int.
|
|
//
|
|
// A reenterable function contains a CORO_REENTER (coroutine) { ... }
|
|
// block. Whenever an asychrnonous operation is initiated within the
|
|
// routine, the function is provided as the handler object. (The simplest
|
|
// way to do this is to have the reenterable function be the operator()
|
|
// member for the coroutine object itself.) For example:
|
|
//
|
|
// CORO_YIELD socket->async_read_some(buffer, *this);
|
|
//
|
|
// The CORO_YIELD keyword updates the current status of the coroutine to
|
|
// indicate the line number currently being executed. The
|
|
// async_read_some() call is initiated, with a copy of the updated
|
|
// corotutine as its handler object, and the current coroutine exits. When
|
|
// the async_read_some() call finishes, the copied coroutine will be
|
|
// called, and will resume processing exactly where the original one left
|
|
// off--right after asynchronous call. This allows asynchronous I/O
|
|
// routines to be written with a logical flow, step following step, rather
|
|
// than as a linked chain of separate handler functions.
|
|
//
|
|
// When necessary, a coroutine can fork itself using the CORO_FORK keyword.
|
|
// This updates the status of the coroutine and makes a copy. The copy can
|
|
// then be called directly or posted to the ASIO service queue so that both
|
|
// coroutines will continue forward, one "parent" and one "child". The
|
|
// is_parent() and is_child() methods indicate which is which.
|
|
//
|
|
// The CORO_REENTER, CORO_YIELD and CORO_FORK keywords are implemented
|
|
// via preprocessor macros. The CORO_REENTER block is actually a large,
|
|
// complex switch statement. Because of this, inline variable declaration
|
|
// is impossible within CORO_REENTER unless it is done in a subsidiary
|
|
// scope--and if it is, that scope cannot contain CORO_YIELD or CORO_FORK
|
|
// keywords.
|
|
//
|
|
// Because coroutines are frequently copied, it is best to minimize copy
|
|
// overhead by limiting the size of data members in derived classes.
|
|
//
|
|
// It should be noted that when a coroutine falls out of scope its memory
|
|
// is reclaimed, even though it may be scheduled to resume when an
|
|
// asynchronous operation completes. Any shared_ptr<> objects declared in
|
|
// the coroutine may be destroyed if their reference count drops to zero,
|
|
// in which case the coroutine will have serious problems once it resumes.
|
|
// One solution so this is to have the space that will be used by a
|
|
// coroutine pre-allocated and stored on a free list; a new coroutine can
|
|
// fetch the block of space off a free list, place a shared pointer to it
|
|
// on an "in use" list, and carry on. The reference in the "in use" list
|
|
// would prevent the data from being destroyed.
|
|
class coroutine
|
|
{
|
|
public:
|
|
coroutine() : value_(0) {}
|
|
virtual ~coroutine() {}
|
|
bool is_child() const { return value_ < 0; }
|
|
bool is_parent() const { return !is_child(); }
|
|
bool is_complete() const { return value_ == -1; }
|
|
int get_value() const { return value_; }
|
|
private:
|
|
friend class coroutine_ref;
|
|
int value_;
|
|
};
|
|
|
|
class coroutine_ref
|
|
{
|
|
public:
|
|
coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {}
|
|
coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {}
|
|
~coroutine_ref() { if (!modified_) value_ = -1; }
|
|
operator int() const { return value_; }
|
|
int& operator=(int v) { modified_ = true; return value_ = v; }
|
|
private:
|
|
void operator=(const coroutine_ref&);
|
|
int& value_;
|
|
bool modified_;
|
|
};
|
|
|
|
#define CORO_REENTER(c) \
|
|
switch (coroutine_ref _coro_value = c) \
|
|
case -1: if (_coro_value) \
|
|
{ \
|
|
goto terminate_coroutine; \
|
|
terminate_coroutine: \
|
|
_coro_value = -1; \
|
|
goto bail_out_of_coroutine; \
|
|
bail_out_of_coroutine: \
|
|
break; \
|
|
} \
|
|
else case 0:
|
|
|
|
#define CORO_YIELD \
|
|
for (_coro_value = __LINE__;;) \
|
|
if (_coro_value == 0) \
|
|
{ \
|
|
case __LINE__: ; \
|
|
break; \
|
|
} \
|
|
else \
|
|
switch (_coro_value ? 0 : 1) \
|
|
for (;;) \
|
|
case -1: if (_coro_value) \
|
|
goto terminate_coroutine; \
|
|
else for (;;) \
|
|
case 1: if (_coro_value) \
|
|
goto bail_out_of_coroutine; \
|
|
else case 0:
|
|
|
|
#define CORO_FORK \
|
|
for (_coro_value = -__LINE__;; _coro_value = __LINE__) \
|
|
if (_coro_value == __LINE__) \
|
|
{ \
|
|
case -__LINE__: ; \
|
|
break; \
|
|
} \
|
|
else
|
|
#endif // COROUTINE_HPP
|
|
|