-
Notifications
You must be signed in to change notification settings - Fork 2
API
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...> && (!std::is_bind_expression_v<Fn>)
constexpr fiber(Fn&& func, Args&&... args); (1)
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...> && (!std::is_bind_expression_v<Fn>)
constexpr fiber(fiber_params&& params, Fn&& func, Args&&... args); (2)
template<std::invocable Fn>
constexpr fiber(Fn&& func); (3)
template<std::invocable Fn>
constexpr fiber(fiber_params&& params, Fn&& func); (4)
fiber(fiber&& other); (5)
fiber(fiber_base_ptr from); (6)
Create a fiber
from a callable object with optional fiber_params
. By default the fiber does not execute immediately unless the launch policy is specified in the fiber_params
object as launch::dispatch
.
- Construct fiber a with given callable object that is not a
std::bind
expression. This allows you to pass the callable object and any arguments directly (likestd::invoke
). Arguments will be perfectly forwarded to the fiber. - Construct fiber a with a
fiber_params
object and a given callable object that is not astd::bind
expression. This allows you to pass the callable object and any arguments directly (likestd::invoke
). Arguments will be perfectly forwarded to the fiber. - Construct fiber a with a callable object that takes no parameters or is a
std::bind
expression. - Construct fiber a with a
fiber_params
object and a given callable object that takes no parameters or is astd::bind
expression. - Construct fiber from another fiber with an rvalue reference (move). The other fiber is no longer valid after the move.
- Construct fiber from a
fiber_base
(std::shared_ptr<fiber_base>
).
All of fiber
is within the namespace FIX8
.
This simple object can be used to pass optional parameters to the fiber
constructor. You can use designated initialisers to selectively set the values (as long as they are in the order given below).
struct fiber_params
{
const char name[FIX8_FIBER_FIBERNAMELEN]{};
int launch_order{99};
bool join{};
// these next values can't be modified once the fiber has been created
const size_t stacksz{FIX8_FIBER_DEFSTKSZ};
const launch policy{launch::post};
f8_stack_ptr stack{make_stack<stack_type::heap>()};
};
Member | Type | Description | Default | Example |
---|---|---|---|---|
name |
char[] |
the name for this fiber; must be a constexpr; max length is FIX8_FIBER_FIBERNAMELEN
|
(empty) |
{.name="first"[,...]} , {"first"} ,... |
launch_order |
int |
where there is more than one active fiber, the launch order (0=first) | 99 |
{.launch_order=1[,...]} |
join |
bool |
when true, the fiber will join if joinable on destruction | false |
{.join=true[,...]} |
stacksz |
size_t |
size in bytes of the stack to allocate; depending on type should be a 16 byte or page aligned value |
FIX8_FIBER_DEFSTKSZ , 131072 |
{.stacksz=65535[,...]} |
policy |
launch::policy |
if launch::dispatch fiber will launch on construction; default launches after first yield depending on order |
launch::post |
{.policy=launch::dispatch[,...]} |
stack |
f8_stack_ptr |
An f8_stack object (std::unique_ptr<f8_stack> ); see f8_stack below |
stack_type::heap |
{.stack=make_stack<stack_type::mapped>()[,...]} |
By default, fiber
will use a heap based stack (stack_type::heap
) of the default stacksz
. You can specify alternative stack types (currently heap, mapped or placement).
Alternatively you can create you own stack class by deriving from f8_stack
.
enum class stack_type { heap, mapped, placement };
This is the default. The stack is allocated on the heap, aligned on a 256
(0xff
) byte boundary.
The stack is allocated from an anonymous private memory mapped region. The memory is default page aligned.
The stack is allocated from a user supplied pointer to memory. The user is responsible for allocating and/or destroying this memory. This stack class takes a pointer to your memory and an optional offset as parameters:
constexpr f8_fixedsize_placement_stack::f8_fixedsize_placement_stack(char *top, size_t offs=0);
You can use the supplied fiber::make_stack
to create your stack pointer. The first version takes the stack_type
enumeration and any additional parameters:
template<stack_type type, typename... Args>
constexpr f8_stack_ptr make_stack(Args&&... args);
fiber f1 { {.name="sub09",.stacksz=8192,.stack=make_stack<stack_type::mapped>()}, &func, 3 };
The second version takes a f8_stack
derived class and any additional parameters:
template<typename T=f8_fixedsize_heap_stack, typename... Args>
requires std::derived_from<T, f8_stack>
constexpr f8_stack_ptr make_stack(Args&&... args);
Here is an example of a custom stack:
class my_stack : public f8_stack
{
public:
char *allocate(std::size_t size) override
{
if (!_ptr)
{
static char stk[_size = size & ~0xff];
_ptr = stk;
}
return _ptr;
}
void deallocate() noexcept override
{
f8_stack::deallocate();
}
};
fiber f1 { {.name="sub09",.stacksz=8192,.stack=make_stack<my_stack>()}, &func, 3 };
See fibertest19.cpp
for an example using different stack types.
~fiber();
Destroy the fiber, releasing any resources (including the stack if allocated). Note that if the fiber is joinable and is not in a detached state, std::terminate
will be called. This is the
same behaviour as in std::thread
. If the fiber flag joinonexit
has been set (see fiber::set_joinonexit()
), fiber::join()
will be called before destruction.
This exception class is used for all fiber exceptions
struct fiber_error : std::system_error
{
using std::system_error::system_error;
};
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...> && (!std::is_bind_expression_v<Fn>)
constexpr jfiber(Fn&& func, Args&&... args); (1)
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...> && (!std::is_bind_expression_v<Fn>)
constexpr jfiber(fiber_params&& params, Fn&& func, Args&&... args); (2)
template<std::invocable Fn>
constexpr jfiber(Fn&& func); (3)
template<std::invocable Fn>
constexpr jfiber(fiber_params&& params, Fn&& func); (4)
Create a jfiber
from a callable object with optional fiber_params
. A jfiber
differs from a fiber
in that it automatically joins on destruction.
Note that by default the fiber does not execute immediately unless the launch policy is specified in the fiber_params
object as launch::dispatch
.
All of jfiber
is within the namespace FIX8
.
- Construct jfiber a with given callable object that is not a
std::bind
expression. This allows you to pass the callable object and any arguments directly (std::invoke
). Arguments will be perfectly forwarded to the jfiber. - Construct jfiber a with a
fiber_params
object and a given callable object that is not astd::bind
expression. This allows you to pass the callable object and any arguments directly (std::invoke
). Arguments will be perfectly forwarded to the jfiber. - Construct jfiber a with a callable object that takes no parameters or is a
std::bind
expression. - Construct jfiber a with a
fiber_params
object and a given callable object that takes no parameters or is astd::bind
expression.
Represents a unique id for each fiber. The fiber id is a 64 bit value. fiber_id
is used by fiber
template<typename charT, class traitsT>
std::basic_ostream<charT, traitsT>& operator<<(std::basic_ostream<charT, traitsT>& os, const fiber_id& what);
Prints a human readable value to the given ostream
. An invalid fiber_id
prints NaF
(Not a Fiber). To make the value more readable it abbreviated by mod 10000
.
template<typename charT, class traitsT>
std::string fiber_id::to_string();
Populates a std::string
with the same value as produced by operator<<
.
Allowed specialisation of std::hash
for fiber_id
template<>
struct std::hash<FIX8::fiber_id>
{
std::size_t operator()(const FIX8::fiber_id& id) const noexcept;
};
void fiber::join();
Suspend the current fiber and wait until this fiber has completed. If this fiber is not joinable()
or the current fiber is the same fiber as the joining fiber
then an exception is thrown. Unlike a thread, the current fiber is not actually suspended, but rather yields to any other fibers.
When the current fiber is re-scheduled, it will recheck this fiber to join;
throws fiber_error(std::errc::invalid_argument)
and fiber_error(std::errc::resource_deadlock_would_occur)
.
A jfiber
will automatically join on destruction.
void fiber::join_if();
Same as join()
but will check if this fiber is joinable()
. If not, will just return rather than throw an exception.
void fiber::detach();
Detach this fiber. The fiber will no longer be accessible or identifiable from its fiber_id
. Detached fibers are still scheduled to run. When the current thread terminates,
all undetached fibers are run until completion. Detached fibers are not destroyed until thread termination.
If this fiber is not joinable()
or the fiber is already is_detached()
or is_dispatched()
an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
.
void fiber::suspend();
Suspend the current fiber. The current fiber is not actually suspended, but rather yields to any other fibers.
If this fiber is not joinable()
or the fiber is already is_detached()
or is_suspended()
an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
.
void fiber::unsuspend();
Unsuspend the current fiber. If this fiber is not joinable()
or the fiber is already is_detached()
or isn't suspended an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
.
void fiber::kill();
Marks the fiber as finished. The fiber will no longer be scheduled and will be removed shortly. If you want the fiber to be retained
you can set the global fiber flag global_fiber_flags::retain
with fibers::set_flag(global_fiber_flags::retain)
. The fiber will be removed when the thread terminates.
If this fiber is not joinable()
or the fiber is is_detached()
an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
.
bool fiber::schedule(bool check=true);
Schedule the fiber to run next meaning this fiber will be promoted in the round-robin queue to be the next scheduled fiber to run.
The defaulted argument check
causes the method to check for joinability and detachment.
If the fiber is not joinable()
or the current fiber is the same fiber as the scheduled fiber or the fiber is is_detached()
an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
and fiber_error(std::errc::resource_deadlock_would_occur)
. If the fiber was successfully
scheduled, true is returned. For the scheduled fiber to run next, the current fiber must still yield or finish.
void fiber::resume(bool check=true);
Resume the fiber. This has the same effect as if the fiber was promoted in the round-robin queue to be the next scheduled fiber to run. The default argument
check
causes the method to check for joinability and detachment.
If the fiber is not joinable()
or the current fiber is the same fiber as the resuming fiber or the fiber is is_detached()
an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
and fiber_error(std::errc::resource_deadlock_would_occur)
.
void fiber::resume_if();
Same as resume()
but will check if the fiber is joinable()
. If not, will just return rather than throw an exception.
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...> && (!std::is_bind_expression_v<Fn>)
void resume_with(Fn&& func, Args&&... args);
template<std::invocable Fn>
void resume_with(Fn&& func);
Resume the fiber but replace the callable object with the object provided. The new callable object has the same signature as the ctor (1) and (3) above.
Any parameters that were given to the original fiber remain. This method provides a compatible continuation
method.
The original function stack is completely overwritten by the new resumed function. See fibertest8.cpp
for an example using resume_with
.
bool fiber::schedule_if();
Same as schedule()
but will check if this fiber is joinable()
. If not, will just return false rather than throw an exception.
void fiber::move(std::thread::id id);
Attempts to move the current fiber from std::this_thread
to the thread with the given std::thread::id
.
If the target thread is not joinable or not running, or the current fiber is not active, or the target and source threads are the same, an exception is thrown. After a move, the framework will call this_fiber::yield()
.
The moved fiber is not guaranteed to run immediately - or at all as this will depend on the number of other fibers running in the target thread and when (if at all) the target thread's currently running fiber yields. If the target thread does not run the moved fiber, and the fiber has not finished, an exception will be thrown when the thread is destroyed.
If an exception is thrown in either thread by a fiber, it should be caught (fiber_error
or std::system_error
). To ensure safe termination, your application
should call fibers::kill_all()
in the exception handler.
void fiber::reset();
Resets the underlying fiber_base
shared pointer (_ctx
).
template<typename Y, typename Deleter>
void reset(Y* ptr, Deleter d);
Resets the underlying fiber_base
(_ctx
) shared pointer with the supplied pointer and deleter.
bool fiber::joinable();
Returns true
if this fiber is joinable. In this context, joinable means not finished.
operator bool();
Returns true
if this fiber is joinable. In this context, joinable means not finished.
operator! ();
Returns false
if this fiber is joinable. In this context, joinable means not finished.
bool fiber::is_main();
Returns true
if this fiber is the main fiber. The main fiber is the parent thread that created the first fiber in the current thread.
It is immutable, cannot be killed, detached, suspended or renamed.
bool fiber::is_detached();
Returns true
if this fiber is detached.
bool fiber::is_suspended();
Returns true
if this fiber is suspended.
bool fiber::is_joinonexit();
Returns true
if this fiber has the joinonexit
flag set.
bool fiber::is_dispatched();
Returns true
if this fiber has the dispatched
flag set. The dispatched flag mean the fiber will execute as soon as it is constructed.
bool fiber::is_transferring();
Returns true
if this fiber is currently being moved
. Moving fibers are asynchronously picked up by other threads.
bool fiber::is_moved();
Returns true
if this fiber has been moved
. The target thread may reset this flag.
fiber_id fiber::get_id();
Returns a unique id representing this fiber. The fiber id is a 64 bit value.
fiber_id fiber::get_pid();
Returns a unique id representing the parent fiber. The fiber id is a 64 bit value. If the parent fiber is main
, the fiber id returned is the equivalent of a nullptr
.
fiber_id fiber::get_prev_id();
Returns a unique id representing the previously executing fiber. The fiber id is a 64 bit value.
int fiber::get_ctxswtchs();
Returns the number of context switches (resumes) this fiber has executed. For our purposes, a context switch means a fiber switch.
fiber_base_ptr fiber::get_ctx();
Returns the fiber_base_ptr
of the the fiber. This is a std::shared_ptr
.
fiber& fiber::set_params(const std::string_view nm, int lo=99, bool jn=false);
Optionally set the fiber name, launch order and joinonexit flag. These parameters can be set anytime during the life of a fiber. Returns a reference to the current object.
std::string_view fiber::name(std::string_view what);
std::string_view fiber::name();
Get and/or optionally set the fiber name. The string will be truncated if it is longer than FIX8_FIBER_FIBERNAMELEN
. The maximum length of FIX8_FIBER_FIBERNAMELEN
includes
a trailing null
.
fiber& fiber::set_joinonexit(bool set=false);
Set the joinonexit flag on or off. Returns a reference to the current object.
int fiber::order(int ord=99);
Get/set the launch order. Return the current or new launch order.
The this_fiber
namespace is defined within the FIX8
namespace and provides static access to a number of functions that operate or refer to the current fiber within the context that is has been called.
void this_fiber::yield();
Suspend execution of the current fiber and yield to the next available runnable fiber. By default the order in which fibers are executed is the order they were created in. This order can be changed by
specifying the launch_order
in the constructor or by using one of the mutator methods above. Fibers that are suspended or not joinable (finished) are not considered when choosing the next fiber to run.
When you create a fiber, it is not executed until you either yield
or resume
(except for fibers created with launch::dispatch
).
void this_fiber::schedule_main();
Schedule main fiber to run next. Main is promoted in the round-robin queue to be the next scheduled fiber to run.
If main is not joinable()
or the current fiber is the same fiber as the main fiber an exception is thrown,
throws fiber_error(std::errc::invalid_argument)
and fiber_error(std::errc::resource_deadlock_would_occur)
.
For main to run next, the current fiber must yield.
void this_fiber::resume_main();
Suspend execution of the current fiber and yield to the main
fiber. If the current fiber is the same fiber as main an exception is thrown.
If global_fiber_flags::skipmain
is set, it will be reset before switching to main.
std::string_view fiber::name(std::string_view what);
std::string_view fiber::name();
Get and/or optionally set the fiber name. The string will be truncated if it is longer than FIX8_FIBER_FIBERNAMELEN
. The maximum length of FIX8_FIBER_FIBERNAMELEN
includes
a trailing null
.
fiber_id this_fiber::get_id();
Returns a unique id representing this fiber. The fiber id is a 64 bit value.
fiber_id this_fiber::get_prev_id();
Returns a unique id representing the previously executing fiber. The fiber id is a 64 bit value.
fiber_id this_fiber::get_pid();
Returns a unique id representing the parent fiber of this fiber. The fiber id is a 64 bit value. If the parent fiber is main
, the fiber id returned is the equivalent of a nullptr
.
template<typename Clock, typename Duration>
void this_fiber::sleep_until(const std::chrono::time_point<Clock, Duration>& sltime);
Suspend the current fiber until the specifed time_point
is reached.
The current fiber is not actually suspended, but rather yields to any other fibers.
If this fiber is not joinable()
or the fiber is already is_detached()
or is_suspended()
an exception is thrown.
template<typename Rep, typename Period>
void this_fiber::sleep_for(const std::chrono::duration<Rep, Period>& retime);
Suspend the current fiber for the specifed duration
. The current fiber is not actually suspended, but rather yields to any other fibers.
If this fiber is not joinable()
or the fiber is already is_detached()
or is_suspended()
an exception is thrown.
void this_fiber::move(std::thread::id id);
Attempts to move the current fiber from std::this_thread
to the thread with the given std::thread::id
.
If the target thread is not joinable or not running, or the current fiber is not active, or the target and source threads are the same, an exception is thrown. After a move, the framework will call this_fiber::yield()
.
The moved fiber is not guaranteed to run immediately - or at all as this will depend on the number of other fibers running in the target thread and when (if at all) the
target thread yields. If the target thread does not run the moved fiber, and the fiber has not finished, an exception will be thrown when the thread is destroyed.
If an exception is thrown in either thread by a fiber, it should be caught (fiber_error
or std::system_error
). To ensure safe termination, your application
should call fibers::kill_all()
in the exception handler.
The fibers
namespace is defined within the FIX8
namespace and provides static access to a number of functions that operate on or refer to all fibers within the current thread.
int fibers::kill_all();
Mark all runnable (schedulable) fibers as finished (non-joinable). This excludes main
and detached fibers. The number of killed fibers is returned. Any killed fiber will be marked for deletion.
If an exception is thrown by a fiber (rather than within a fiber), it should be caught (fiber_error
or std::system_error
). To ensure safe termination, your application
should call fibers::kill_all()
in the exception handler if no other recovery strategy is made.
void fibers::sort();
Performs a sort on the scheduler queue. Fibers are sorted by launch_order
. Normally not required as this queue is sorted whenever a fiber is added.
struct cvars
{
std::set<fiber_base_ptr> _uniq;
std::deque<fiber_base_ptr> _sched;
fiber_base_ptr _curr;
const fiber_base_ptr _main;
bool _term;
std::chrono::time_point<std::chrono::system_clock> _now;
std::bitset<static_cast<int>(global_fiber_flags::count)> _gflags;
int _finished;
std::exception_ptr _eptr;
.
.
.
};
const cvars& fibers::const_get_vars();
Obtain a const&
to the internal context variable structure. This structure is a per-thread singleton and contains containers and flags used to manage all fibers within a thread. The members are as follows:
Member | Type | Description |
---|---|---|
_uniq |
std::set<fiber_base_ptr> |
is a unique set of all fibers in this thread; sorted by fiber_id
|
_sched |
std::dequeue<fiber_base_ptr> |
is a queue of all runnable fibers in this thread; the next scheduled fiber is at the top position |
_curr |
fiber_base_ptr |
is a pointer to the currently running fiber |
_main |
const fiber_base_ptr |
is a pointer to the main fiber |
_term |
bool |
when true, the fiber manager is terminating |
_now |
std::chrono::time_point |
the current time (used by wait_until and wait_for ) |
_gflags |
std::bitset<global_fiber_flags::count> |
a set of system-wide flags |
_finished |
int |
count of fibers that have finished |
_eptr |
std::exception_ptr |
exception ptr used to recover exceptions |
class all_cvars final
{
mutable f8_spin_lock _tvs_spl;
std::unordered_map<std::thread::id, cvars *> _tvs;
std::unordered_multimap<std::thread::id, fiber_base_ptr> _trf;
.
.
.
};
const all_cvars& const_get_all_cvars() noexcept;
Obtain a const&
to the internal all context variable structure. This structure is a per-process singleton and contains pointers to all cvars
for each thread in the process. The members are as follows:
Member | Type | Description |
---|---|---|
_tvs_spl |
f8_spin_lock |
spin lock used to concurrently manage the containers |
_tvs |
std::unordered_map<std::thread::id, cvars *> |
is a map of thread ids to cvars |
_trf |
std::unordered_multimap<std::thread::id, fiber_base_ptr> |
is a multimap of fibers waiting to be inserted into the given thread |
int fibers::size();
Return the number of runnable (joinable) fibers in the current thread (excluding detached, suspended fibers and the current fiber).
The value returned may not be up-to-date until the next this_fiber::yield
has been called.
int fibers::size_accurate();
Return the number of runnable (joinable) fibers in the current thread (excluding detached, suspended fibers and the current fiber). The value is guaranteed be up-to-date.
int fibers::size_detached();
Return the number of detached fibers in the current thread.
int fibers::size_finished();
Return the number of finished fibers in the current thread.
bool fibers::has_fibers();
Returns true if there are runnable (joinable) fibers in the current thread (excluding detached, suspended fibers and the current fiber). The value returned may not be up-to-date
until the next this_fiber::yield
has been called.
bool fibers::has_finished();
Returns true if there are any finished fibers in the current thread. The value returned may not be up-to-date
until the next this_fiber::yield
has been called.
void fibers::wait_all();
Wait for all active fibers to finish before returning. In this context waiting means yielding to other active fibers. This is effectively the same as:
while(fibers::has_fibers())
this_fiber::yield();
You can use this method in main
(recommended) or in any fiber however you may get unexpected results if you use it in a fiber.
wait_all
assumes that all the fibers in the current thread are candidates for waiting. Do not use this method if you have other uninvolved fibers as well.
template<typename Fn>
requires std::invocable<Fn>
void fibers::wait_all(Fn func);
Wait for all active fibers to finish before returning. In this context waiting means yielding to other active fibers.
On each loop, the predicate function func
is tested. If true, wait_all
exits. The invocable
function should have or be convertible to
a callable object with the signature bool func()
. This is effectively the same as:
while(fibers::has_fibers() && !func())
this_fiber::yield();
You can use this method in main
(recommended) or in any fiber however you may get unexpected results if you use it in a fiber.
wait_all
assumes that all the fibers in the current thread are candidates for waiting. Do not use this method if you have other uninvolved fibers as well.
void fibers::wait_any();
Wait for any active fibers to finish. Any fiber that finishes will cause wait_any
to return. In this context waiting means yielding to other active fibers.
This is effectively the same as:
while(!fibers::has_finished())
this_fiber::yield();
You can use this method in main
(recommended) or in any fiber however you may get unexpected results if you use it in a fiber. wait_any
assumes that all the fibers
in the current thread are candidates for waiting. Do not use this method if you have other uninvolved fibers as well.
template<typename Fn>
requires std::invocable<Fn>
void fibers::wait_any(Fn func);
Wait for any active fibers to finish. Any fiber that finishes will cause wait_any
to return. In this context waiting means yielding to other active fibers.
On each loop, the predicate function func
is tested. If true, wait_any
exits. The invocable
function should have or be convertible to
a callable object with the signature bool func()
. This is effectively the same as:
while(!fibers::has_finished() && !func())
this_fiber::yield();
You can use this method in main
(recommended) or in any fiber however you may get unexpected results if you use it in a fiber.
wait_any
assumes that all the fibers in the current thread are candidates for waiting. Do not use this method if you have other uninvolved fibers as well.
bool fibers::terminating();
Returns true if the current thread is terminating. This is triggered when the static thread_local
fiber manager goes out of scope. If you have active fibers in a thread (or in main)
and the fiber goes out of scope, the fiber manager will wake up any suspended or detached fibers and will also join any jfibers or fibers with joinonexit
set.
This method allows a fiber to test for this condition.
void fibers::move(std::thread::id id, const fiber& fb);
Attempts to move the specifed fiber from std::this_thread
to the thread with the given std::thread::id
.
If the target thread is not joinable or not running, or the current fiber is not active, or the target and source threads are the same, an exception is thrown. After a move, the framework will call this_fiber::yield()
.
The moved fiber is not guaranteed to run immediately - or at all as this will depend on the number of other fibers running in the target thread and when (if at all) the
target thread yields. If the target thread does not run the moved fiber, and the fiber has not finished, an exception will be thrown when the thread is destroyed.
If an exception is thrown in either thread by a fiber, it should be caught (fiber_error
or std::system_error
). To ensure safe termination, your application
should call fibers::kill_all()
in the exception handler.
void fibers::move(std::thread::id id, fiber_base_ptr ptr);
Attempts to move the specifed fiber (as a fiber_base_ptr
) from std::this_thread
to the thread with the given std::thread::id
.
If the target thread is not joinable or not running, or the current fiber is not active, or the target and source threads are the same, an exception is thrown. After a move, the framework will call this_fiber::yield()
.
The moved fiber is not guaranteed to run immediately - or at all as this will depend on the number of other fibers running in the target thread and when (if at all) the
target thread yields. If the target thread does not run the moved fiber, and the fiber has not finished, an exception will be thrown when the thread is destroyed.
If an exception is thrown in either thread by a fiber, it should be caught (fiber_error
or std::system_error
). To ensure safe termination, your application
should call fibers::kill_all()
in the exception handler.
void fibers::set_flag(global_fiber_flags flag);
Set the specified flag
. These are as follows:
flag | Default | Description |
---|---|---|
retain |
false |
when set, retain fiber record after it finishes; these are available for intrumentation purposes only and cannot be joined |
fairshare |
false |
this is a reserved flag; a future fairshare scheduler may be implemented |
hidedetached |
false |
when set, the built-in printer will hide detached fibers |
skipmain |
false |
when set, the fiber scheduler will not switch to main while other fibers are available |
termthrow |
false |
when set, abnormal fiber termination (via fiber_exit ) will throw a std::logic_error instead of terminating (std::terminate ) |
excepthandling |
false |
when set, all exceptions thrown within the fiber func are caught and transfered to the cvar _eptr
|
void fibers::reset_flag(global_fiber_flags flag);
Clear the specified flag
.
bool fibers::has_flag(global_fiber_flags flag);
Returns true
if the specified flag
is set.
void fibers::print(std::ostream& os=std::cout);
Print all schedulable fibers including the current fiber to the specified ostream
. If the current process has more than one thread containing fibers
this function will also print the std::thread::id
of the current thread.
Every thread also has a main
fiber which is marked with an m
flag and is given the name main
.
The main fiber is the parent thread that created the first fiber. It is immutable, cannot be killed, detached, suspended or renamed. The reported
stacksz of main is the stack size of the current thread. Since the main thread's stack is managed by the OS, the fiber manager cannot track
stack depth and the depth will always be shown as 0.
The following is an example output:
Thread id 140467943503552
# fid pfid prev ctxsw stack ptr stack alloc depth stacksz flags ord name
0 * 5696 NaF 9856 13 0x556bab3d9cd8 0x556bab3d7eb0 464 8192 _________ 99 sub08
1 5392 NaF 5696 12 0x7f4d0134be28 0x7f4d0134a000 464 8192 _________ 99 sub12
2 NaF NaF 5392 13 0x7ffd3c31b538 0 0 8388608 m________ 99 main
3 9856 NaF NaF 13 0x556bab3e2338 0x556bab3e0420 224 8192 _f_______ 99 sub04
The columns are as follows:
Name | Description |
---|---|
# |
The position of the fiber in the scheduler queue. 0 is the current (active) fiber, also marked with an *
|
fid |
The unique fiber_id of the fiber. NaF for main means Not a Fiber
|
pfid |
The unique parent fiber_id of the fiber. NaF for main
|
prev |
The previous running fiber_id
|
ctxsw |
The number of context switches this fiber has been scheduled to run |
stack ptr |
The address of the current stack pointer (esp ) |
stack alloc |
The address of the allocated stack (0 for main ) |
depth |
The depth in bytes of this fiber's stack (stack used) |
stacksz |
The total size in bytes of the initial available stack |
flags |
A representation of the internal fiber flags (see below) |
ord |
The launch_order of this fiber; defaults to 99
|
name |
The optional name of the fiber |
The built-in monitor is also available to examine fibers.
Flag | Description |
---|---|
m |
This is the main fiber |
M |
This is the main fiber with global_fiber_flags::skipmain set |
f |
This fiber has finished (non-joinable) |
s |
This fiber is suspended |
p |
This fiber is dispatched |
d |
This fiber is detached (see hidedetached flag above) |
n |
This fiber hasn't started |
j |
This fiber will join on exit |
t |
This fiber is being transferred (moved) |
v |
This fiber has been transferred (moved) |
Within the FIX8
namespace a number of helper functions are available. Some provide the necessary glue so that fiber
will work with std::future, std::promise, std::packaged_task
.
Others help with launching multiple fibers.
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...>
std::future<std::invoke_result_t<std::decay_t<Fn>, std::decay_t<Args>...>>
constexpr async(fiber_params&& params, Fn&& func, Args... args);
template<typename Fn, typename... Args>
requires std::invocable<Fn, Args...>
std::future<std::invoke_result_t<std::decay_t<Fn>, std::decay_t<Args>...>>
constexpr async(Fn&& func, Args... args);
The function template async runs the function fn asynchronously (either in a separate fiber or in the current fiber) and returns a std::future
that will eventually hold the result of that function call.
This function is designed to be used instead of std::async
when using fiber
.
The first version runs the function with arguments. The second version runs the function with a fiber_params
object and arguments.
In this implementation async
launches its fiber immediately (launch policy is set to launch::dispatch
). See fiber_params
above.
See fibertest4.cpp
for an example using async.
template<std::invocable... Fns>
constexpr void launch_all(Fns&& ...funcs);
template<typename Ps, std::invocable Fn, typename... Fns>
constexpr void launch_all_with_params(Ps&& params, Fn&& func, Fns&& ...funcs);
Launch the provided fibers. The second version allows you to provide a fiber_params
object for each fiber.
See fibertest12.cpp
for an example using launch_all
and launch_all_with_params
. Use std::bind
to bind any arguments.
Both these templates detach every fiber they create.
template <typename C>
concept container_emplace_back = requires(C& c)
{
c.emplace_back();
c.begin();
};
template<container_emplace_back C, std::invocable... Fns>
constexpr void launch_all_n(C& c, Fns&& ...funcs);
template<container_emplace_back C, typename Ps, std::invocable Fn, typename... Fns>
constexpr void launch_all_with_params_n(C& c, Ps&& params, Fn&& func, Fns&& ...funcs);
As with launch_all
and launch_all_with_params
, launch the provided fibers. They differ in the following way:
- The first parameter must be a reference to a container of
fiber
that supportsemplace_back()
andbegin()
. Sostd::list
,std::vector
,std::deque
and so forth are ok. - The created fibers are not detached. They are added to the supplied container.
- After all the fibers have been created, control switches to the first fiber in the container, via
resume()
.
Both versions launches each fiber. The second version allows you to provide a fiber_params
object for each fiber.
Use std::bind
to bind any arguments. _n
means no detach.
See fibertest27.cpp
for an example using launch_all_n
and launch_all_with_params_n
.
This example also demonstrates the use of fibers::wait_all()
and global_fiber_flags::skipmain
which instructs the fiber scheduler to skip switching to main while other fibers are active.
template<typename T=fiber, typename... Args>
requires std::derived_from<T, fiber>
constexpr fiber_ptr make_fiber(Args&&... args); (1)
template<typename T=fiber, typename... Args>
requires std::derived_from<T, fiber>
constexpr fiber_ptr make_fiber(fiber_params&& params, Args&&... args); (2)
Constructs an object of type T and wraps it in a fiber_ptr
(std::unique_ptr<fiber>
). The object type T
is either a fiber
or a class drived from fiber
, allowing
you to use this template to construct your own fiber
derived objects.
- Construct fiber with the given arguments
- Construct fiber with a
fiber_params
object and the given arguments
By default any exception thrown within a fiber will not be caught. It is not possible to catch these exceptions from the calling (parent) function (e.g main). To handle these exceptions, set the global flag as follows:
fibers::set_flag(global_fiber_flags::excepthandling);
This will cause any exception to be copied to the fiber cvar std::exception_ptr _eptr
. Your calling function then needs to test for the presence of an exception and then use
fibers::get_exception_ptr()
to recover the exception. To handle the exception you must then rethrow
it, as follows:
try
{
if (fibers::get_exception_ptr())
std::rethrow_exception(fibers::get_exception_ptr());
}
catch(const std::exception& e)
{
std::cerr << "rethrown: " << e.what() << '\n';
}
Note only user exceptions thrown within a fiber are handled by this mechanism. Exceptions thrown by fiber
will not be caught. These exceptions can be handled normally by the calling
function.
By default when a fiber exits abnormally it will call std::terminate
. This is because it has no pathway to return to the calling function. This is usually the result of a calling application
exiting before any fibers it created having not finished and exited. Specifically if a fiber goes out of scope that has not finished, it will call std::terminate
.
There are 3 ways to deal with situation programmatically. Use one of the following:
- Signal to the fiber that it should exit (you can use a global flag, pass a reference to a local flag, or use a cancellation token); you must yield to the fiber in order for it to process this signal;
- Set
global_fiber_flags::termthrow
which will throw astd::logic_error
instead of callingstd::terminate
; if you wish to also catch this exception you must setglobal_fiber_flags::excepthandling
and handle as described above; - Call
fiber::kill()
orfibers::kill_all()
from the parent function before exiting.