1
0
Fork 0
arangodb/3rdParty/boost/1.62.0/libs/context/doc/execution_context_v2.qbk

665 lines
25 KiB
Plaintext

[/
Copyright Oliver Kowalke 2014.
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
]
[#ecv2]
[section:ecv2 Class execution_context (version 2)]
[note This class is enabled per default.]
Class __econtext__ encapsulates context switching and manages the associated
context' stack (allocation/deallocation).
__econtext__ allocates the context stack (using its [link stack
__stack_allocator__] 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 __econtext__). In contrast to __ecv1__ the
ownership of the control structure is not shared (no member variable to control
structure in __econtext__). __econtext__ keeps internally a state that is moved
by a call of __ec_op__ (`*this` will be invalidated), e.g. after a calling
__ec_op__, `*this` can not be used for an additional context switch.
__econtext__ is only move-constructible and move-assignable.
The moved state is assigned to a new instance of __econtext__. 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 __ec_op__ that has
been called in the resumed context.
In contrast to __ecv1__, the context switch is faster because no global pointer
etc. is involved.
[important Segmented stacks are not supported by __econtext__ (v2).]
On return the context-function of the current context has to specify an
__econtext__ to which the execution control 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 context' stack is
deallocated via the __stack_allocator__. The stack walking makes the destruction
of __econtext__ slow and should be prevented if possible.
__econtext__ expects a __context_fn__ with signature
`execution_context(execution_context ctx, Args ... args)`. The parameter `ctx`
represents the context from which this context was resumed (e.g. that has called
__ec_op__ on `*this`) and `args` are the data passed to __ec_op__. The return
value represents the execution_context that has to be resumed, after termiantion
of this context.
Benefits of __ecv2__ over __ecv1__ are: faster context switch, type-safety of
passed/returned arguments.
[heading usage of __econtext__]
int n=35;
ctx::execution_context<int> source(
[n](ctx::execution_context<int> sink, int) 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(i);
source=std::move(std::get<0>(result));
std::cout<<std::get<1>(result)<<" ";
}
output:
0 1 1 2 3 5 8 13 21 34
This simple example demonstrates the basic usage of __econtext__ as a generator.
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 __ec_op__, the new
state of the __econtext__, returned by __ec_op__, needs to be assigned to `sink`
after each call.
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()]). Note that this example represents a ['generator] thus the value
transferred into the lambda via ['source()] is not used. Using
['boost::optional<>] as transferred type, might also appropriate to express this
fact.
The locale variables `a`, `b` and ` next` remain their values during each
context switch (['yield(a)]). This is possible due `source` has its own stack
and the stack is exchanged by each context switch.
[heading parameter passing]
With `execution_context<void>` no data will be transferred, only the context
switch is executed.
boost::context::execution_context<void> ctx1([](boost::context::execution_context<void> ctx2){
std::printf("inside ctx1\n");
return ctx2();
});
ctx1();
output:
inside ctx1
`ctx1()` resumes `ctx1`, e.g. the lambda passed at the constructor of `ctx1` is
entered. Argument `ctx2` represents the context that has been suspended with the
invocation of `ctx1()`. When the lambda returns `ctx2`, context `ctx1` will be
terminated while the context represented by `ctx2` is resumed, hence the control
of execution returns from `ctx1()`.
The arguments passed to __ec_op__, in one context, is passed as the last
arguments of the __context_fn__ if the context is started for the first time.
In all following invocations of __ec_op__ the arguments passed to __ec_op__, in
one context, is returned by __ec_op__ in the other context.
boost::context::execution_context<int> ctx1([](boost::context::execution_context<int> ctx2, int j){
std::printf("inside ctx1, j == %d\n", j);
return ctx2(j+1);
});
int i = 1;
std::tie(ctx1, i) = ctx1(i);
std::printf("i == %d\n", i);
output:
inside ctx1, j == 1
i == 2
`ctx1(i)` enters the lambda in context `ctx1` with argument `j=1`. The
expression `ctx2(j+1)` resumes the context represented by `ctx2` and transfers
back an integer of `j+1`. On return of `ctx1(i)`, the variable `i` contains the
value of `j+1`.
If more than one argument has to be transferred, the signature of the
context-function is simply extended.
boost::context::execution_context<int,int> ctx1([](boost::context::execution_context<int,int> ctx2, int i, int j){
std::printf("inside ctx1, i == %d j == %d\n", i, j);
return ctx2(i+j,i-j);
});
int i = 2, j = 1;
std::tie(ctx1, i, j) = ctx1(i,j);
std::printf("i == %d j == %d\n", i, j);
output:
inside ctx1, i == 2 j == 1
i == 3 j == 1
For use-cases, that require to transfer data of different type in each
direction, ['boost::variant<>] could be used.
class X{
private:
std::exception_ptr excptr_;
boost::context::execution_context<boost::variant<int,std::string>> ctx_;
public:
X():
excptr_(),
ctx_(
[=](boost::context::execution_context<boost::variant<int,std::string>> ctx, boost::variant<int,std::string> data){
try {
for (;;) {
int i = boost::get<int>(data);
data = boost::lexical_cast<std::string>(i);
auto result = ctx( data);
ctx = std::move( std::get<0>( result) );
data = std::get<1>( result);
} catch (std::bad_cast const&) {
excptr_=std::current_exception();
}
return ctx;
})
{}
std::string operator()(int i){
boost::variant<int,std::string> data = i;
auto result = ctx_( data);
ctx_ = std::move( std::get<0>( result) );
data = std::get<1>( result);
if(excptr_){
std::rethrow_exception(excptr_);
}
return boost::get<std::string>(data);
}
};
X x;
std::cout << x( 7) << std::endl;
output:
7
In the case of unidirectional transfer of data, ['boost::optional<>] or a
pointer are appropriate.
[heading exception handling]
If the function executed inside a __econtext__ emits ans exception, the
application is terminated by calling ['std::terminate()]. ['std::exception_ptr]
can be used to transfer exceptions between different execution contexts.
[important Do not jump from inside a catch block and then re-throw the exception
in another execution context.]
[#ecv2_ontop]
[heading Executing function on top of a context]
Sometimes it is useful to execute a new function on top of a resumed context.
For this purpose __ec_op__ with first argument `exec_ontop_arg` has to be used.
The function passed as argument must return a tuple of execution_context and
arguments.
boost::context::execution_context<int> f1(boost::context::execution_context<int> ctx,int data) {
std::cout << "f1: entered first time: " << data << std::endl;
std::tie(ctx,data) = ctx(data+1);
std::cout << "f1: entered second time: " << data << std::endl;
std::tie(ctx,data) = ctx(data+1);
std::cout << "f1: entered third time: " << data << std::endl;
return ctx;
}
std::tuple<boost::context::execution_context<int>,int> f2(boost::context::execution_context<int> ctx,int data) {
std::cout << "f2: entered: " << data << std::endl;
return std::make_tuple(std::move(ctx),-1);
}
int data = 0;
ctx::execution_context< int > ctx(f1);
std::tie(ctx,data) = ctx(data+1);
std::cout << "f1: returned first time: " << data << std::endl;
std::tie(ctx,data) = ctx(data+1);
std::cout << "f1: returned second time: " << data << std::endl;
std::tie(ctx,data) = ctx(ctx::exec_ontop_arg,f2,data+1);
output:
f1: entered first time: 1
f1: returned first time: 2
f1: entered second time: 3
f1: returned second time: 4
f2: entered: 5
f1: entered third time: -1
The expression `ctx(ctx::exec_ontop_arg,f2,data+1)` executes `f2()` on top of
context `ctx`, e.g. an additional stack frame is allocated on top of the context
stack (in front of `f1()`). `f2()` returns argument `-1` that will returned by
the second invocation of `ctx(data+1)` in `f1()`.
Another option is to execute a function on top of the context that throws an
exception.
struct interrupt {
boost::context::execution_context< void > ctx;
interrupt( boost::context::execution_context< void > && ctx_) :
ctx( std::forward< boost::context::execution_context< void > >( ctx_) ) {
}
};
boost::context::execution_context<void> f1(boost::context::execution_context<void> ctx) {
try {
for (;;) {
std::cout << "f1()" << std::endl;
ctx = ctx();
}
} catch (interrupt & e) {
std::cout << "f1(): interrupted" << std::endl;
ctx = std::move( e.ctx);
}
return ctx;
}
boost::context::execution_context<void> f2(boost::context::execution_context<void> ctx) {
throw interrupt(std::move(ctx));
return ctx;
}
boost::context::execution_context< void > ctx(f1);
ctx = ctx();
ctx = ctx();
ctx = ctx(boost::context::exec_ontop_arg,f2);
output:
f1()
f1()
f1(): interrupted
In this example `f2()` is used to interrupt the `for`-loop in `f1()`.
[heading stack unwinding]
On construction of __econtext__ a stack is allocated.
If the __context_fn__ returns the stack will be destructed.
If the __context_fn__ has not yet returned and the destructor of an valid
__econtext__ instance (e.g. ['execution_context::operator bool()] returns
`true`) is called, the stack will be destructed too.
[important Code executed by __context_fn__ must not prevent the propagation of the
__forced_unwind__ exception. Absorbing that exception will cause stack
unwinding to fail. Thus, any code that catches all exceptions must re-throw any
pending __forced_unwind__ exception.]
[#ecv2_prealloc]
[heading allocating control structures on top of stack]
Allocating control structures on top of the stack requires to allocated the
__stack_context__ and create the control structure with placement new before
__econtext__ 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) {
}
...
};
[heading inverting the control flow]
/*
* 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<char> source(
[&is,&done,&except](ctx::execution_context<char> sink,char){
// 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('\0');
source = std::move(std::get<0>(result));
char c = std::get<1>(result);
if ( except) {
std::rethrow_exception(except);
}
while( ! done) {
printf("Parsed: %c\n",c);
std::tie(source,c) = source('\0');
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 __econtext__ 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 __econtext__.
If the code executed by __econtext__ 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.
[heading Class `execution_context`]
struct exec_ontop_arg_t {};
const exec_ontop_arg_t exec_ontop_arg{};
template< typename ... Args >
class execution_context {
public:
template< typename Fn, typename ... Params >
execution_context( Fn && fn, Params && ... params);
template< typename StackAlloc, typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params);
template< typename StackAlloc, typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params);
template< typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, segemented_stack, Fn && fn, Params && ... params) = delete;
template< typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, preallocated palloc, segmented, Fn && fn, Params && ... params)= 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, Args ... > operator()( Args ... args);
template< typename Fn >
std::tuple< execution_context, Args ... > operator()( 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);
};
[constructor_heading ecv2..constructor]
template< typename Fn, typename ... Params >
execution_context( Fn && fn, Params && ... params);
template< typename StackAlloc, typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params);
template< typename StackAlloc, typename Fn, typename ... Params >
execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params);
[variablelist
[[Effects:] [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 [link ecv2_prealloc (for instance additional control structures)] on
top of the stack.]]
]
[destructor_heading ecv2..destructor destructor]
~execution_context();
[variablelist
[[Effects:] [Destructs the associated stack if `*this` is a valid context,
e.g. ['execution_context::operator bool()] returns `true`.]]
[[Throws:] [Nothing.]]
]
[move_constructor_heading ecv2..move constructor]
execution_context( execution_context && other) noexcept;
[variablelist
[[Effects:] [Moves underlying capture record to `*this`.]]
[[Throws:] [Nothing.]]
]
[move_assignment_heading ecv2..move assignment]
execution_context & operator=( execution_context && other) noexcept;
[variablelist
[[Effects:] [Moves the state of `other` to `*this` using move semantics.]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_bool..operator bool]
explicit operator bool() const noexcept;
[variablelist
[[Returns:] [`true` if `*this` points to a capture record.]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_not..operator!]
bool operator!() const noexcept;
[variablelist
[[Returns:] [`true` if `*this` does not point to a capture record.]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_call..operator()]
std::tuple< execution_context< Args ... >, Args ... > operator()( Args ... args); // member of generic execution_context template
execution_context< void > operator()(); // member of execution_context< void >
[variablelist
[[Effects:] [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 arguments, `... args`, are passed to the current context to be returned
by the most recent call to `execution_context::operator()` in the same thread.]]
[[Returns:] [The tuple of execution_context and returned arguments passed to the
most recent call to `execution_context::operator()`, if any and a
execution_context representing the context that has been suspended.]]
[[Note:] [The returned execution_context indicates if the suspended context has
terminated (return from context-function) via `bool operator()`. If the returned
execution_context has terminated no data are transferred in the returned tuple.]]
]
[operator_heading ecv2..operator_call_ontop..operator()]
template< typename Fn >
std::tuple< execution_context< Args ... >, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); // member of generic execution_context
template< typename Fn >
execution_context< void > operator()( exec_ontop_arg_t, Fn && fn); // member of execution_context< void >
[variablelist
[[Effects:] [Same as __ec_op__. Additionally, function `fn` is executed
in the context of `*this` (e.g. the stack frame of `fn` is allocated on
stack of `*this`).]]
[[Returns:] [The tuple of execution_context and returned arguments passed to the
most recent call to `execution_context::operator()`, if any and a
execution_context representing the context that has been suspended .]]
[[Note:] [The tuple of execution_context and returned arguments from `fn` are
passed as arguments to the context-function of resumed context (if the context
is entered the first time) or those arguments are returned from
`execution_context::operator()` within the resumed context.]]
[[Note:] [Function `fn` needs to return a tuple of execution_context and
arguments ([link ecv2_ontop see description]).]]
[[Note:] [The context calling this function must not be destroyed before the
arguments, that will be returned from `fn`, are preserved at least in the stack
frame of the resumed context.]]
[[Note:] [The returned execution_context indicates if the suspended context has
terminated (return from context-function) via `bool operator()`. If the returned
execution_context has terminated no data are transferred in the returned tuple.]]
]
[operator_heading ecv2..operator_equal..operator==]
bool operator==( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [`true` if `*this` and `other` represent the same execution context,
`false` otherwise.]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_notequal..operator!=]
bool operator!=( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [[`! (other == * this)]]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_less..operator<]
bool operator<( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [`true` if `*this != other` is true and the
implementation-defined total order of `execution_context` values places `*this`
before `other`, false otherwise.]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_greater..operator>]
bool operator>( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [`other < * this`]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_lesseq..operator<=]
bool operator<=( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [`! (other < * this)`]]
[[Throws:] [Nothing.]]
]
[operator_heading ecv2..operator_greatereq..operator>=]
bool operator>=( execution_context const& other) const noexcept;
[variablelist
[[Returns:] [`! (* this < other)`]]
[[Throws:] [Nothing.]]
]
[hding ecv2_..Non-member function [`operator<<()]]
template< typename charT, class traitsT >
std::basic_ostream< charT, traitsT > &
operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
[variablelist
[[Efects:] [Writes the representation of `other` to stream `os`.]]
[[Returns:] [`os`]]
]
[endsect]