Skip to content
David Dight edited this page Sep 25, 2023 · 91 revisions

1. Fiber construction, destruction

ctor

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.

  1. 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 (like std::invoke). Arguments will be perfectly forwarded to the fiber.
  2. Construct fiber a with a fiber_params object and a given callable object that is not a std::bind expression. This allows you to pass the callable object and any arguments directly (like std::invoke). Arguments will be perfectly forwarded to the fiber.
  3. Construct fiber a with a callable object that takes no parameters or is a std::bind expression.
  4. Construct fiber a with a fiber_params object and a given callable object that takes no parameters or is a std::bind expression.
  5. Construct fiber from another fiber with an rvalue reference (move). The other fiber is no longer valid after the move.
  6. Construct fiber from a fiber_base (std::shared_ptr<fiber_base>).

All of fiber is within the namespace FIX8.

1.1 Using fiber_params

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>()[,...]}

1.2 Using f8_stack

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 };

stack_type::heap

This is the default. The stack is allocated on the heap, aligned on a 256 (0xff) byte boundary.

stack_type::mapped

The stack is allocated from an anonymous private memory mapped region. The memory is default page aligned.

stack_type::placement

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.

dtor

~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.

fiber_error

This exception class is used for all fiber exceptions

struct fiber_error : std::system_error
{
   using std::system_error::system_error;
};

jfiber ctor

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.

  1. 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.
  2. Construct jfiber a with a fiber_params object and a 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.
  3. Construct jfiber a with a callable object that takes no parameters or is a std::bind expression.
  4. Construct jfiber a with a fiber_params object and a given callable object that takes no parameters or is a std::bind expression.

fiber_id

Represents a unique id for each fiber. The fiber id is a 64 bit value. fiber_id is used by fiber

operator<<

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.

to_string

template<typename charT, class traitsT>
std::string fiber_id::to_string();

Populates a std::string with the same value as produced by operator<<.

std::hash<FIX8::fiber_id>

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;
};

2. Fiber operations

join

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.

join_if

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.

detach

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).

suspend

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).

unsuspend

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).

kill

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).

schedule

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.

resume

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).

resume_if

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.

resume_with

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.

schedule_if

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.

move

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.

reset

void fiber::reset();

Resets the underlying fiber_base shared pointer (_ctx).

reset

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.

3. Fiber accessors

joinable

bool fiber::joinable();

Returns true if this fiber is joinable. In this context, joinable means not finished.

operator bool

operator bool();

Returns true if this fiber is joinable. In this context, joinable means not finished.

operator!

operator! ();

Returns false if this fiber is joinable. In this context, joinable means not finished.

is_main

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.

is_detached

bool fiber::is_detached();

Returns true if this fiber is detached.

is_suspended

bool fiber::is_suspended();

Returns true if this fiber is suspended.

is_joinonexit

bool fiber::is_joinonexit();

Returns true if this fiber has the joinonexit flag set.

is_dispatched

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.

is_transferring

bool fiber::is_transferring();

Returns true if this fiber is currently being moved. Moving fibers are asynchronously picked up by other threads.

is_moved

bool fiber::is_moved();

Returns true if this fiber has been moved. The target thread may reset this flag.

get_id

fiber_id fiber::get_id();

Returns a unique id representing this fiber. The fiber id is a 64 bit value.

get_pid

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.

get_prev_id

fiber_id fiber::get_prev_id();

Returns a unique id representing the previously executing fiber. The fiber id is a 64 bit value.

get_ctxswtchs

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.

get_ctx

fiber_base_ptr fiber::get_ctx();

Returns the fiber_base_ptr of the the fiber. This is a std::shared_ptr.

4. Fiber mutators

set_params

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.

name

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.

set_joinonexit

fiber& fiber::set_joinonexit(bool set=false);

Set the joinonexit flag on or off. Returns a reference to the current object.

order

int fiber::order(int ord=99);

Get/set the launch order. Return the current or new launch order.

5. Namespace this_fiber

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.

yield

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).

schedule_main

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.

resume_main

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.

name

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.

get_id

fiber_id this_fiber::get_id();

Returns a unique id representing this fiber. The fiber id is a 64 bit value.

get_prev_id

fiber_id this_fiber::get_prev_id();

Returns a unique id representing the previously executing fiber. The fiber id is a 64 bit value.

get_pid

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.

sleep_until

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.

sleep_for

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.

move

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.

6. Namespace fibers

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.

kill_all

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.

sort

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.

const_get_vars

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

const_get_all_cvars

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

size

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.

size_accurate

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.

size_detached

int fibers::size_detached();

Return the number of detached fibers in the current thread.

size_finished

int fibers::size_finished();

Return the number of finished fibers in the current thread.

has_fibers

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.

has_finished

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.

wait_all

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.

wait_all with predicate

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.

wait_any

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.

wait_any with predicate

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.

terminating

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.

move (fiber)

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.

move (fiber ptr)

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.

set_flag

void fibers::set_flag(global_fiber_flags flag);

Set the specified flag. These are as follows:

Global flags

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

reset_flag

void fibers::reset_flag(global_fiber_flags flag);

Clear the specified flag.

has_flag

bool fibers::has_flag(global_fiber_flags flag);

Returns true if the specified flag is set.

print

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:

Print columns

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.

Fiber flags

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)

7. Helper functions

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.

async

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.

launch_all and launch_all_with_params

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.

launch_all_n and launch_all_with_params_n

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:

  1. The first parameter must be a reference to a container of fiber that supports emplace_back() and begin(). So std::list, std::vector, std::deque and so forth are ok.
  2. The created fibers are not detached. They are added to the supplied container.
  3. 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.

make_fiber

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.

  1. Construct fiber with the given arguments
  2. Construct fiber with a fiber_params object and the given arguments

8. Exception handling

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.

Termination handler

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:

  1. 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;
  2. Set global_fiber_flags::termthrow which will throw a std::logic_error instead of calling std::terminate; if you wish to also catch this exception you must set global_fiber_flags::excepthandling and handle as described above;
  3. Call fiber::kill() or fibers::kill_all() from the parent function before exiting.