diff --git a/doc/condition_variables.qbk b/doc/condition_variables.qbk index 7030453f..fd89d6ec 100644 --- a/doc/condition_variables.qbk +++ b/doc/condition_variables.qbk @@ -18,7 +18,7 @@ class condition_variable; class condition_variable_any; -The class `condition_variable` provides a mechanism for one fiber to wait for +The class `condition_variable` provides a mechanism for a fiber to wait for notification on `condition_variable`. When the fiber awakens from the wait, then it checks to see if the appropriate condition is now true, and continues if so. If the condition is not true, then the fiber calls `wait` again to resume @@ -26,7 +26,7 @@ waiting. In the simplest case, this condition is just a boolean variable: boost::fibers::condition_variable cond; boost::fibers::mutex mtx; - bool data_ready; + bool data_ready = false; void process_data(); @@ -107,16 +107,22 @@ optimize as described for `boost::thread::condition_variable`. void wait( LockType & lk, Pred predicate); template< typename LockType, typename Clock, typename Duration > - cv_status wait_until( LockType & lk, std::chrono::time_point< Clock, Duration > const& timeout_time); + cv_status wait_until( LockType & lk, + std::chrono::time_point< Clock, Duration > const& timeout_time); template< typename LockType, typename Clock, typename Duration, typename Pred > - bool wait_until( LockType & lk, std::chrono::time_point< Clock, Duration > const& timeout_time, Pred pred); + bool wait_until( LockType & lk, + std::chrono::time_point< Clock, Duration > const& timeout_time, + Pred pred); template< typename LockType, typename Rep, typename Period > - cv_status wait_for( LockType & lk, std::chrono::duration< Rep, Period > const& timeout_duration); + cv_status wait_for( LockType & lk, + std::chrono::duration< Rep, Period > const& timeout_duration); template< typename LockType, typename Rep, typename Period, typename Pred > - bool wait_for( LockType & lk, std::chrono::duration< Rep, Period > const& timeout_duration, Pred pred); + bool wait_for( LockType & lk, + std::chrono::duration< Rep, Period > const& timeout_duration, + Pred pred); }; typedef condition_variable condition_variable_any; @@ -204,36 +210,39 @@ fibers calling `wait` on `*this` must wait on `lk` objects governing the ['same] `mutex`. Three distinct objects are involved in any `condition_variable::wait()` call: the `condition_variable` itself, the `mutex` coordinating access between fibers and a lock object (e.g. -`mutex::scoped_lock`). In some sense it would be nice if the +`std::lock_guard`). In some sense it would be nice if the `condition_variable`'s constructor could accept the related `mutex` object, enforcing agreement across all `wait()` calls; but the existing APIs prevent that. Instead we must require the `wait()` call to accept a reference to the local lock object. It is an error to reuse a given `condition_variable` instance with lock objects that reference ['different] underlying `mutex` -objects. It would be like a road intersection with traffic lights disconnected -from one another: sooner or later a collision will result.]] +objects. It would be like a road intersection with traffic lights independent +of one another: sooner or later a collision will result.]] ] [template_member_heading condition_variable..wait_until] template< typename LockType, typename Clock, typename Duration > - cv_status wait_until( LockType & lk, std::chrono::time_point< Clock, Duration > const& timeout_time); + cv_status wait_until( LockType & lk, + std::chrono::time_point< Clock, Duration > const& timeout_time); template< typename LockType, typename Clock, typename Duration, typename Pred > - bool wait_until( LockType & lk, std::chrono::time_point< Clock, Duration > const& timeout_time, Pred pred); + bool wait_until( LockType & lk, + std::chrono::time_point< Clock, Duration > const& timeout_time, + Pred pred); [variablelist [[Precondition:] [`lk` is locked by the current fiber, and either no other fiber is currently waiting on `*this`, or the execution of the `mutex()` member function on the `lk` objects supplied in the calls to `wait`, `wait_for` or `wait_until` in all the fibers currently waiting on `*this` would return the -same value as `lk->mutex()` for this call to `wait`.]] +same value as `lk.mutex()` for this call to `wait_until`.]] [[Effects:] [Atomically call `lk.unlock()` and blocks the current fiber. The fiber will unblock when notified by a call to `this->notify_one()` or -`this->notify_all()`, when the time as reported by `clock_type::now()` +`this->notify_all()`, when the system time would be equal to or later than the specified `timeout_time`, or spuriously. When the fiber is unblocked (for whatever reason), the lock is reacquired by -invoking `lk.lock()` before the call to `wait` returns. The lock is also +invoking `lk.lock()` before the call to `wait_until` returns. The lock is also reacquired by invoking `lk.lock()` if the function exits with an exception. The member function accepting `pred` is shorthand for: `` @@ -253,27 +262,30 @@ __interrupt__ on the __fiber__ object associated with the current fiber of execution.]] [[Returns:] [The overload without `pred` returns `cv_status::no_timeout` if awakened by `notify_one()` or `notify_all()`, or `cv_status::timeout` if -awakened because `clock_type::now()` is past `timeout_time`.]] +awakened because the system time is past `timeout_time`.]] [[Returns:] [The overload accepting `pred` returns `false` if the call is returning because the time specified by `timeout_time` was reached and the predicate returns `false`, `true` otherwise.]] -[[Note:] [See [*Note] for `wait()`.]] +[[Note:] [See [*Note] for [member_link condition_variable..wait].]] ] [template_member_heading condition_variable..wait_for] template< typename LockType, typename Rep, typename Period > - cv_status wait_for( LockType & lk, std::chrono::duration< Rep, Period > const& timeout_duration); + cv_status wait_for( LockType & lk, + std::chrono::duration< Rep, Period > const& timeout_duration); template< typename LockType, typename Rep, typename Period, typename Pred > - bool wait_for( LockType & lk, std::chrono::duration< Rep, Period > const& timeout_duration, Pred pred); + bool wait_for( LockType & lk, + std::chrono::duration< Rep, Period > const& timeout_duration, + Pred pred); [variablelist [[Precondition:] [`lk` is locked by the current fiber, and either no other fiber is currently waiting on `*this`, or the execution of the `mutex()` member function on the `lk` objects supplied in the calls to `wait`, `wait_for` or `wait_until` in all the fibers currently waiting on `*this` would return the -same value as `lk->mutex()` for this call to `wait`.]] +same value as `lk.mutex()` for this call to `wait_for`.]] [[Effects:] [Atomically call `lk.unlock()` and blocks the current fiber. The fiber will unblock when notified by a call to `this->notify_one()` or `this->notify_all()`, when a time interval equal to or greater than the @@ -281,7 +293,7 @@ specified `timeout_duration` has elapsed, or spuriously. When the fiber is unblocked (for whatever reason), the lock is reacquired by invoking `lk.lock()` before the call to `wait` returns. The lock is also reacquired by invoking `lk.lock()` if the function exits with an exception. -The member function accepting `pred` is shorthand for: `` +The `wait_for()` member function accepting `pred` is shorthand for: `` while ( ! pred() ) { if ( cv_status::timeout == wait_for( lk, timeout_duration) ) { @@ -303,7 +315,7 @@ awakened because at least `timeout_duration` has elapsed.]] [[Returns:] [The overload accepting `pred` returns `false` if the call is returning because at least `timeout_duration` has elapsed and the predicate returns `false`, `true` otherwise.]] -[[Note:] [See [*Note] for `wait()`.]] +[[Note:] [See [*Note] for [member_link condition_variable..wait].]] ] [endsect] diff --git a/doc/customization.qbk b/doc/customization.qbk index 10bd0a46..f37c67e1 100644 --- a/doc/customization.qbk +++ b/doc/customization.qbk @@ -5,6 +5,7 @@ http://www.boost.org/LICENSE_1_0.txt ] +[#custom] [section:custom Customization] __boost_fiber__ allows to customize the scheduling algorithm by a user-defined @@ -12,72 +13,16 @@ implementation. A fiber-scheduler must implement interface __algo__. __boost_fiber__ provides scheduler [class_link round_robin]. -In order to use a custom scheduler for a given thread, that thread must call -[function_link set_scheduling_algorithm] before any other __boost_fiber__ entry -point. - void thread_fn() { my_fiber_scheduler mfs; boost::fibers::set_scheduling_algorithm( & mfs); ... } -You are explicitly permitted to code your own __algo__ subclass, and to pass -it to [function_link set_scheduling_algorithm]. +[heading fiber_context] - -[class_heading sched_algorithm] - - #include - - struct sched_algorithm { - virtual ~sched_algorithm() {} - - virtual void awakened( fiber_context *) = 0; - - virtual fiber_context * pick_next() = 0; - - virtual std::size_t ready_fibers() const noexcept = 0 - }; - - void set_scheduling_algorithm( sched_algorithm *); - -[member_heading algorithm..awakened] - - virtual void awakened( fiber_context * f) = 0; - -[variablelist -[[Effects:] [Marks fiber `f`, to be ready to run.]] -] - -[member_heading algorithm..pick_next] - - virtual fiber_context * pick_next() = 0; - -[variablelist -[[Effects:] [Depending on the scheduling algorithm, this function returns the -fiber which has to be resumed next.]] -] - -[member_heading algorithm..ready_fibers] - - virtual std::size_t ready_fibers() const noexcept = 0; - -[variablelist -[[Effects:] [Returns `false` if scheduling algorithm has fibers ready to run, -otherwise `true`.]] -] - - -[ns_function_heading fibers..set_scheduling_algorithm] - - void set_scheduling_algorithm( sched_algorithm * a); - -[variablelist -[[Effects:] [Registers `a` as scheduling algorithm.]] -] - -< TODO > +The fiber_context class is mostly supposed to be opaque. However, certain +members are available for use by a custom scheduling algorithm. [endsect] diff --git a/doc/fiber.qbk b/doc/fiber.qbk index 28ee5d22..62f6ce00 100644 --- a/doc/fiber.qbk +++ b/doc/fiber.qbk @@ -52,7 +52,7 @@ [heading Tutorial] Each __fiber__ represents a micro-thread which will be launched and managed -cooperatively by a scheduler. Objects of type __fiber__ are only moveable. +cooperatively by a scheduler. Objects of type __fiber__ are move-only. boost::fibers::fiber f1; // not-a-fiber @@ -91,7 +91,7 @@ The spawned __fiber__ is enqueued in the list of ready-to-run fibers at construc [heading Exceptions] -A exception thrown by the function or callable object passed to the __fiber__ +An exception escaping from the function or callable object passed to the __fiber__ constructor calls `std::terminate()`. If you need to know which exception was thrown, use __future__ and __packaged_task__. @@ -183,6 +183,18 @@ __interruption_enabled__. * __sleep_until__ * __yield__ * __interruption_point__ +* [member_link bounded_channel..push] +* [member_link bounded_channel..pop] +* [member_link bounded_channel..value_pop] +* [member_link unbounded_channel..push] +* [member_link unbounded_channel..pop] +* [member_link unbounded_channel..value_pop] +* [member_link future..wait] +* [member_link future..get] +* [member_link future..get_exception_ptr] +* [member_link shared_future..wait] +* [member_link shared_future..get] +* [member_link shared_future..get_exception_ptr] [#class_fiber_id] [heading Fiber IDs] @@ -273,11 +285,13 @@ operators on __fiber_id__ yield a total order for every non-equal __fiber_id__. [variablelist [[Preconditions:] [`Fn` must be copyable or movable.]] -[[Effects:] [`fn` is copied into internal storage for access by the new fiber.]] +[[Effects:] [`fn` is copied or moved into internal storage for access by the new fiber.]] [[Postconditions:] [`*this` refers to the newly created fiber of execution.]] [[Throws:] [__fiber_exception__ if an error occurs.]] [[Note:] [StackAllocator is required to allocate a stack for the internal -__econtext__.]] +__econtext__. If StackAllocator is not explicitly passed, a +__fixedsize_stack__ is used by default.]] +[[See also:] [[link stack]]] ] [heading Move constructor] @@ -314,7 +328,7 @@ value of `other.get_id()` prior to the assignment.]] [[Throws:] [Nothing.]] [[Note:] [The programmer must ensure that the destructor is never executed while the fiber is still __joinable__. Even if you know (by calling -[member_link fiber..operator safe_bool]) that the fiber has completed, you must +[member_link fiber..operator bool]) that the fiber has completed, you must still call either __join__ or __detach__ before destroying the `fiber` object.]] ] @@ -326,7 +340,7 @@ still call either __join__ or __detach__ before destroying the `fiber` object.] [[Returns:] [`true` if `*this` refers to a fiber of execution, which may or may not have completed; otherwise `false`.]] [[Throws:] [Nothing]] -[[See also:] [[member_link fiber..operator safe_bool]]] +[[See also:] [[member_link fiber..operator bool]]] ] [member_heading fiber..join] @@ -386,18 +400,26 @@ interruption enabled.]] [[Throws:] [Nothing]] ] -< TODO > -[#fiber_properties] -[heading Member function `template< typename PROPS > PROPS & properties()`] +[template_member_heading fiber..properties] template< typename PROPS > PROPS & properties(); [variablelist -[[Preconditions:] [`*this` refers to a fiber of execution.]] -[[Effects:] []] -[[Throws:] []] -[[Note:] []] +[[Preconditions:] [`*this` refers to a fiber of execution. [function_link +set_scheduling_algorithm] has been called from this thread with a pointer to a +(subclass) instance of [template_link sched_algorithm_with_properties] with the +same template argument `PROPS`. `*this` has been scheduled for execution at +least once.]] +[[Returns:] [a reference to the scheduler properties instance for `*this`.]] +[[Throws:] [`std::bad_cast` if `set_scheduling_algorithm()` was passed a +pointer to a `sched_algorithm_with_properties` subclass with some other +template parameter than `PROPS`.]] +[[Note:] [[template_link sched_algorithm_with_properties] provides a way for a +user-coded scheduler to associate extended properties, such as priority, with +a fiber instance. This method allows access to those user-provided properties +-- but only after this fiber has been scheduled for the first time.]] +[[See also:] [[link custom]]] ] [member_heading fiber..operator bool] @@ -420,7 +442,7 @@ distinguish whether the referenced fiber of execution is still running.]] [[Returns:] [`true` if `*this` does not refer to a fiber of execution or if its fiber of execution has terminated, `false` otherwise.]] [[Throws:] [Nothing]] -[[See also:] [[member_link fiber..operator safe_bool], __joinable__]] +[[See also:] [[member_link fiber..operator bool], __joinable__]] ] [member_heading fiber..swap] @@ -453,8 +475,6 @@ prior to the call.]] [[Effects:] [Directs __boost_fiber__ to use `scheduler`, which must be a concrete subclass of __algo__, as the scheduling algorithm for all fibers in the current thread.]] -[[Returns:] [Previous __algo__ instance set for the current thread, 0 if this -is the first __boost_fiber__ entry point called by the current thread.]] [[Note:] [If you want a given thread to use a non-default scheduling algorithm, make that thread call `set_scheduling_algorithm()` before any other __boost_fiber__ entry point. If no scheduler has been set for the current @@ -466,6 +486,7 @@ lifespan of the referenced `scheduler` object. The caller must eventually destroy the passed `scheduler`, just as it must allocate it in the first place.]] [[Throws:] [Nothing]] +[[See also:] [[link scheduling], [link custom]]] ] [function_heading ready_fibers] @@ -530,7 +551,7 @@ or both represent __not_a_fiber__, `false` otherwise.]] bool operator!=( id const& other) const noexcept; [variablelist -[[Returns:] [`! (other == * this)]] +[[Returns:] [[`! (other == * this)]]] [[Throws:] [Nothing.]] ] @@ -595,7 +616,7 @@ implementation-defined total order of `fiber::id` values places `*this` before fibers::fiber::id get_id() noexcept; void yield(); template< typename Clock, typename Duration > - void sleep_until( std::chrono::time_point< Clock, Duration > const& sleep_time) + void sleep_until( std::chrono::time_point< Clock, Duration > const& abs_time) template< typename Rep, typename Period > void sleep_for( std::chrono::duration< Rep, Period > const& timeout_duration); template< typename PROPS > @@ -626,7 +647,7 @@ executing fiber.]] #include template< typename Clock, typename Duration > - void sleep_until( std::chrono::time_point< Clock, Duration > const& sleep_time) + void sleep_until( std::chrono::time_point< Clock, Duration > const& abs_time) [variablelist [[Effects:] [Suspends the current fiber until the time point specified by @@ -647,8 +668,8 @@ guarantees about how soon after `abs_time` it might resume.]] [variablelist [[Effects:] [Suspends the current fiber until the time duration specified by `rel_time` has elapsed.]] -[[Throws:] [Nothing if operations of std::chrono::duration do not throw -exceptions. __fiber_interrupted__ if the current fiber is interrupted.]] +[[Throws:] [Any exceptions thrown by operations of `std::chrono::duration`. __fiber_interrupted__ if the current fiber is interrupted.]] [[Note:] [`sleep_for()` is one of the predefined __interruption_points__.]] [[Note:][The current fiber will not resume before `rel_time` has elapsed, but there are no guarantees about how soon after that it might resume.]] @@ -663,9 +684,11 @@ there are no guarantees about how soon after that it might resume.]] [variablelist [[Effects:] [Reliquishes execution control, allowing other fibers to run.]] [[Throws:] [__fiber_resource_error__ if an error occurs.]] +[[Note:] [`yield()` is ['not] an interruption point. A fiber that calls +`yield()` is not suspended: it is immediately passed to the scheduler as ready +to run.]] ] -< TODO > [ns_function_heading this_fiber..properties] #include @@ -674,9 +697,19 @@ there are no guarantees about how soon after that it might resume.]] PROPS & properties(); [variablelist -[[Effects:] []] -[[Throws:] []] -[[Note:] []] +[[Preconditions:] [[function_link set_scheduling_algorithm] has been called +from this thread with a pointer to a (subclass) instance of [template_link +sched_algorithm_with_properties] with the same template argument `PROPS`.]] +[[Returns:] [a reference to the scheduler properties instance for the +currently running fiber.]] +[[Throws:] [`std::bad_cast` if `set_scheduling_algorithm()` was passed a +pointer to a `sched_algorithm_with_properties` subclass with some other +template parameter than `PROPS`.]] +[[Note:] [[template_link sched_algorithm_with_properties] provides a way for a +user-coded scheduler to associate extended properties, such as priority, with +a fiber instance. This function allows access to those user-provided +properties.]] +[[See also:] [[link custom]]] ] [ns_function_heading this_fiber..interruption_point] diff --git a/doc/fibers.qbk b/doc/fibers.qbk index f7f98b70..fada0608 100644 --- a/doc/fibers.qbk +++ b/doc/fibers.qbk @@ -64,6 +64,11 @@ ] [template static_member_link[class_name method_name] [member_link [class_name]..[method_name]]] +[template data_member_heading[class_name member_name] +[hding [class_name]_[member_name]..Data member [`[member_name]]] +] +[template data_member_link[class_name member_name] [member_link [class_name]..[member_name]]] + [template function_heading[function_name] [hding [function_name]..Non-member function [`[function_name]()]] ] @@ -87,7 +92,7 @@ [def __barrier__ [class_link barrier]] [def __condition__ [class_link condition_variable]] [def __disable_interruption__ [class_link disable_interruption]] -[def __econtext__ ['execution_context]] +[def __econtext__ [@boost:/libs/context/doc/html/context/econtext.html ['execution_context]]] [def __fiber__ [class_link fiber]] [def __fiber_exception__ `fiber_exception`] [def __fiber_group__ [class_link fiber_group]] @@ -111,8 +116,9 @@ [def __segmented_stack__ [class_link segmented_stack]] [def __segmented_stack_stack__ ['segmented_stack-stack]] [def __shared_future__ [template_link shared_future]] +[def __stack_allocator__ ['stack_allocator]] [def __stack_allocator_concept__ [link stack_allocator_concept ['stack-allocator concept]]] -[def __stack_context__ [class_link stack_context]] +[def __stack_context__ [@boost:/libs/context/doc/html/context/stack/stack_context.html `stack_context`]] [def __timed_mutex__ [class_link timed_mutex]] [def __wait_for__ [member_link future..wait_for]] [def __wait__ [member_link future..wait]] @@ -160,7 +166,16 @@ [include fiber.qbk] [include scheduling.qbk] [include stack.qbk] +[#synchronization] [section:synchronization Synchronization] + +In general, __boost_fiber__ synchronization objects can neither be moved nor +copied. A synchronization object acts as a mutually-agreed rendezvous point +between different fibers. If such an object were copied somewhere else, the +new copy would have no consumers. If such an object were ['moved] somewhere +else, leaving the original instance in an unspecified state, existing +consumers would behave strangely. + [include mutexes.qbk] [include condition_variables.qbk] [include barrier.qbk] diff --git a/doc/future.qbk b/doc/future.qbk index 5ac0a7a7..cf9cabaa 100644 --- a/doc/future.qbk +++ b/doc/future.qbk @@ -58,12 +58,6 @@ A __future__ contains a shared state which is not shared with any other future. future & operator=( future const& other) = delete; - void swap( future & other) noexcept; - - operator safe_bool() const noexcept; - - bool operator!() const noexcept; - bool valid() const noexcept; shared_future< R > share(); @@ -72,6 +66,8 @@ A __future__ contains a shared state which is not shared with any other future. R & get(); // member only of future< R & > template specialization void get(); // member only of future< void > template specialization + std::exception_ptr get_exception_ptr(); + void wait() const; template< class Rep, class Period > @@ -120,33 +116,6 @@ After construction is `false == other.valid()`]] [[Throws:] [Nothing.]] ] -[member_heading future..swap] - - void swap( future & other) noexcept; - -[variablelist -[[Effects:] [Swaps the shared state between other and `this`.]] -[[Throws:] [Nothing.]] -] - -[member_heading future..operator safe_bool] - - operator safe_bool() const noexcept; - -[variablelist -[[Effects:] [Returns `true` if future is valid.]] -[[Throws:] [Nothing.]] -] - -[operator_heading future..operator_not..operator!] - - bool operator!() const noexcept; - -[variablelist -[[Effects:] [Returns `false` if future is valid.]] -[[Throws:] [Nothing.]] -] - [member_heading future..valid] bool valid() const noexcept; @@ -173,11 +142,20 @@ After construction is `false == other.valid()`]] [variablelist [[Precondition:] [`true == valid()`]] -[[Effects:] [Returns the result.]] +[[Returns:] [the result. If result not yet set, waits.]] [[Postcondition:] [`false == valid()`]] [[Throws:] [__future_error__ with error condition __no_state__.]] ] +[member_heading future..get_exception_ptr] + + std::exception_ptr get_exception_ptr(); + +[variablelist +[[Precondition:] [`true == valid()`]] +[[Returns:] [the exception pointer, if set. If no result is set, waits.]] +] + [member_heading future..wait] void wait(); @@ -233,12 +211,6 @@ A __shared_future__ contains a shared state which might be shared with other fut shared_future & operator=( shared_future const& other) noexcept; - void swap( shared_future & other) noexcept; - - operator safe_bool() const noexcept; - - bool operator!() const noexcept; - bool valid() const noexcept; R const& get(); // member only of generic shared_future template @@ -277,7 +249,7 @@ After construction is `false == other.valid()`.]] [heading Copy constructor] - shared_future( shared_future cosnt& other) noexcept; + shared_future( shared_future const& other) noexcept; [variablelist [[Effects:] [Constructs a future with the shared state of other. @@ -310,33 +282,6 @@ return * this; [[Throws:] [Nothing.]] ] -[member_heading shared_future..swap] - - void swap( shared_future & other) noexcept; - -[variablelist -[[Effects:] [Swaps the shared state between other and `this`.]] -[[Throws:] [Nothing.]] -] - -[member_heading shared_future..operator safe_bool] - - operator safe_bool() const noexcept; - -[variablelist -[[Effects:] [Returns `true` if future is valid.]] -[[Throws:] [Nothing.]] -] - -[operator_heading shared_future..operator_not..operator!] - - bool operator!() const noexcept; - -[variablelist -[[Effects:] [Returns `false` if future is valid.]] -[[Throws:] [Nothing.]] -] - [member_heading shared_future..valid] bool valid() const noexcept; @@ -359,6 +304,15 @@ return * this; [[Throws:] [__future_error__ with error condition __no_state__.]] ] +[member_heading shared_future..get_exception_ptr] + + std::exception_ptr get_exception_ptr(); + +[variablelist +[[Precondition:] [`true == valid()`]] +[[Returns:] [the exception pointer, if set. If no result is set, waits.]] +] + [member_heading shared_future..wait] void wait(); diff --git a/doc/mutexes.qbk b/doc/mutexes.qbk index 5b341433..dc2269e6 100644 --- a/doc/mutexes.qbk +++ b/doc/mutexes.qbk @@ -19,7 +19,7 @@ mutex( mutex const& other) = delete; mutex & operator=( mutex const& other) = delete; - + void lock(); bool try_lock(); void unlock(); @@ -32,6 +32,31 @@ __lock__, __try_lock__ and __unlock__ shall be permitted. Any fiber blocked in __lock__ is suspended in the scheduler until the owning fiber releases the lock by calling __unlock__. +[member_heading mutex..lock] + + void lock(); + +[variablelist +[[Effects:] []] +] + +[member_heading mutex..try_lock] + + bool try_lock(); + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[member_heading mutex..unlock] + + void unlock(); + +[variablelist +[[Effects:] []] +] + [class_heading timed_mutex] @@ -60,6 +85,51 @@ the lock on a given instance of __timed_mutex__ at any time. Multiple concurrent calls to __lock__, __try_lock__, __try_lock_until__, __try_lock_for__ and __unlock__ shall be permitted. +[member_heading timed_mutex..lock] + + void lock(); + +[variablelist +[[Effects:] []] +] + +[member_heading timed_mutex..try_lock] + + bool try_lock(); + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[member_heading timed_mutex..unlock] + + void unlock(); + +[variablelist +[[Effects:] []] +] + +[template_member_heading timed_mutex..try_lock_until] + + template< typename Clock, typename Duration > + bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time) + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[template_member_heading timed_mutex..try_lock_for] + + template< typename Rep, typename Period > + bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration) + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + [class_heading recursive_mutex] @@ -87,6 +157,31 @@ additional level of ownership of the mutex. __unlock__ must be called once for each level of ownership acquired by a single fiber before ownership can be acquired by another fiber. +[member_heading recursive_mutex..lock] + + void lock(); + +[variablelist +[[Effects:] []] +] + +[member_heading recursive_mutex..try_lock] + + bool try_lock(); + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[member_heading recursive_mutex..unlock] + + void unlock(); + +[variablelist +[[Effects:] []] +] + [class_heading recursive_timed_mutex] @@ -121,5 +216,50 @@ ownership of the mutex. __unlock__ must be called once for each level of ownership acquired by a single fiber before ownership can be acquired by another fiber. +[member_heading recursive_timed_mutex..lock] + + void lock(); + +[variablelist +[[Effects:] []] +] + +[member_heading recursive_timed_mutex..try_lock] + + bool try_lock(); + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[member_heading recursive_timed_mutex..unlock] + + void unlock(); + +[variablelist +[[Effects:] []] +] + +[template_member_heading recursive_timed_mutex..try_lock_until] + + template< typename Clock, typename Duration > + bool try_lock_until( std::chrono::time_point< Clock, Duration > const& timeout_time) + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + +[template_member_heading recursive_timed_mutex..try_lock_for] + + template< typename Rep, typename Period > + bool try_lock_for( std::chrono::duration< Rep, Period > const& timeout_duration) + +[variablelist +[[Effects:] []] +[[Returns:] []] +] + [endsect] diff --git a/doc/overview.qbk b/doc/overview.qbk index d69a1bf1..3129d3a5 100644 --- a/doc/overview.qbk +++ b/doc/overview.qbk @@ -17,8 +17,8 @@ Each fiber has its own stack. A fiber can save the current execution state, including all registers and CPU flags, the instruction pointer, and the stack pointer and later restore this state. -The idea is to have multiple execution paths running on a single thread using a -sort of cooperative scheduling (versus threads, which are preemptively scheduled). The +The idea is to have multiple execution paths running on a single thread using +cooperative scheduling (versus threads, which are preemptively scheduled). The running fiber decides explicitly when it should yield to allow another fiber to run (context switching). __boost_fiber__ internally uses __econtext__ from __boost_context__; the classes in @@ -68,6 +68,11 @@ A fiber launched on a particular thread will always run on that thread. A fiber can count on thread-local storage; however that storage will be shared among all fibers running on the same thread. +The fiber synchronization objects provided by this library will not, by +default, safely synchronize fibers running on different threads. However, they +can be coerced to provide that safety by building the library with +`BOOST_FIBERS_THREADSAFE` defined. Please see [link synchronization]. + For fiber-local storage, please see __fsp__. [#blocking] @@ -90,15 +95,12 @@ entire thread. Fiber authors are encouraged to consistently use asynchronous I/O. __boost_asio__ explicitly supports fibers; other asynchronous I/O operations can straightforwardly be adapted for __boost_fiber__. -Synchronization between a fiber running on one thread and a fiber running on a -different thread is an advanced topic. - -[warning This library is ['not] an official Boost library] +[warning This library is ['not] an official Boost library.] __boost_fiber__ depends upon __boost_context__. Boost version 1.58.0 or greater is required. -[info This library is C++14-only!] +[note This library is C++14-only!] [endsect] diff --git a/doc/queue.qbk b/doc/queue.qbk index 5ac7d909..85559a37 100644 --- a/doc/queue.qbk +++ b/doc/queue.qbk @@ -95,10 +95,11 @@ channel operations return the state of the channel. channel_op_status try_pop( value_type & va); template< typename Rep, typename Period > channel_op_status pop_wait_for( value_type & va, - std::chrono::duration< Rep, Period > const& timeout_duration); + std::chrono::duration< Rep, Period > const& timeout_duration); template< typename Clock, typename Duration > channel_op_status pop_wait_until( value_type & va, - std::chrono::time_point< Clock, Duration > const& timeout_time); + std::chrono::time_point< Clock, + Duration > const& timeout_time); }; [member_heading unbounded_channel..is_closed] @@ -118,7 +119,8 @@ channel operations return the state of the channel. [variablelist [[Effects:] [Deactivates the channel. No values can be put after calling `this->close()`. Fibers blocked in `this->pop()`, `this->pop_wait_for()` -or `this->pop_wait_until()` will return `closed`.]] +or `this->pop_wait_until()` will return `closed`. Fibers blocked in +`this->value_pop()` will receive an exception.]] [[Throws:] [Nothing.]] [[Note:] [`close()` is like closing a pipe. It informs waiting consumers that no more values will arrive.]] @@ -140,7 +142,7 @@ non-empty.]] channel_op_status push( value_type && va); [variablelist -[[Effects:] [Enchannels the value in the channel and wakes up a fiber blocked on +[[Effects:] [Enqueues the value in the channel and wakes up a fiber blocked on `this->pop()`, `this->pop_wait_for()` or `this->pop_wait_until()`.]] [[Throws:] [Nothing.]] ] @@ -150,9 +152,9 @@ non-empty.]] channel_op_status pop( value_type & va); [variablelist -[[Effects:] [Dechannels a value from the channel. If the channel `is_empty()`, the +[[Effects:] [Dequeues a value from the channel. If the channel `is_empty()`, the fiber gets suspended until at least one new item is `push()`ed (return value -`success` and `va` contains dechanneld value) or the channel gets `close()`d +`success` and `va` contains dequeued value) or the channel gets `close()`d (return value `closed`).]] [[Throws:] [__fiber_interrupted__]] ] diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 3310a822..09d5abbf 100644 --- a/doc/scheduling.qbk +++ b/doc/scheduling.qbk @@ -5,132 +5,324 @@ http://www.boost.org/LICENSE_1_0.txt ] +[#scheduling] [section:scheduling Scheduling] -Fibers are managed by a scheduler which coordinates the sequence of fibers -running or waiting. -Each thread is required to have its own scheduler. By default, __boost_fiber__ -creates for each thread a scheduler ([class_link round_robin]). +The fibers in a thread are coordinated by a fiber manager. Fibers trade +control cooperatively, rather than preemptively: the currently-running fiber +retains control until it invokes some operation that passes control to the +manager. Each time a fiber suspends (or yields), the fiber manager consults a +scheduler to determine which fiber will run next. + +__boost_fiber__ provides the fiber manager, but the scheduler is a +customization point. (See [link custom].) + +Each thread has its own scheduler. By default, __boost_fiber__ implicitly +instantiates [class_link round_robin] as the scheduler for each thread. + +To prevent the library from heap-allocating the default scheduler for a given +thread, that thread must call [function_link set_scheduling_algorithm] before +any other __boost_fiber__ entry point. -A fiber-scheduler must implement interface __algo__. You are explicitly permitted to code your own __algo__ subclass, and to pass it to [function_link set_scheduling_algorithm]. + void thread_fn() { my_fiber_scheduler mfs; boost::fibers::set_scheduling_algorithm( & mfs); ... } - -[class_heading sched_algorithm] - - #include - - struct sched_algorithm { - virtual ~sched_algorithm() {} - - virtual void awakened( fiber_context *) = 0; - - virtual fiber_context * pick_next() = 0; - }; - -[member_heading sched_algorithm..awakened] - - virtual void awakened( fiber_context * f) = 0; - -[variablelist -[[Effects:] [Marks fiber `f`, to be ready to run.]] -] - -[member_heading sched_algorithm..pick_next] - - virtual fiber_context * pick_next() = 0; - -[variablelist -[[Effects:] [Depending on the scheduling algorithm, this function returns the -fiber which is to be resumed next.]] -] - -[member_heading sched_algorithm..priority] - - virtual void priority( fiber_context *, int) noexcept = 0; - -[variablelist -[[Effects:] [Resets the priority of fiber `f`.]] -] - - -To prevent the library from heap-allocating a default scheduler for a given -thread, that thread must call [function_link set_scheduling_algorithm] before -any other __boost_fiber__ entry point. - - void thread_fn() - { - my_fiber_scheduler mfs; - boost::fibers::set_scheduling_algorithm( & mfs); - ... - } - -A fiber-scheduler must implement interface __algo__. __boost_fiber__ provides one -scheduler: [class_link round_robin]. - -You are explicitly permitted to code your own __algo__ subclass, and to pass -it to [function_link set_scheduling_algorithm]. +A scheduler class must implement interface __algo__. __boost_fiber__ provides +one scheduler: [class_link round_robin]. [class_heading sched_algorithm] +`sched_algorithm` is the abstract base class defining the interface that a +fiber scheduler must implement. + #include struct sched_algorithm { virtual ~sched_algorithm() {} - virtual void awakened( detail::worker_fiber *) = 0; + virtual void awakened( fiber_context *) = 0; - virtual detail::worker_fiber * pick_next() = 0; + virtual fiber_context * pick_next() = 0; - virtual void priority( detail::worker_fiber *, int) noexcept = 0; + virtual std::size_t ready_fibers() const noexcept = 0; }; [member_heading sched_algorithm..awakened] - virtual void awakened( detail::worker_fiber * f) = 0; + virtual void awakened( fiber_context * f) = 0; [variablelist -[[Effects:] [Marks fiber `f`, to be ready to run.]] +[[Effects:] [Informs the scheduler that fiber `f` is ready to run. Fiber `f` +might be newly launched, or it might have been blocked but has just been +awakened, or it might have called [ns_function_link this_fiber..yield].]] +[[Note:] [This method advises the scheduler to add fiber `f` to its collection +of fibers ready to run. A typical scheduler implementation places `f` into a +queue.]] +[[See also:] [[class_link round_robin]]] ] [member_heading sched_algorithm..pick_next] - virtual detail::worker_fiber * pick_next() = 0; + virtual fiber_context * pick_next() = 0; [variablelist -[[Effects:] [Depending on the scheduling algorithm, this function returns the -fiber which is to be resumed next.]] +[[Returns:] [the fiber which is to be resumed next, or `nullptr` if there is no +ready fiber.]] +[[Note:] [This is where the scheduler actually specifies the fiber which is to +run next. A typical scheduler implementation chooses the head of the ready +queue.]] +[[See also:] [[class_link round_robin]]] ] -[member_heading sched_algorithm..priority] +[member_heading sched_algorithm..ready_fibers] - virtual void priority( detail::worker_fiber *, int) noexcept = 0; + virtual std::size_t ready_fibers() const noexcept = 0; [variablelist -[[Effects:] [Resets the priority of fiber `f`.]] +[[Effects:] [Returns 0 if scheduling algorithm has fibers ready to run, +otherwise nonzero.]] ] [class_heading round_robin] -This class implements __algo__ and schedules fibers in round-robin fashion -(ignores `fiber::priority()`). +This class implements __algo__, scheduling fibers in round-robin fashion. -[info The example section provides a scheduler used for migrating fibers -(work-stealing) between threads (different schedulers).] + #include + + class round_robin: public sched_algorithm + { + virtual void awakened( fiber_context *); + + virtual fiber_context * pick_next(); + }; + +[member_heading round_robin..awakened] + + virtual void awakened( fiber_context * f); + +[variablelist +[[Effects:] [Enqueues fiber `f` onto a ready queue.]] +] + +[member_heading round_robin..pick_next] + + virtual fiber_context * pick_next(); + +[variablelist +[[Returns:] [the fiber at the head of the ready queue, or 0 if the queue is +empty.]] +[[Note:] [Placing ready fibers onto a queue, and returning them from the head +of that queue, shares the thread between ready fibers in round-robin fashion.]] +] -< TODO > -[heading customized scheduler] +[heading Custom Scheduler Fiber Properties] +A scheduler class directly derived from __algo__ can use any information +available from [class_link fiber_context] to implement the `sched_algorithm` +interface. But a custom scheduler might need to track additional properties +for a fiber. For instance, a priority-based scheduler would need to track a +fiber's priority. + +__boost_fiber__ provides a mechanism by which your custom scheduler can +associate custom properties with each fiber. + +[class_heading fiber_properties] + +A custom fiber properties class must be derived from `fiber_properties`. + + #include + + class fiber_properties + { + public: + fiber_properties( back_ptr f ); + + virtual ~fiber_properties() {} + + protected: + void notify(); + }; + +[heading Constructor] + + fiber_properties( back_ptr f ); + +[variablelist +[[Effects:] [Constructs base-class component of custom subclass.]] +[[Note:] [Your subclass constructor must accept a `back_ptr` and pass it to +the base-class `fiber_properties` constructor.]] +] + +[member_heading fiber_properties..notify] + + void notify(); + +[variablelist +[[Effects:] [Pass control to the custom [template_link +sched_algorithm_with_properties] subclass's [member_link +sched_algorithm_with_properties..property_change] method.]] +[[Note:] [A custom scheduler's [member_link +sched_algorithm_with_properties..pick_next] method might dynamically select +from the ready fibers, or [member_link +sched_algorithm_with_properties..awakened] might instead insert each ready +fiber into some form of ready queue for `pick_next()`. In the latter case, if +application code modifies a fiber property (e.g. priority) that should affect +that fiber's relationship to other ready fibers, the custom scheduler must be +given the opportunity to reorder its ready queue. The custom property subclass +should implement an access method to modify such a property; that access +method should call `notify()` once the new property value has been stored. +This passes control to the custom scheduler's `property_change()` method, +allowing the custom scheduler to reorder its ready queue appropriately. Use at +your discretion. Of course, if you define a property which does not affect the +behavior of the `pick_next()` method, you need not call `notify()` when that +property is modified.]] +] + +[template_heading sched_algorithm_with_properties] + +A custom scheduler that depends on a custom properties class `PROPS` should be +derived from `sched_algorithm_with_properties`. `PROPS` should be +derived from [class_link fiber_properties]. + + #include + + template< typename PROPS > + struct sched_algorithm_with_properties + { + // override this method instead of sched_algorithm::awakened() + virtual void awakened( fiber_context * f, PROPS& properties ) = 0; + + virtual fiber_context * pick_next(); + + // obtain f's associated PROPS instance + PROPS& properties( fiber_context * f); + + // override this to be notified by PROPS::notify() + virtual void property_change( fiber_context * f, PROPS & properties); + + // Override this to customize instantiation of PROPS, e.g. use a different + // allocator. Each PROPS instance is associated with a particular + // fiber_context. + virtual fiber_properties * new_properties( fiber_context * f); + }; + +[member_heading sched_algorithm_with_properties..awakened] + + virtual void awakened( fiber_context * f, PROPS& properties ); + +[variablelist +[[Effects:] [Informs the scheduler that fiber `f` is ready to run, like +[member_link sched_algorithm..awakened]. Passes the fiber's associated `PROPS` +instance.]] +[[Note:] [A `sched_algorithm_with_properties<>` subclass must override this +method instead of `sched_algorithm::awakened()`.]] +] + +[member_heading sched_algorithm_with_properties..pick_next] + + virtual fiber_context * pick_next(); + +[variablelist +[[Returns:] [the fiber which is to be resumed next, or `nullptr` if there is no +ready fiber.]] +[[Note:] [same as [member_link sched_algorithm..pick_next]]] +] + +[member_heading sched_algorithm_with_properties..properties] + + PROPS& properties( fiber_context * f); + +[variablelist +[[Returns:] [the `PROPS` instance associated with fiber `f`.]] +[[Note:] [The fiber's associated `PROPS` instance is already passed to +`awakened()` and `property_change()`. However, every [class_link +sched_algorithm] subclass is expected to track a collection of ready +[class_link fiber_context] instances. This method allows your custom scheduler +to retrieve the [class_link fiber_properties] subclass instance for any +`fiber_context` in its collection.]] +] + +[member_heading sched_algorithm_with_properties..property_change] + + virtual void property_change( fiber_context * f, PROPS & properties); + +[variablelist +[[Effects:] [Notify the custom scheduler of a possibly-relevant change to a +property belonging to fiber `f`. `properties` contains the new values of +all relevant properties.]] +[[Note:] [This method is only called when a custom [class_link +fiber_properties] subclass explicitly calls [member_link +fiber_properties..notify].]] +] + +[member_heading sched_algorithm_with_properties..new_properties] + + virtual fiber_properties * new_properties( fiber_context * f); + +[variablelist +[[Returns:] [A new instance of [class_link fiber_properties] subclass +`PROPS`.]] +[[Note:] [By default, `sched_algorithm_with_properties<>::new_properties()` +simply returns `new PROPS(f)`, placing the `PROPS` instance on the heap. +Override this method to allocate `PROPS` some other way. The returned +`fiber_properties` pointer must point to the `PROPS` instance to be associated +with fiber `f`.]] +] + +[class_heading fiber_context] + +While you are free to treat `fiber_context*` as an opaque token, certain +`fiber_context` members may be useful to a custom scheduler implementation. + +(Most `fiber_context` members are implementation details; most of interest are +implementations of [class_link fiber] methods.) + + #include + + class fiber_context + { + public: + + fiber_context * nxt; + + static fiber_context * main_fiber(); + + }; + +[data_member_heading fiber_context..nxt] + + fiber_context * nxt; + +[variablelist +[[Note:] [This link pointer may be used to help implement a custom scheduler's +ready queue. It is overwritten by the fiber manager every time this +`fiber_context` is returned by [member_link sched_algorithm..pick_next], but +between the time the fiber manager passes this `fiber_context` to [member_link +sched_algorithm..awakened] (or [member_link +sched_algorithm_with_properties..awakened]) and the time your `pick_next()` +returns it to the fiber manager, it is available for use by your custom +scheduler.]] +] + +[static_member_heading fiber_context..main_fiber] + + static fiber_context * main_fiber(); + +[variablelist +[[Returns:] [the `fiber_context` associated with the "main" fiber of the +thread: the one implicitly created by the thread itself, rather than one +explicitly created by __boost_fiber__.]] +] [endsect] diff --git a/doc/stack.qbk b/doc/stack.qbk index 2ca8ae0d..5645bce7 100644 --- a/doc/stack.qbk +++ b/doc/stack.qbk @@ -5,6 +5,7 @@ http://www.boost.org/LICENSE_1_0.txt ] +[#stack] [section:stack Stack allocation] A __fiber__ uses internally a __econtext__ which manages a set of registers and a stack. @@ -15,7 +16,7 @@ which is required to model a __stack_allocator_concept__. [heading __stack_allocator_concept__] A __stack_allocator__ must satisfy the __stack_allocator_concept__ requirements shown in the following table, in which `a` is an object of a -__stack_allocator__ type, `sctx` is a `stack_context`, and `size` is a `std::size_t`: +__stack_allocator__ type, `sctx` is a __stack_context__, and `size` is a `std::size_t`: [table [[expression][return type][notes]] @@ -26,7 +27,7 @@ __stack_allocator__ type, `sctx` is a `stack_context`, and `size` is a `std::siz ] [ [`a.allocate()`] - [`stack_context`] + [__stack_context__] [creates a stack] ] [ @@ -40,12 +41,13 @@ __stack_allocator__ type, `sctx` is a `stack_context`, and `size` is a `std::siz against exceeding the context's available stack size rather than leaving it as undefined behaviour.] -[important Calling `deallocate()` with a `stack_context` not previously passed -to `allocate()` results in undefined behaviour.] +[important Calling `deallocate()` with a __stack_context__ not obtained from +`allocate()` results in undefined behaviour.] [note The memory for the stack is not required to be aligned; alignment takes place inside __econtext__.] +See also [@boost:/libs/context/doc/html/context/stack.html Boost.Context stack allocation]. [class_heading protected_fixedsize_stack] @@ -55,9 +57,9 @@ It appends a guard page at the end of each stack to protect against exceeding the stack. If the guard page is accessed (read or write operation) a segmentation fault/access violation is generated by the operating system. -[important Using __pfixedsize_stack__ is expensive. That is, launching a -new fiber with a new stack is expensive; the allocated stack is just as -efficient to use as any other stack.] +[important Using __pfixedsize_stack__ is expensive. Launching a new fiber with +a stack of this type incurs the overhead of setting the memory protection; +once allocated, this stack is just as efficient to use as __fixedsize_stack__.] [note The appended `guard page` is [*not] mapped to physical memory, only virtual addresses are used.] @@ -74,19 +76,17 @@ end of each stack. The memory is simply managed by `std::malloc()` and [class_heading segmented_stack] -__boost_fiber__ supports usage of a __segmented_stack__, e. g. the size of +__boost_fiber__ supports usage of a __segmented_stack__, i.e. the stack grows on demand. The fiber is created with a minimal stack size -and will be increased as required. +which will be increased as required. Class __segmented_stack__ models the __stack_allocator_concept__. In contrast to __pfixedsize_stack__ and __fixedsize_stack__ it creates a stack which grows on demand. [note Segmented stacks are currently only supported by [*gcc] from version -[*4.7] [*clang] from version [*3.4] onwards. In order to use a -__segmented_stack__ __boost_fiber__ must be built with +[*4.7] and [*clang] from version [*3.4] onwards. In order to use a +__segmented_stack__, __boost_fiber__ must be built with property `segmented-stacks`, e.g. [*toolset=gcc segmented-stacks=on] at b2/bjam -command line. - -[endsect] +command line.] [endsect] diff --git a/examples/priority.cpp b/examples/priority.cpp index f6a28d1d..2c6991c7 100644 --- a/examples/priority.cpp +++ b/examples/priority.cpp @@ -56,10 +56,10 @@ public: {} // For a subclass of sched_algorithm_with_properties<>, it's important to - // override awakened_props(), NOT awakened(). - virtual void awakened_props(boost::fibers::fiber_context* f) + // override the correct awakened() overload. + virtual void awakened(boost::fibers::fiber_context* f, priority_props& props) { - int f_priority = properties(f).get_priority(); + int f_priority = props.get_priority(); // With this scheduler, fibers with higher priority values are // preferred over fibers with lower priority values. But fibers with // equal priority values are processed in round-robin fashion. So when @@ -75,7 +75,7 @@ public: f->nxt = *fp; *fp = f; - std::cout << "awakened(" << properties(f).name << "): "; + std::cout << "awakened(" << props.name << "): "; describe_ready_queue(); } diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index b1c00cc6..2afd8f4c 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -47,15 +48,20 @@ struct sched_algorithm_with_properties : public sched_algorithm_with_properties_ typedef sched_algorithm_with_properties_base super; // Mark this override 'final': sched_algorithm_with_properties subclasses - // must override awakened_props() instead. Otherwise you'd have to - // remember to start every subclass awakened() override with: - // sched_algorithm_with_properties::awakened(fb); + // must override awakened() with properties parameter instead. Otherwise + // you'd have to remember to start every subclass awakened() override + // with: sched_algorithm_with_properties::awakened(fb); virtual void awakened( fiber_context * f) final { fiber_properties * props = super::get_properties( f); if ( ! props) { // TODO: would be great if PROPS could be allocated on the new // fiber's stack somehow - props = new PROPS( f); + props = new_properties( f); + // It is not good for new_properties() to return 0. + BOOST_ASSERT_MSG(props, "new_properties() must return non-NULL"); + // new_properties() must return instance of (a subclass of) PROPS + BOOST_ASSERT_MSG(dynamic_cast(props), + "new_properties() must return properties class"); super::set_properties( f, props); } // Set sched_algo_ again every time this fiber becomes READY. That @@ -64,11 +70,11 @@ struct sched_algorithm_with_properties : public sched_algorithm_with_properties_ props->set_sched_algorithm( this); // Okay, now forward the call to subclass override. - awakened_props( f); + awakened( f, properties(f) ); } // subclasses override this method instead of the original awakened() - virtual void awakened_props( fiber_context *) = 0; + virtual void awakened( fiber_context *, PROPS& ) = 0; // used for all internal calls PROPS& properties( fiber_context * f) { @@ -83,6 +89,14 @@ struct sched_algorithm_with_properties : public sched_algorithm_with_properties_ void property_change_( fiber_context * f, fiber_properties * props ) final { property_change( f, * static_cast< PROPS * >( props) ); } + + // Override this to customize instantiation of PROPS, e.g. use a different + // allocator. Each PROPS instance is associated with a particular + // fiber_context. + virtual fiber_properties * new_properties( fiber_context * f) + { + return new PROPS( f); + } }; }} diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 418d032a..577cf8dc 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -137,7 +137,9 @@ public: template< typename PROPS > PROPS & properties() { - return dynamic_cast< PROPS & >( * impl_->get_properties() ); + fiber_properties* props = impl_->get_properties(); + BOOST_ASSERT_MSG(props, "fiber::properties not set"); + return dynamic_cast< PROPS & >( * props ); } }; diff --git a/include/boost/fiber/fiber_context.hpp b/include/boost/fiber/fiber_context.hpp index 2137137f..16917d11 100644 --- a/include/boost/fiber/fiber_context.hpp +++ b/include/boost/fiber/fiber_context.hpp @@ -331,7 +331,7 @@ public: void resume() { BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm - ctx_(); + ctx_.resume(); } std::chrono::high_resolution_clock::time_point const& time_point() const noexcept { diff --git a/include/boost/fiber/operations.hpp b/include/boost/fiber/operations.hpp index 8c102dc4..0b994c1b 100644 --- a/include/boost/fiber/operations.hpp +++ b/include/boost/fiber/operations.hpp @@ -11,6 +11,7 @@ #include // std::unique_lock #include +#include #include #include @@ -52,7 +53,10 @@ void sleep_for( std::chrono::duration< Rep, Period > const& timeout_duration) { template< typename PROPS > PROPS & properties() { - return dynamic_cast< PROPS & >( * fibers::detail::scheduler::instance()->active()->get_properties() ); + fibers::fiber_properties* props = + fibers::detail::scheduler::instance()->active()->get_properties(); + BOOST_ASSERT_MSG(props, "this_fiber::properties not set"); + return dynamic_cast< PROPS & >( * props ); } } // this_fiber