Home | Libraries | People | FAQ | More |
Note | |
---|---|
This class is enabled per default. |
Class execution_context encapsulates context switching and manages the associated context' stack (allocation/deallocation).
execution_context allocates the context stack (using its
StackAllocator argument)
and creates a control structure on top of it. This structure is responsible
for managing context' stack. The address of the control structure is stored
in the first frame of context' stack (e.g. it can not directly accessed from
within execution_context). In contrast to execution_context
(v1) the ownership of the control structure is not shared (no member
variable to control structure in execution_context).
execution_context keeps internally a state that is moved
by a call of execution_context::operator() (*this
will be
invalidated), e.g. after a calling execution_context::operator(),
*this
can not be used for an additional context switch.
execution_context is only move-constructible and move-assignable.
The moved state is assigned to a new instance of execution_context. This object becomes the first argument of the context-function, if the context was resumed the first time, or the first element in a tuple returned by execution_context::operator() that has been called in the resumed context. In contrast to execution_context (v1), the context switch is faster because no global pointer etc. is involved.
Important | |
---|---|
Segmented stacks are not supported by execution_context (v2). |
On return the context-function of the current context has to specify an execution_context to which the execution cotnrol is transferred after termination of the current context.
If an instance with valid state goes out of scope and the context-function has not yet returned, the stack is traversed in order to access the control structure (address stored at the first stack frame) and to deallocate context' stack via the StackAllocator. The stack walking makes the destruction of execution_context very slow and should be prevented if possible.
execution_context expects a function/functor with signature
execution_context(execution_context ctx, void*
vp)
.
The parameter ctx
represents
the context from which this context was resumed (e.g. that has called execution_context::operator()
on *this
)
and vp
is the data passed to
execution_context::operator(). The return value is the
execution_context that has to be resumed, while this context terminates.
int n=35; ctx::execution_context source( [n](ctx::execution_context sink,void*)mutable{ int a=0; int b=1; while(n-->0){ auto result=sink(&a); sink=std::move(std::get<0>(result)); auto next=a+b; a=b; b=next; } return sink; }); for(int i=0;i<10;++i){ auto result=source(); source=std::move(std::get<0>(result)); std::cout<<*(int*)std::get<1>(result)<<" "; } output: 0 1 1 2 3 5 8 13 21 34
This simple example demonstrates the basic usage of execution_context.
The context sink
represents
the main-context (function main()
running). sink
is generated
by the framework (first element of lambda's parameter list). Because the state
is invalidated (== changed) by each call of execution_context::operator(),
sink
has to be assigned the
new state of the execution_context returned by execution_context::operator().
The lambda that calculates the Fibonacci numbers is executed inside the context
represented by source
. Calculated
Fibonacci numbers are transferred between the two context' via expression
sink(&a) (and returned by source()).
The locale variables a
, b
and next
remain their values during each context switch (yield(a)).
This is possible due ctx
has
its own stack and the stack is exchanged by each context switch.
/* * grammar: * P ---> E '\0' * E ---> T {('+'|'-') T} * T ---> S {('*'|'/') S} * S ---> digit | '(' E ')' */ class Parser{ // implementation omitted; see examples directory }; std::istringstream is("1+1"); bool done=false; std::exception_ptr except; // execute parser in new execution context boost::context::execution_context source( [&is,&done,&except](ctx::execution_context sink,void*){ // create parser with callback function Parser p( is, [&sink](char ch){ // resume main execution context auto result = sink(&ch); sink = std::move(std::get<0>(result)); }); try { // start recursive parsing p.run(); } catch (...) { // store other exceptions in exception-pointer except = std::current_exception(); } // set termination flag done=true; // resume main execution context return sink; }); // user-code pulls parsed data from parser // invert control flow auto result = source(); source = std::move(std::get<0>(result)); void * vp = std::get<1>(result); if ( except) { std::rethrow_exception(except); } while( ! done) { printf("Parsed: %c\n",* static_cast<char*>(vp)); std::tie(source,vp) = source(); if (except) { std::rethrow_exception(except); } } output: Parsed: 1 Parsed: + Parsed: 1
In this example a recursive descent parser uses a callback to emit a newly passed symbol. Using execution_context the control flow can be inverted, e.g. the user-code pulls parsed symbols from the parser - instead to get pushed from the parser (via callback).
The data (character) is transferred between the two execution_context.
If the code executed by execution_context emits an exception, the application is terminated. std::exception_ptr can be used to transfer exceptions between different execution contexts.
Sometimes it is necessary to unwind the stack of an unfinished context to destroy local stack variables so they can release allocated resources (RAII pattern). The user is responsible for this task.
Allocating control structures on top of the stack requires to allocated the stack_context and create the control structure with placement new before execution_context is created.
Note | |
---|---|
The user is responsible for destructing the control structure at the top of the stack. |
// stack-allocator used for (de-)allocating stack fixedsize_stack salloc( 4048); // allocate stack space stack_context sctx( salloc.allocate() ); // reserve space for control structure on top of the stack void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); std::size_t size = sctx.size - sizeof( my_control_structure); // placement new creates control structure on reserved space my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); ... // destructing the control structure cs->~my_control_structure(); ... struct my_control_structure { // captured context execution_context cctx; template< typename StackAllocator > my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : // create captured context cctx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { } ... };
If the function executed inside a execution_context emits ans exception, the application is terminated by calling std::terminate(). std::exception_ptr can be used to transfer exceptions between different execution contexts.
The void pointer argument passed to execution_context::operator(), in one context, is passed as the last argument of the context-function if the context is started for the first time. In all following invocations of execution_context::operator() the void pointer passed to execution_context::operator(), in one context, is returned by execution_context::operator() in the other context.
class X { private: std::exception_ptr excptr_; boost::context::execution_context ctx_; public: X() : excptr_(), ctx_([=](ctx::execution_context ctx, void* vp)mutable{ try { for (;;) { int i = * static_cast<int*>(vp); std::string str = boost::lexical_cast<std::string>(i); auto result = ctx(& str); ctx = std::move(std::get<0>(result) ); vp = std::get<1>(result); } } catch (ctx::detail::forced_unwind const&) { throw; } catch (...) { excptr_=std::current_exception(); } return ctx; }) {} std::string operator()(int i) { auto result = ctx_(&i); ctx_ = std::move(std::get<0>(result)); void * ret = std::get<1>(result); if(excptr_){ std::rethrow_exception(excptr_); } return * static_cast<std::string*>(ret); } }; X x; std::cout << x( 7) << std::endl; output: 7
execution_context
class execution_context { public: template< typename Fn, typename ... Args > execution_context( Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args); template< typename Fn, typename ... Args > execution_context( std::allocator_arg_t, segemented_stack, Fn && fn, Args && ... args) = delete; template< typename Fn, typename ... Args > execution_context( std::allocator_arg_t, preallocated palloc, segmented, Fn && fn, Args && ... args)= delete; ~execution_context(); execution_context( execution_context && other) noexcept; execution_context & operator=( execution_context && other) noexcept; execution_context( execution_context const& other) noexcept = delete; execution_context & operator=( execution_context const& other) noexcept = delete; explicit operator bool() const noexcept; bool operator!() const noexcept; std::tuple< execution_context, void * > operator()( void * data = nullptr); template< typename Fn, typename ... Args > std::tuple< execution_context, void * > operator()( exec_ontop_arg_t, Fn && fn, Args && ... args); template< typename Fn, typename ... Args > std::tuple< execution_context, void * > operator()( void * data, exec_ontop_arg_t, Fn && fn, Args && ... args); bool operator==( execution_context const& other) const noexcept; bool operator!=( execution_context const& other) const noexcept; bool operator<( execution_context const& other) const noexcept; bool operator>( execution_context const& other) const noexcept; bool operator<=( execution_context const& other) const noexcept; bool operator>=( execution_context const& other) const noexcept; template< typename charT, class traitsT > friend std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); };
template< typename Fn, typename ... Args > execution_context( Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Args && ... args); template< typename StackAlloc, typename Fn, typename ... Args > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Args && ... args);
Creates a new execution context and prepares the context to execute
fn
. fixedsize_stack
is used as default stack allocator (stack size == fixedsize_stack::traits::default_size()).
The constructor with argument type preallocated
,
is used to create a user defined data (for
instance additional control structures) on top of the stack.
execution_context( execution_context && other) noexcept;
Moves underlying capture record to *this
.
Nothing.
execution_context & operator=( execution_context && other) noexcept;
Moves the state of other
to *this
using move semantics.
Nothing.
operator bool
()
explicit operator bool() const noexcept;
true
if *this
points to a capture record.
Nothing.
operator!
()
bool operator!() const noexcept;
true
if *this
does not point to a capture record.
Nothing.
operator()
()
std::tuple< execution_context, void * > operator()( void * data = nullptr);
Stores internally the current context data (stack pointer, instruction
pointer, and CPU registers) of the current active context and restores
the context data from *this
, which implies jumping to *this
's
context. The void pointer argument, vp
,
is passed to the current context to be returned by the most recent call
to execution_context::operator()
in the same thread. fn
is executed with arguments args
on top of the stack of this
.
The behaviour is undefined if operator()()
is called while execution_context::current()
returns *this
(e.g. resuming an already running
context). If the top-level context function returns, std::exit()
is called.
The tuple of void pointer argument passed to the most recent call to
execution_context::operator()
,
if any and a execution_context representing the context that has been
suspended .
operator()
()
template< typename Fn, typename ... Args > std::tuple< execution_context, void * > operator()( exec_ontop_arg_t, Fn && fn, Args && ... args); template< typename Fn, typename ... Args > std::tuple< execution_context, void * > operator()( void * data, exec_ontop_arg_t, Fn && fn, Args && ... args);
Same as execution_context::operator(). Additionally,
function fn
is executed
with arguments args
in
the context of *this
(e.g. the stack frame of fn
is allocated on stack of *this
).
The tuple of void pointer argument passed to the most recent call to
execution_context::operator()
,
if any and a execution_context representing the context that has been
suspended .
operator==
()
bool operator==( execution_context const& other) const noexcept;
true
if *this
and other
represent the same execution context, false
otherwise.
Nothing.
operator!=
()
bool operator!=( execution_context const& other) const noexcept;
! (other == * this)
Nothing.
operator<
()
bool operator<( execution_context const& other) const noexcept;
true
if *this != other
is true and the implementation-defined
total order of execution_context
values places *this
before other
, false otherwise.
Nothing.
operator>
()
bool operator>( execution_context const& other) const noexcept;
other <
* this
Nothing.
operator<=
()
bool operator<=( execution_context const& other) const noexcept;
! (other <
* this)
Nothing.
operator>=
()
bool operator>=( execution_context const& other) const noexcept;
! (*
this <
other)
Nothing.
operator<<()
template< typename charT, class traitsT > std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
Writes the representation of other
to stream os
.
os