From 6fb1b0478980f4ea6f61534924a3bc9603011352 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 22 Sep 2014 10:00:34 -0400 Subject: [PATCH 01/30] Change some doc references from 'algorithm' to 'sched_algorithm'. Add some explanatory material in scheduling.qbk about coding your own scheduler and setting it with set_scheduling_algorithm(). Document sched_algorithm interface class. Fix the example in condition_variables.qbk to explicitly unlock 'lk' before calling process_data(). --- doc/condition_variables.qbk | 20 +++++++----- doc/fiber.qbk | 2 +- doc/scheduling.qbk | 62 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/doc/condition_variables.qbk b/doc/condition_variables.qbk index 5428563b..c474e65d 100644 --- a/doc/condition_variables.qbk +++ b/doc/condition_variables.qbk @@ -33,11 +33,13 @@ waiting. In the simplest case, this condition is just a boolean variable: void wait_for_data_to_process() { - boost::fibers::mutex::scoped_lock lk( mtx); - while ( ! data_ready) { - cond.wait( lk); - } + boost::lock_guard< boost::fibers::mutex > lk( mtx); + while ( ! data_ready) + { + cond.wait( lk); + } + } // release lk process_data(); } @@ -48,9 +50,9 @@ When the fiber is awakened, the mutex will be locked again before the call to update the shared data, and ensures that the data associated with the condition is correctly synchronized. -In the meantime, another fiber sets the condition to `true`, and then calls -either `notify_one` or `notify_all` on the condition variable to wake one -waiting fiber or all the waiting fibers respectively. +In the meantime, another fiber sets `data_ready` to `true`, and then calls +either `notify_one` or `notify_all` on the condition variable `cond` to wake +one waiting fiber or all the waiting fibers respectively. void retrieve_data(); void prepare_data(); @@ -69,6 +71,10 @@ waiting fiber or all the waiting fibers respectively. Note that the same mutex is locked before the shared data is updated, but that the mutex does not have to be locked across the call to `notify_one`. +Locking is important because the synchronization objects provided by +__boost_fiber__ can be used to synchronize fibers running on different +threads. + __boost_fiber__ provides both `condition_variable` and `condition_variable_any` because __boost_thread__ provides both. (__boost_fiber__ also provides the name `condition`, which has been deprecated diff --git a/doc/fiber.qbk b/doc/fiber.qbk index 54c69113..81a5c96b 100644 --- a/doc/fiber.qbk +++ b/doc/fiber.qbk @@ -566,7 +566,7 @@ __boost_fiber__ entry point. If no scheduler has been set for the current thread by the time __boost_fiber__ needs to use it, the library will heap-allocate a default [class_link round_robin] instance for this thread.]] [[Note:] [`set_scheduling_algorithm()` does ['not] take ownership of the -passed `algorithm*`: __boost_fiber__ does not claim responsibility for the +passed `sched_algorithm*`: __boost_fiber__ does not claim responsibility for the lifespan of the referenced `scheduler` object. The caller must eventually destroy the passed `scheduler`, just as it must allocate it in the first place. (Storing the pointer in a `boost::thread_specific_ptr` is one way to diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 8ad4f6e3..b0087c92 100644 --- a/doc/scheduling.qbk +++ b/doc/scheduling.qbk @@ -13,10 +13,72 @@ Each thread is required to have its own scheduler. By default, __boost_fiber__ allocates for each thread a scheduler ([class_link round_robin]) on the heap. +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]. + + +[class_heading sched_algorithm] + + #include + + struct sched_algorithm + { + virtual ~sched_algorithm() {} + + virtual void awakened( detail::worker_fiber *) = 0; + + virtual detail::worker_fiber * pick_next() = 0; + + virtual void priority( detail::worker_fiber *, int) noexcept = 0; + }; + +[member_heading sched_algorithm..awakened] + + virtual void awakened( detail::worker_fiber * f) = 0; + +[variablelist +[[Effects:] [Marks fiber `f`, to be ready to run.]] +] + +[member_heading sched_algorithm..pick_next] + + virtual detail::worker_fiber * 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( detail::worker_fiber *, int) noexcept = 0; + +[variablelist +[[Effects:] [Resets the priority of fiber `f`.]] +] + + [class_heading round_robin] This class implements __algo__ and schedules fibers in round-robin fashion (ignores `fiber::priority()`). +[info The example section provides a scheduler used for migrating fibers +(work-stealing) between threads (different schedulers).] + [endsect] From 312833436411606fbd96e6994f3daf5489394459 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Nov 2014 17:00:47 -0600 Subject: [PATCH 02/30] Initial cut at supporting arbitrary user-coded scheduler properties. Introduce fiber_properties class from which to derive a specific properties class for a particular user-coded sched_algorithm subclass. Add sched_algorithm::property_change(worker_fiber*, fiber_properties*) method to allow a fiber_properties subclass method to notify the sched_algorithm subclass of a change in a relevant property. This generalizes the present priority() method. Introduce sched_algorithm_with_properties template class from which to derive a user-coded scheduler that uses fiber_properties subclass PROPS. Give it ref-returning properties(fiber::id) and properties(worker_fiber*) methods. Introduce fiber_properties* field in worker_fiber, and initialize it to 0. Delete it when the worker_fiber is destroyed. Give it pointer-flavored access methods. Normally this field will remain 0; but the first time the worker_fiber is passed to a sched_algorithm_with_properties subclass, its awakened() method will instantiate the relevant PROPS subclass. Add ref-returning fiber::properties() method. Calling this method presumes that the current thread's sched_algorithm is derived from sched_algorithm_with_properties. Also add this_fiber::properties() with the same restriction. Add ref-returning fm_properties() functions to implement fiber::properties() and this_fiber::properties(). Allow sched_algorithm_with_properties to extract the worker_fiber* from a fiber::id (aka worker_fiber::id) so it can present public-facing properties(id) method as well as properties(worker_fiber*) method. (cherry picked from commit 18c7f2c13b9642826b42aa3f6fa0a6642fce9cbc) Conflicts: include/boost/fiber/detail/worker_fiber.hpp include/boost/fiber/fiber_manager.hpp --- .../migration/workstealing_round_robin.hpp | 1 + include/boost/fiber/algorithm.hpp | 47 ++++++++++++ include/boost/fiber/detail/fiber_base.hpp | 3 + include/boost/fiber/detail/worker_fiber.hpp | 11 +++ include/boost/fiber/fiber.hpp | 7 ++ include/boost/fiber/fiber_manager.hpp | 19 ++++- include/boost/fiber/operations.hpp | 6 ++ include/boost/fiber/properties.hpp | 73 +++++++++++++++++++ src/detail/worker_fiber.cpp | 13 +++- src/fiber_manager.cpp | 8 ++ 10 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 include/boost/fiber/properties.hpp diff --git a/examples/cpp03/migration/workstealing_round_robin.hpp b/examples/cpp03/migration/workstealing_round_robin.hpp index c9373c31..c21fa7d0 100644 --- a/examples/cpp03/migration/workstealing_round_robin.hpp +++ b/examples/cpp03/migration/workstealing_round_robin.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #ifdef BOOST_HAS_ABI_HEADERS diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 9eae1236..967b885b 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -33,6 +33,53 @@ struct sched_algorithm virtual detail::worker_fiber * pick_next() = 0; virtual void priority( detail::worker_fiber *, int) BOOST_NOEXCEPT = 0; + + virtual void property_change( detail::worker_fiber *, fiber_properties* ) {} +}; + +namespace detail { +// support sched_algorithm_with_properties::properties(fiber::id) +inline +fiber_base* extract_base(fiber_base::id id) { return id.impl_; } +} // detail + +template +struct sched_algorithm_with_properties: public sched_algorithm +{ +public: + typedef sched_algorithm_with_properties super; + + // Start every subclass awakened() override with: + // sched_algorithm_with_properties::awakened(f); + virtual void awakened( detail::worker_fiber *f) + { + if (! f->get_properties()) + { + // TODO: would be great if PROPS could be allocated on the new + // fiber's stack somehow + f->set_properties(new PROPS(f, this)); + } + } + + // used for all internal calls + PROPS& properties(detail::worker_fiber* f) + { + return static_cast(*f->get_properties()); + } + + // public-facing properties(fiber::id) method in case consumer retains a + // pointer to supplied sched_algorithm_with_properties subclass + PROPS& properties(detail::worker_fiber::id id) + { + return properties(extract(id)); + } + +private: + // support sched_algorithm_with_properties::properties(fiber::id) + detail::worker_fiber* extract(detail::worker_fiber::id id) + { + return static_cast(detail::extract_base(id)); + } }; }} diff --git a/include/boost/fiber/detail/fiber_base.hpp b/include/boost/fiber/detail/fiber_base.hpp index 00e11eb6..f6ad3b79 100644 --- a/include/boost/fiber/detail/fiber_base.hpp +++ b/include/boost/fiber/detail/fiber_base.hpp @@ -39,6 +39,9 @@ public: private: fiber_base * impl_; + // support sched_algorithm_with_properties::properties(fiber::id) + friend fiber_base* extract_base(id); + public: id() BOOST_NOEXCEPT : impl_( 0) diff --git a/include/boost/fiber/detail/worker_fiber.hpp b/include/boost/fiber/detail/worker_fiber.hpp index ed5c139b..a562e2c9 100644 --- a/include/boost/fiber/detail/worker_fiber.hpp +++ b/include/boost/fiber/detail/worker_fiber.hpp @@ -41,6 +41,9 @@ namespace boost { namespace fibers { + +class fiber_properties; + namespace detail { namespace coro = boost::coroutines; @@ -99,6 +102,7 @@ private: exception_ptr except_; spinlock splk_; std::vector< worker_fiber * > waiting_; + fiber_properties * properties_; public: worker_fiber( coro_t::yield_type *); @@ -254,6 +258,13 @@ public: f->deallocate(); } } + + void set_properties( fiber_properties* props); + + fiber_properties* get_properties() + { + return properties_; + } }; }}} diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index ab995b92..b1b74ef2 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef BOOST_HAS_ABI_HEADERS @@ -318,6 +319,12 @@ public: void join(); void interrupt() BOOST_NOEXCEPT; + + template + PROPS& properties() + { + return fm_properties(impl_); + } }; inline diff --git a/include/boost/fiber/fiber_manager.hpp b/include/boost/fiber/fiber_manager.hpp index e991f38e..5a233497 100644 --- a/include/boost/fiber/fiber_manager.hpp +++ b/include/boost/fiber/fiber_manager.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +//#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -54,6 +54,8 @@ void fm_resume_( detail::worker_fiber *); void fm_set_sched_algo( sched_algorithm *); +sched_algorithm* fm_get_sched_algo_(); + void fm_spawn( detail::worker_fiber *); void fm_priority( detail::worker_fiber *, int) BOOST_NOEXCEPT; @@ -98,6 +100,21 @@ chrono::high_resolution_clock::time_point fm_next_wakeup(); void fm_migrate( detail::worker_fiber *); +// implementation for fiber::properties() +template < class PROPS > +PROPS& fm_properties( detail::worker_fiber * f ) +{ + return dynamic_cast&>(*fm_get_sched_algo_()) + .properties(f); +} + +// implementation for this_fiber::properties() +template < class PROPS > +PROPS& fm_properties() +{ + return fm_properties(fm_active()); +} + }} # if defined(BOOST_MSVC) diff --git a/include/boost/fiber/operations.hpp b/include/boost/fiber/operations.hpp index 4bd3724c..955d135e 100644 --- a/include/boost/fiber/operations.hpp +++ b/include/boost/fiber/operations.hpp @@ -79,8 +79,14 @@ void thread_affinity( bool req) BOOST_NOEXCEPT fibers::fm_active()->thread_affinity( req); } +template < class PROPS > +PROPS& properties() +{ + return fibers::fm_properties(); } +} // this_fiber + namespace fibers { inline diff --git a/include/boost/fiber/properties.hpp b/include/boost/fiber/properties.hpp new file mode 100644 index 00000000..2d65ce90 --- /dev/null +++ b/include/boost/fiber/properties.hpp @@ -0,0 +1,73 @@ +// Copyright Nat Goodspeed 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Define fiber_properties, a base class from which a library consumer can +// derive a subclass with specific properties important to a user-coded +// scheduler. + +#ifndef BOOST_FIBERS_PROPERTIES_HPP +#define BOOST_FIBERS_PROPERTIES_HPP + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +# if defined(BOOST_MSVC) +# pragma warning(push) +# pragma warning(disable:4275) +# endif + +namespace boost { +namespace fibers { + +namespace detail { +class worker_fiber; +} // detail + +struct sched_algorithm; + +class fiber_properties +{ +protected: + detail::worker_fiber* fiber_; + sched_algorithm* sched_algo_; + + // Inform the relevant sched_algorithm instance that something important + // has changed, so it can (presumably) adjust its data structures + // accordingly. + void notify() + { + sched_algo_->property_change(fiber_, this); + } + +public: + // fiber_properties, and by implication every subclass, must accept back + // pointers to its worker_fiber and the associated sched_algorithm*. Any + // specific property setter method, after updating the relevant instance + // variable, can/should call notify(). + fiber_properties(detail::worker_fiber* f, sched_algorithm* algo): + fiber_(f), + sched_algo_(algo) + {} + + // We need a virtual destructor (hence a vtable) because fiber_properties + // is stored polymorphically (as fiber_properties*) in worker_fiber, and + // destroyed via that pointer. + ~fiber_properties() {} +}; + +}} // namespace boost::fibers + +# if defined(BOOST_MSVC) +# pragma warning(pop) +# endif + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBERS_PROPERTIES_HPP diff --git a/src/detail/worker_fiber.cpp b/src/detail/worker_fiber.cpp index c7fa9f5f..39cb2844 100644 --- a/src/detail/worker_fiber.cpp +++ b/src/detail/worker_fiber.cpp @@ -14,6 +14,7 @@ #include "boost/fiber/detail/scheduler.hpp" #include "boost/fiber/exceptions.hpp" +#include "boost/fiber/properties.hpp" #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -37,13 +38,16 @@ worker_fiber::worker_fiber( coro_t::yield_type * callee) : flags_( 0), priority_( 0), except_(), - waiting_() + waiting_(), + properties_(0) { BOOST_ASSERT( callee_); } worker_fiber::~worker_fiber() { BOOST_ASSERT( is_terminated() ); BOOST_ASSERT( waiting_.empty() ); + + delete properties_; } void @@ -141,6 +145,13 @@ worker_fiber::set_fss_data( fss_data( data, cleanup_fn) ) ); } +void +worker_fiber::set_properties( fiber_properties* props) +{ + delete properties_; + properties_ = props; +} + }}} #ifdef BOOST_HAS_ABI_HEADERS diff --git a/src/fiber_manager.cpp b/src/fiber_manager.cpp index dfa234e6..ed5cfeed 100644 --- a/src/fiber_manager.cpp +++ b/src/fiber_manager.cpp @@ -100,6 +100,14 @@ void fm_set_sched_algo( sched_algorithm * algo) fm->def_algo_.reset(); } +sched_algorithm* fm_get_sched_algo_() +{ + fiber_manager * fm = detail::scheduler::instance(); + + BOOST_ASSERT( 0 != fm); + return fm->sched_algo_; +} + chrono::high_resolution_clock::time_point fm_next_wakeup() { fiber_manager * fm = detail::scheduler::instance(); From ed64ee77f8142f7aef4c9f87494e43ad4633ad55 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 Nov 2014 19:59:29 -0500 Subject: [PATCH 03/30] Set fiber_properties::sched_algo_ every time through awakened(). Instead of setting a fiber_properties subclass's sched_algo_ back pointer once at construction time, unconditionally set it every time that fiber becomes READY (and is therefore passed to sched_algorithm::awakened()). This handles the case in which that fiber migrates to a different thread with a different sched_algorithm subclass instance. Break out fiber_properties::notify() implementation to a separate .cpp implementation file so it can bring in algorithm.hpp. We don't want properties.hpp to depend on algorithm.hpp. --- build/Jamfile.v2 | 1 + include/boost/fiber/algorithm.hpp | 7 ++++++- include/boost/fiber/properties.hpp | 25 +++++++++++++++---------- src/properties.cpp | 28 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 src/properties.cpp diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index cd69bfa5..36bc1a89 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -41,6 +41,7 @@ lib boost_fiber future.cpp interruption.cpp mutex.cpp + properties.cpp recursive_mutex.cpp recursive_timed_mutex.cpp round_robin.cpp diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 967b885b..2e5f5bda 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -57,8 +58,12 @@ public: { // TODO: would be great if PROPS could be allocated on the new // fiber's stack somehow - f->set_properties(new PROPS(f, this)); + f->set_properties(new PROPS(f)); } + // Set sched_algo_ again every time this fiber becomes READY. That + // handles the case of a fiber migrating to a new thread with a new + // sched_algorithm subclass instance. + f->get_properties()->set_sched_algorithm(this); } // used for all internal calls diff --git a/include/boost/fiber/properties.hpp b/include/boost/fiber/properties.hpp index 2d65ce90..e13da8c3 100644 --- a/include/boost/fiber/properties.hpp +++ b/include/boost/fiber/properties.hpp @@ -33,31 +33,36 @@ struct sched_algorithm; class fiber_properties { protected: + // initialized by constructor detail::worker_fiber* fiber_; + // set every time this fiber becomes READY sched_algorithm* sched_algo_; // Inform the relevant sched_algorithm instance that something important // has changed, so it can (presumably) adjust its data structures // accordingly. - void notify() - { - sched_algo_->property_change(fiber_, this); - } + void notify(); public: - // fiber_properties, and by implication every subclass, must accept back - // pointers to its worker_fiber and the associated sched_algorithm*. Any - // specific property setter method, after updating the relevant instance - // variable, can/should call notify(). - fiber_properties(detail::worker_fiber* f, sched_algorithm* algo): + // fiber_properties, and by implication every subclass, must accept a back + // pointer to its worker_fiber. Any specific property setter method, after + // updating the relevant instance variable, can/should call notify(). + fiber_properties(detail::worker_fiber* f): fiber_(f), - sched_algo_(algo) + sched_algo_(0) {} // We need a virtual destructor (hence a vtable) because fiber_properties // is stored polymorphically (as fiber_properties*) in worker_fiber, and // destroyed via that pointer. ~fiber_properties() {} + + // not really intended for public use, but sched_algorithm_with_properties + // must be able to call this + void set_sched_algorithm(sched_algorithm* algo) + { + sched_algo_ = algo; + } }; }} // namespace boost::fibers diff --git a/src/properties.cpp b/src/properties.cpp new file mode 100644 index 00000000..226c7441 --- /dev/null +++ b/src/properties.cpp @@ -0,0 +1,28 @@ +// Copyright Oliver Kowalke 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "boost/fiber/properties.hpp" +#include "boost/fiber/algorithm.hpp" +#include "boost/fiber/fiber_manager.hpp" + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { + +void fiber_properties::notify() +{ + BOOST_ASSERT(sched_algo_); + sched_algo_->property_change(fiber_, this); +} + +}} // boost::fiber + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif From 402a4353f77a58d78a0bcff8d17c7d8b46c39089 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 Nov 2014 21:19:28 -0500 Subject: [PATCH 04/30] Define sched_algorithm methods on fiber_base*, not worker_fiber*. Some reviewers disliked that to build a custom sched_algorithm subclass, you must necessarily manipulate pointers to classes in the boost::fibers::detail namespace. Redefine all such methods to use fiber_base* rather than detail::worker_fiber*. Hoist fiber_base* into boost::fibers namespace. We considered an opaque typedef rather than fiber_base*, but in fact a sched_algorithm subclass may need is_ready(). Moreover, a sched_algorithm subclass may well want to use an intrusive linked list to queue fibers. Hoist worker_fiber::nxt_ pointer into fiber_base, and remove worker_fiber::next() and next_reset() methods. This allows recasting detail::fifo in terms of fiber_base*, therefore round_robin can be stated almost entirely in terms of fiber_base* rather than worker_fiber*. Recast fiber constructor taking worker_fiber* to take fiber_base* instead. This constructor is used solely for fiber migration; with relevant functions now returning fiber_base*, we think we no longer need fiber(worker_fiber*). --- .../migration/workstealing_round_robin.cpp | 12 ++++++------ .../migration/workstealing_round_robin.hpp | 8 ++++---- include/boost/fiber/algorithm.hpp | 16 ++++++++++------ include/boost/fiber/detail/fiber_base.hpp | 6 +++++- include/boost/fiber/detail/fifo.hpp | 18 +++++++++--------- include/boost/fiber/detail/waiting_queue.hpp | 18 +++++++++--------- include/boost/fiber/detail/worker_fiber.hpp | 10 ---------- include/boost/fiber/fiber.hpp | 6 ++++-- include/boost/fiber/round_robin.hpp | 6 +++--- src/detail/worker_fiber.cpp | 1 - src/fiber_manager.cpp | 6 +++--- src/round_robin.cpp | 10 +++++----- 12 files changed, 58 insertions(+), 59 deletions(-) diff --git a/examples/cpp03/migration/workstealing_round_robin.cpp b/examples/cpp03/migration/workstealing_round_robin.cpp index 7617cc11..0ad78ee4 100644 --- a/examples/cpp03/migration/workstealing_round_robin.cpp +++ b/examples/cpp03/migration/workstealing_round_robin.cpp @@ -13,17 +13,17 @@ #endif void -workstealing_round_robin::awakened( boost::fibers::detail::worker_fiber * f) +workstealing_round_robin::awakened( boost::fibers::fiber_base * f) { boost::mutex::scoped_lock lk( mtx_); rqueue_.push_back( f); } -boost::fibers::detail::worker_fiber * +boost::fibers::fiber_base * workstealing_round_robin::pick_next() { boost::mutex::scoped_lock lk( mtx_); - boost::fibers::detail::worker_fiber * f = 0; + boost::fibers::fiber_base * f = 0; if ( ! rqueue_.empty() ) { f = rqueue_.front(); @@ -33,20 +33,20 @@ workstealing_round_robin::pick_next() } void -workstealing_round_robin::priority( boost::fibers::detail::worker_fiber * f, int prio) BOOST_NOEXCEPT +workstealing_round_robin::priority( boost::fibers::fiber_base * f, int prio) BOOST_NOEXCEPT { BOOST_ASSERT( f); // set only priority to fiber // round-robin does not respect priorities - f->priority( prio); + static_cast(f)->priority( prio); } boost::fibers::fiber workstealing_round_robin::steal() BOOST_NOEXCEPT { boost::mutex::scoped_lock lk( mtx_); - boost::fibers::detail::worker_fiber * f = 0; + boost::fibers::fiber_base * f = 0; if ( ! rqueue_.empty() ) { f = rqueue_.back(); diff --git a/examples/cpp03/migration/workstealing_round_robin.hpp b/examples/cpp03/migration/workstealing_round_robin.hpp index c21fa7d0..7c70fd71 100644 --- a/examples/cpp03/migration/workstealing_round_robin.hpp +++ b/examples/cpp03/migration/workstealing_round_robin.hpp @@ -29,17 +29,17 @@ class workstealing_round_robin : public boost::fibers::sched_algorithm { private: - typedef std::deque< boost::fibers::detail::worker_fiber * > rqueue_t; + typedef std::deque< boost::fibers::fiber_base * > rqueue_t; boost::mutex mtx_; rqueue_t rqueue_; public: - virtual void awakened( boost::fibers::detail::worker_fiber *); + virtual void awakened( boost::fibers::fiber_base *); - virtual boost::fibers::detail::worker_fiber * pick_next(); + virtual boost::fibers::fiber_base * pick_next(); - virtual void priority( boost::fibers::detail::worker_fiber *, int) BOOST_NOEXCEPT; + virtual void priority( boost::fibers::fiber_base *, int) BOOST_NOEXCEPT; boost::fibers::fiber steal(); }; diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 2e5f5bda..007b6cd1 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -25,17 +25,20 @@ namespace boost { namespace fibers { +// hoist fiber_base out of detail namespace into boost::fibers +typedef detail::fiber_base fiber_base; + struct sched_algorithm { virtual ~sched_algorithm() {} - virtual void awakened( detail::worker_fiber *) = 0; + virtual void awakened( fiber_base *) = 0; - virtual detail::worker_fiber * pick_next() = 0; + virtual fiber_base * pick_next() = 0; - virtual void priority( detail::worker_fiber *, int) BOOST_NOEXCEPT = 0; + virtual void priority( fiber_base *, int) BOOST_NOEXCEPT = 0; - virtual void property_change( detail::worker_fiber *, fiber_properties* ) {} + virtual void property_change( fiber_base *, fiber_properties* ) {} }; namespace detail { @@ -51,9 +54,10 @@ public: typedef sched_algorithm_with_properties super; // Start every subclass awakened() override with: - // sched_algorithm_with_properties::awakened(f); - virtual void awakened( detail::worker_fiber *f) + // sched_algorithm_with_properties::awakened(fb); + virtual void awakened( fiber_base *fb) { + detail::worker_fiber* f = static_cast(fb); if (! f->get_properties()) { // TODO: would be great if PROPS could be allocated on the new diff --git a/include/boost/fiber/detail/fiber_base.hpp b/include/boost/fiber/detail/fiber_base.hpp index f6ad3b79..d4ff6457 100644 --- a/include/boost/fiber/detail/fiber_base.hpp +++ b/include/boost/fiber/detail/fiber_base.hpp @@ -86,7 +86,8 @@ public: { return 0 == impl_; } }; - fiber_base() + fiber_base(): + nxt_(0) {} virtual ~fiber_base() {}; @@ -96,6 +97,9 @@ public: virtual void set_ready() BOOST_NOEXCEPT = 0; virtual id get_id() const BOOST_NOEXCEPT = 0; + + // for use by sched_algorithm to queue any subclass instance + fiber_base * nxt_; }; }}} diff --git a/include/boost/fiber/detail/fifo.hpp b/include/boost/fiber/detail/fifo.hpp index adf5aceb..6bc0fb4c 100644 --- a/include/boost/fiber/detail/fifo.hpp +++ b/include/boost/fiber/detail/fifo.hpp @@ -36,28 +36,28 @@ public: bool empty() const BOOST_NOEXCEPT { return 0 == head_; } - void push( worker_fiber * item) BOOST_NOEXCEPT + void push( fiber_base * item) BOOST_NOEXCEPT { BOOST_ASSERT( 0 != item); - BOOST_ASSERT( 0 == item->next() ); + BOOST_ASSERT( 0 == item->nxt_ ); if ( empty() ) head_ = tail_ = item; else { - tail_->next( item); + tail_->nxt_ = item; tail_ = item; } } - worker_fiber * pop() BOOST_NOEXCEPT + fiber_base * pop() BOOST_NOEXCEPT { BOOST_ASSERT( ! empty() ); - worker_fiber * item = head_; - head_ = head_->next(); + fiber_base * item = head_; + head_ = head_->nxt_; if ( 0 == head_) tail_ = 0; - item->next_reset(); + item->nxt_ = 0; return item; } @@ -68,8 +68,8 @@ public: } private: - worker_fiber * head_; - worker_fiber * tail_; + fiber_base * head_; + fiber_base * tail_; }; }}} diff --git a/include/boost/fiber/detail/waiting_queue.hpp b/include/boost/fiber/detail/waiting_queue.hpp index e53f9632..f9125a89 100644 --- a/include/boost/fiber/detail/waiting_queue.hpp +++ b/include/boost/fiber/detail/waiting_queue.hpp @@ -40,7 +40,7 @@ public: void push( worker_fiber * item) BOOST_NOEXCEPT { BOOST_ASSERT( 0 != item); - BOOST_ASSERT( 0 == item->next() ); + BOOST_ASSERT( 0 == item->nxt_ ); if ( empty() ) head_ = tail_ = item; @@ -49,22 +49,22 @@ public: worker_fiber * f = head_, * prev = 0; do { - worker_fiber * nxt = f->next(); + worker_fiber * nxt = static_cast(f->nxt_); if ( item->time_point() <= f->time_point() ) { if ( head_ == f) { BOOST_ASSERT( 0 == prev); - item->next( f); + item->nxt_ = f; head_ = item; } else { BOOST_ASSERT( 0 != prev); - item->next( f); - prev->next( item); + item->nxt_ = f; + prev->nxt_ = item; } break; } @@ -72,7 +72,7 @@ public: { BOOST_ASSERT( 0 == nxt); - tail_->next( item); + tail_->nxt_ = item; tail_ = item; break; } @@ -100,7 +100,7 @@ public: chrono::high_resolution_clock::time_point now( chrono::high_resolution_clock::now() ); while ( 0 != f) { - worker_fiber * nxt = f->next(); + worker_fiber * nxt = static_cast(f->nxt_); if ( fn( f, now) ) { if ( f == head_) @@ -118,9 +118,9 @@ public: if ( 0 == nxt) tail_ = prev; - prev->next( nxt); + prev->nxt_ = nxt; } - f->next_reset(); + f->nxt_ = 0; f->time_point_reset(); sched_algo->awakened( f); } diff --git a/include/boost/fiber/detail/worker_fiber.hpp b/include/boost/fiber/detail/worker_fiber.hpp index a562e2c9..fc844fae 100644 --- a/include/boost/fiber/detail/worker_fiber.hpp +++ b/include/boost/fiber/detail/worker_fiber.hpp @@ -92,7 +92,6 @@ private: atomic< std::size_t > use_count_; fss_data_t fss_data_; - worker_fiber * nxt_; chrono::high_resolution_clock::time_point tp_; coro_t::yield_type * callee_; coro_t::call_type caller_; @@ -218,15 +217,6 @@ public: BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm } - worker_fiber * next() const BOOST_NOEXCEPT - { return nxt_; } - - void next( worker_fiber * nxt) BOOST_NOEXCEPT - { nxt_ = nxt; } - - void next_reset() BOOST_NOEXCEPT - { nxt_ = 0; } - chrono::high_resolution_clock::time_point const& time_point() const BOOST_NOEXCEPT { return tp_; } diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index b1b74ef2..b1b3885d 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -108,8 +108,10 @@ public: impl_() {} - explicit fiber( detail::worker_fiber * impl) BOOST_NOEXCEPT : - impl_( impl) + // This fiber_base* is allowed to be 0 -- call joinable() before performing + // operations on such a fiber object! + explicit fiber( fiber_base * impl) BOOST_NOEXCEPT : + impl_( dynamic_cast(impl)) {} #ifdef BOOST_MSVC diff --git a/include/boost/fiber/round_robin.hpp b/include/boost/fiber/round_robin.hpp index 09f7d5ef..01ce10fe 100644 --- a/include/boost/fiber/round_robin.hpp +++ b/include/boost/fiber/round_robin.hpp @@ -33,11 +33,11 @@ private: rqueue_t rqueue_; public: - virtual void awakened( detail::worker_fiber *); + virtual void awakened( fiber_base *); - virtual detail::worker_fiber * pick_next(); + virtual fiber_base * pick_next(); - virtual void priority( detail::worker_fiber *, int) BOOST_NOEXCEPT; + virtual void priority( fiber_base *, int) BOOST_NOEXCEPT; }; }} diff --git a/src/detail/worker_fiber.cpp b/src/detail/worker_fiber.cpp index 39cb2844..a98a0c2e 100644 --- a/src/detail/worker_fiber.cpp +++ b/src/detail/worker_fiber.cpp @@ -30,7 +30,6 @@ worker_fiber::worker_fiber( coro_t::yield_type * callee) : fiber_base(), use_count_( 1), // allocated on stack fss_data_(), - nxt_( 0), tp_( (chrono::high_resolution_clock::time_point::max)() ), callee_( callee), caller_(), diff --git a/src/fiber_manager.cpp b/src/fiber_manager.cpp index ed5cfeed..be22b478 100644 --- a/src/fiber_manager.cpp +++ b/src/fiber_manager.cpp @@ -172,17 +172,17 @@ void fm_run() BOOST_ASSERT( 0 != fm); - // move all fibers witch are ready (state_ready) + // move all fibers which are ready (state_ready) // from waiting-queue to the runnable-queue fm->wqueue_.move_to( fm->sched_algo_, fetch_ready); // pop new fiber from ready-queue which is not complete // (example: fiber in ready-queue could be canceled by active-fiber) - detail::worker_fiber * f( fm->sched_algo_->pick_next() ); + fiber_base * f( fm->sched_algo_->pick_next()); if ( f) { BOOST_ASSERT_MSG( f->is_ready(), "fiber with invalid state in ready-queue"); - fm_resume_( f); + fm_resume_( static_cast(f)); } else { diff --git a/src/round_robin.cpp b/src/round_robin.cpp index bf398da5..c81d361e 100644 --- a/src/round_robin.cpp +++ b/src/round_robin.cpp @@ -16,30 +16,30 @@ namespace boost { namespace fibers { void -round_robin::awakened( detail::worker_fiber * f) +round_robin::awakened( fiber_base * f) { BOOST_ASSERT( 0 != f); rqueue_.push( f); } -detail::worker_fiber * +fiber_base * round_robin::pick_next() { - detail::worker_fiber * victim = 0; + fiber_base * victim = 0; if ( ! rqueue_.empty() ) victim = rqueue_.pop(); return victim; } void -round_robin::priority( detail::worker_fiber * f, int prio) BOOST_NOEXCEPT +round_robin::priority( fiber_base * f, int prio) BOOST_NOEXCEPT { BOOST_ASSERT( f); // set only priority to fiber // round-robin does not respect priorities - f->priority( prio); + static_cast(f)->priority( prio); } }} From 12fe326c6fb7cad6f3e2c52fa24066534a7f0c6b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 07:26:45 -0500 Subject: [PATCH 05/30] Simplify detail::fifo by making tail_ point to last link pointer. Maintaining a singly-linked list is tricky when you walk it with a node* pointer. But if you use a node** pointer starting at &head_ and advancing to point to each &nxt_ pointer, you can coalesce the empty-list case into the normal case. --- include/boost/fiber/detail/fifo.hpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/include/boost/fiber/detail/fifo.hpp b/include/boost/fiber/detail/fifo.hpp index 6bc0fb4c..df58baf5 100644 --- a/include/boost/fiber/detail/fifo.hpp +++ b/include/boost/fiber/detail/fifo.hpp @@ -30,7 +30,7 @@ class fifo : private noncopyable public: fifo() BOOST_NOEXCEPT : head_( 0), - tail_( 0) + tail_( &head_) {} bool empty() const BOOST_NOEXCEPT @@ -41,13 +41,11 @@ public: BOOST_ASSERT( 0 != item); BOOST_ASSERT( 0 == item->nxt_ ); - if ( empty() ) - head_ = tail_ = item; - else - { - tail_->nxt_ = item; - tail_ = item; - } + // *tail_ holds the null marking the end of the fifo. So we can extend + // the fifo by assigning to *tail_. + *tail_ = item; + // Advance tail_ to point to the new end marker. + tail_ = &item->nxt_; } fiber_base * pop() BOOST_NOEXCEPT @@ -56,7 +54,7 @@ public: fiber_base * item = head_; head_ = head_->nxt_; - if ( 0 == head_) tail_ = 0; + if ( 0 == head_) tail_ = &head_; item->nxt_ = 0; return item; } @@ -68,8 +66,13 @@ public: } private: + // head_ points to the head item, or is null fiber_base * head_; - fiber_base * tail_; + // tail_ points to the nxt_ field that contains the null that marks the + // end of the fifo. When the fifo is empty, tail_ points to head_. tail_ + // must never be null: it always points to a real fiber_base*. However, in + // normal use, (*tail_) is always null. + fiber_base ** tail_; }; }}} From 2bffbddeaaa9309a70ca19f2a987f8b14c83acf9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 07:42:56 -0500 Subject: [PATCH 06/30] Reimplement waiting_queue::push() using pointer-to-pointer trick. Change waiting_queue::head_ from worker_fiber* to fiber_base* for uniformity with worker_fiber::nxt_. This lets push() scan from head_ with a fiber_base**, looking for the right insertion point. Insertion then becomes a couple unconditional assignments. Because push() always scans from head_, tail_ was actually never used. Remove it. --- include/boost/fiber/detail/waiting_queue.hpp | 75 +++++++------------- 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/include/boost/fiber/detail/waiting_queue.hpp b/include/boost/fiber/detail/waiting_queue.hpp index f9125a89..61dcc97e 100644 --- a/include/boost/fiber/detail/waiting_queue.hpp +++ b/include/boost/fiber/detail/waiting_queue.hpp @@ -30,8 +30,7 @@ class waiting_queue : private noncopyable { public: waiting_queue() BOOST_NOEXCEPT : - head_( 0), - tail_( 0) + head_( 0) {} bool empty() const BOOST_NOEXCEPT @@ -42,53 +41,36 @@ public: BOOST_ASSERT( 0 != item); BOOST_ASSERT( 0 == item->nxt_ ); - if ( empty() ) - head_ = tail_ = item; - else - { - worker_fiber * f = head_, * prev = 0; - do - { - worker_fiber * nxt = static_cast(f->nxt_); - if ( item->time_point() <= f->time_point() ) - { - if ( head_ == f) - { - BOOST_ASSERT( 0 == prev); + // Skip past any worker_fibers in the queue whose time_point() is less + // than item->time_point(), looking for the first worker_fiber in the + // queue whose time_point() is at least item->time_point(). Insert + // item before that. In other words, insert item so as to preserve + // ascending order of time_point() values. (Recall that a worker_fiber + // waiting with no timeout uses the maximum time_point value.) - item->nxt_ = f; - head_ = item; - } - else - { - BOOST_ASSERT( 0 != prev); + // We do this by walking the linked list of nxt_ fields with a + // fiber_base**. In other words, first we point to &head_, then to + // &head_->nxt_, then to &head_->nxt_->nxt_ and so forth. When we find + // the item with the right time_point(), we're already pointing to the + // fiber_base* that links it into the list. Insert item right there. - item->nxt_ = f; - prev->nxt_ = item; - } - break; - } - else if ( tail_ == f) - { - BOOST_ASSERT( 0 == nxt); + fiber_base** f = &head_; + for ( ; *f; f = &(*f)->nxt_) + if (item->time_point() <= (static_cast(*f))->time_point()) + break; - tail_->nxt_ = item; - tail_ = item; - break; - } - - prev = f; - f = nxt; - } - while ( 0 != f); - } + // Here, either we reached the end of the list (! *f) or we found a + // (*f) before which to insert 'item'. Break the link at *f and insert + // item. + item->nxt_ = *f; + *f = item; } worker_fiber * top() const BOOST_NOEXCEPT { BOOST_ASSERT( ! empty() ); - return head_; + return static_cast(head_); } template< typename SchedAlgo, typename Fn > @@ -96,28 +78,23 @@ public: { BOOST_ASSERT( sched_algo); - worker_fiber * f = head_, * prev = 0; + worker_fiber * f = static_cast(head_), * prev = 0; chrono::high_resolution_clock::time_point now( chrono::high_resolution_clock::now() ); while ( 0 != f) { worker_fiber * nxt = static_cast(f->nxt_); if ( fn( f, now) ) { - if ( f == head_) + if ( f == static_cast(head_)) { BOOST_ASSERT( 0 == prev); head_ = nxt; - if ( 0 == head_) - tail_ = 0; } else { BOOST_ASSERT( 0 != prev); - if ( 0 == nxt) - tail_ = prev; - prev->nxt_ = nxt; } f->nxt_ = 0; @@ -133,12 +110,10 @@ public: void swap( waiting_queue & other) { std::swap( head_, other.head_); - std::swap( tail_, other.tail_); } private: - worker_fiber * head_; - worker_fiber * tail_; + fiber_base * head_; }; }}} From 20a2f599137dbe810f1f208a7b4e394839dc260e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 08:38:34 -0500 Subject: [PATCH 07/30] Reimplement waiting_queue::move_to() using fiber_base** scan. This simplifies unlinking from the queue. --- include/boost/fiber/detail/waiting_queue.hpp | 41 ++++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/include/boost/fiber/detail/waiting_queue.hpp b/include/boost/fiber/detail/waiting_queue.hpp index 61dcc97e..886d4342 100644 --- a/include/boost/fiber/detail/waiting_queue.hpp +++ b/include/boost/fiber/detail/waiting_queue.hpp @@ -78,32 +78,31 @@ public: { BOOST_ASSERT( sched_algo); - worker_fiber * f = static_cast(head_), * prev = 0; chrono::high_resolution_clock::time_point now( chrono::high_resolution_clock::now() ); - while ( 0 != f) + + // Search the queue for every worker_fiber 'f' for which fn(f, now) + // returns true. Each time we find such a worker_fiber, unlink it from + // the queue and pass it to sched_algo->awakened(). + + // Search using a fiber_base**, starting at &head_. + for (fiber_base** fp = &head_; *fp; ) { - worker_fiber * nxt = static_cast(f->nxt_); - if ( fn( f, now) ) + worker_fiber *f = static_cast(*fp); + + if (! fn(f, now)) { - if ( f == static_cast(head_)) - { - BOOST_ASSERT( 0 == prev); - - head_ = nxt; - } - else - { - BOOST_ASSERT( 0 != prev); - - prev->nxt_ = nxt; - } - f->nxt_ = 0; - f->time_point_reset(); - sched_algo->awakened( f); + // If f does NOT meet caller's criteria, skip fp past it. + fp = &(*fp)->nxt_; } else - prev = f; - f = nxt; + { + // Here f satisfies our caller. Unlink it from the list. + *fp = (*fp)->nxt_; + f->nxt_ = 0; + // Pass the newly-unlinked worker_fiber* to sched_algo. + f->time_point_reset(); + sched_algo->awakened(f); + } } } From 3cb43d6b9dcee11eac54f3e81931fd084186dbfa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 08:49:46 -0500 Subject: [PATCH 08/30] Make bounded_queue::tail_ a ptr* to simplify appending new nodes. --- include/boost/fiber/bounded_queue.hpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/include/boost/fiber/bounded_queue.hpp b/include/boost/fiber/bounded_queue.hpp index 1e484086..affa325a 100644 --- a/include/boost/fiber/bounded_queue.hpp +++ b/include/boost/fiber/bounded_queue.hpp @@ -93,7 +93,7 @@ private: state_t state_; std::size_t count_; typename node_type::ptr head_; - typename node_type::ptr tail_; + typename node_type::ptr * tail_; mutable mutex mtx_; condition not_empty_cond_; condition not_full_cond_; @@ -171,13 +171,8 @@ private: void push_tail_( typename node_type::ptr new_node) { - if ( is_empty_() ) - head_ = tail_ = new_node; - else - { - tail_->next = new_node; - tail_ = new_node; - } + *tail_ = new_node; + tail_ = &new_node->next; ++count_; } @@ -210,7 +205,7 @@ private: { typename node_type::ptr old_head = head_; head_ = old_head->next; - if ( 0 == head_) tail_ = 0; + if ( 0 == head_) tail_ = &head_; old_head->next = 0; return old_head; } @@ -222,7 +217,7 @@ public: state_( OPEN), count_( 0), head_(), - tail_( head_), + tail_( &head_), mtx_(), not_empty_cond_(), not_full_cond_(), @@ -240,7 +235,7 @@ public: state_( OPEN), count_( 0), head_(), - tail_( head_), + tail_( &head_), mtx_(), not_empty_cond_(), not_full_cond_(), From 84c2c6abadf5e0f125a276a1133130227b2452dc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 08:56:06 -0500 Subject: [PATCH 09/30] Make unbounded_queue::tail_ a ptr* to simplify linking new nodes. --- include/boost/fiber/unbounded_queue.hpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/include/boost/fiber/unbounded_queue.hpp b/include/boost/fiber/unbounded_queue.hpp index 20e08b40..a199d04d 100644 --- a/include/boost/fiber/unbounded_queue.hpp +++ b/include/boost/fiber/unbounded_queue.hpp @@ -90,7 +90,7 @@ private: state state_; typename node_type::ptr head_; - typename node_type::ptr tail_; + typename node_type::ptr * tail_; mutable mutex mtx_; condition not_empty_cond_; @@ -132,13 +132,8 @@ private: void push_tail_( typename node_type::ptr new_node) { - if ( is_empty_() ) - head_ = tail_ = new_node; - else - { - tail_->next = new_node; - tail_ = new_node; - } + *tail_ = new_node; + tail_ = &new_node->next; } value_type value_pop_() @@ -161,7 +156,7 @@ private: { typename node_type::ptr old_head = head_; head_ = old_head->next; - if ( 0 == head_) tail_ = 0; + if ( 0 == head_) tail_ = &head_; old_head->next = 0; return old_head; } @@ -170,7 +165,7 @@ public: unbounded_queue() : state_( OPEN), head_(), - tail_( head_), + tail_( &head_), mtx_(), not_empty_cond_() {} From 3cb5b2a3412867f38cf58ed3dbbb852d3f740cdf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 09:53:25 -0500 Subject: [PATCH 10/30] Remove thread_affinity flag and access methods. Specificaly, remove access methods in worker_fiber, fiber and this_fiber. thread_affinity is not used by any present library code. It was intended for use by workstealing user sched_algorithm implementations. The properties mechanism is a better way to address scheduler-specific properties. --- include/boost/fiber/detail/flags.hpp | 3 +-- include/boost/fiber/detail/worker_fiber.hpp | 5 ----- include/boost/fiber/fiber.hpp | 4 ---- include/boost/fiber/operations.hpp | 15 --------------- src/detail/worker_fiber.cpp | 9 --------- src/fiber.cpp | 16 ---------------- 6 files changed, 1 insertion(+), 51 deletions(-) diff --git a/include/boost/fiber/detail/flags.hpp b/include/boost/fiber/detail/flags.hpp index 559990e7..b0ea7b60 100644 --- a/include/boost/fiber/detail/flags.hpp +++ b/include/boost/fiber/detail/flags.hpp @@ -23,8 +23,7 @@ enum flag_t { flag_interruption_blocked = 1 << 0, flag_interruption_requested = 1 << 1, - flag_thread_affinity = 1 << 2, - flag_detached = 1 << 3 + flag_detached = 1 << 2 }; }}} diff --git a/include/boost/fiber/detail/worker_fiber.hpp b/include/boost/fiber/detail/worker_fiber.hpp index fc844fae..4108a9aa 100644 --- a/include/boost/fiber/detail/worker_fiber.hpp +++ b/include/boost/fiber/detail/worker_fiber.hpp @@ -129,11 +129,6 @@ public: void request_interruption( bool req) BOOST_NOEXCEPT; - bool thread_affinity() const BOOST_NOEXCEPT - { return 0 != ( flags_.load() & flag_thread_affinity); } - - void thread_affinity( bool req) BOOST_NOEXCEPT; - bool is_terminated() const BOOST_NOEXCEPT { return TERMINATED == state_; } diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index b1b3885d..6b7411b1 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -312,10 +312,6 @@ public: void priority( int) BOOST_NOEXCEPT; - bool thread_affinity() const BOOST_NOEXCEPT; - - void thread_affinity( bool) BOOST_NOEXCEPT; - void detach() BOOST_NOEXCEPT; void join(); diff --git a/include/boost/fiber/operations.hpp b/include/boost/fiber/operations.hpp index 955d135e..8c24bb2f 100644 --- a/include/boost/fiber/operations.hpp +++ b/include/boost/fiber/operations.hpp @@ -64,21 +64,6 @@ template< typename Rep, typename Period > void sleep_for( chrono::duration< Rep, Period > const& timeout_duration) { sleep_until( chrono::high_resolution_clock::now() + timeout_duration); } -inline -bool thread_affinity() BOOST_NOEXCEPT -{ - return 0 != fibers::fm_active() - ? fibers::fm_active()->thread_affinity() - : true; -} - -inline -void thread_affinity( bool req) BOOST_NOEXCEPT -{ - if ( 0 != fibers::fm_active() ) - fibers::fm_active()->thread_affinity( req); -} - template < class PROPS > PROPS& properties() { diff --git a/src/detail/worker_fiber.cpp b/src/detail/worker_fiber.cpp index a98a0c2e..4483c3fa 100644 --- a/src/detail/worker_fiber.cpp +++ b/src/detail/worker_fiber.cpp @@ -97,15 +97,6 @@ worker_fiber::request_interruption( bool req) BOOST_NOEXCEPT flags_ &= ~flag_interruption_requested; } -void -worker_fiber::thread_affinity( bool req) BOOST_NOEXCEPT -{ - if ( req) - flags_ |= flag_thread_affinity; - else - flags_ &= ~flag_thread_affinity; -} - void * worker_fiber::get_fss_data( void const* vp) const { diff --git a/src/fiber.cpp b/src/fiber.cpp index f70b798c..1c2a9883 100644 --- a/src/fiber.cpp +++ b/src/fiber.cpp @@ -44,22 +44,6 @@ fiber::priority( int prio) BOOST_NOEXCEPT fm_priority( impl_.get(), prio); } -bool -fiber::thread_affinity() const BOOST_NOEXCEPT -{ - BOOST_ASSERT( impl_); - - return impl_->thread_affinity(); -} - -void -fiber::thread_affinity( bool req) BOOST_NOEXCEPT -{ - BOOST_ASSERT( impl_); - - impl_->thread_affinity( req); -} - void fiber::join() { From 757d692cae0d2acdcae623abb60014f2fb946475 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 16:15:25 -0500 Subject: [PATCH 11/30] Re-add thread_affinity specific to workstealing_round_robin. thread_affinity is a good example of a property relevant only to a particular sched_algorithm implementation. In examples/cpp03/migration, introduce an 'affinity' subclass of fiber_properties with a thread_affinity data member. Derive workstealing_round_robin from sched_algorithm_with_properties and, as required by that base class, forward awakened() calls to base-class awakened() method. Reimplement workstealing_round_robin's queue from a std::deque to a "by hand" intrusive singly-linked list so we can efficiently remove an arbitrary item. Make steal() method, instead of always popping the last item, scan the list to find the last item willing to migrate (! thread_affinity). From examples/cpp03/migration/workstealing_round_robin.hpp, an example of a user-supplied sched_algorithm implementation, remove all boost/fiber/detail #includes. These should no longer be needed. Change sched_algorithm_with_properties::properties(worker_fiber*) method to accept fiber_base* instead. The original signature was introduced when every sched_algorithm implementation necessarily manipulated worker_fiber* pointers. Now we're intentionally avoiding the need. For the same reason, introduce a fiber_properties::back_ptr typedef so subclasses can opaquely pass such pointers through their own constructor to the base-class constructor. --- .../migration/workstealing_round_robin.cpp | 52 +++++++++++++++---- .../migration/workstealing_round_robin.hpp | 29 ++++++++--- include/boost/fiber/algorithm.hpp | 4 +- include/boost/fiber/properties.hpp | 8 +-- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/examples/cpp03/migration/workstealing_round_robin.cpp b/examples/cpp03/migration/workstealing_round_robin.cpp index 0ad78ee4..78e56191 100644 --- a/examples/cpp03/migration/workstealing_round_robin.cpp +++ b/examples/cpp03/migration/workstealing_round_robin.cpp @@ -12,11 +12,22 @@ # include BOOST_ABI_PREFIX #endif +workstealing_round_robin::workstealing_round_robin(): + rhead_(0), + rtail_(&rhead_) +{} + void workstealing_round_robin::awakened( boost::fibers::fiber_base * f) { + // forward this call to base-class method + boost::fibers::sched_algorithm_with_properties::awakened(f); + boost::mutex::scoped_lock lk( mtx_); - rqueue_.push_back( f); + // append this fiber_base* to ready queue + BOOST_ASSERT(! f->nxt_); + *rtail_ = f; + rtail_ = &f->nxt_; } boost::fibers::fiber_base * @@ -24,10 +35,15 @@ workstealing_round_robin::pick_next() { boost::mutex::scoped_lock lk( mtx_); boost::fibers::fiber_base * f = 0; - if ( ! rqueue_.empty() ) + if ( rhead_ ) { - f = rqueue_.front(); - rqueue_.pop_front(); + f = rhead_; + // pop head item from ready queue + rhead_ = rhead_->nxt_; + f->nxt_ = 0; + // if that was the last item, reset tail_ + if (! rhead_) + rtail_ = &rhead_; } return f; } @@ -46,13 +62,31 @@ boost::fibers::fiber workstealing_round_robin::steal() BOOST_NOEXCEPT { boost::mutex::scoped_lock lk( mtx_); - boost::fibers::fiber_base * f = 0; - if ( ! rqueue_.empty() ) + + // Search the queue for the LAST fiber_base that's willing to migrate, + // in other words (! thread_affinity). + boost::fibers::fiber_base ** fp = &rhead_, ** found = 0; + for ( ; *fp; fp = &(*fp)->nxt_) { - f = rqueue_.back(); - rqueue_.pop_back(); + // do not consider any fiber whose thread_affinity is set + if (! properties(*fp).thread_affinity) + found = fp; } - return boost::fibers::fiber( f); + if (! found) + { + // either the queue is completely empty or all current entries have + // thread_affinity set + return boost::fibers::fiber(static_cast(0)); + } + // We found at least one fiber_base whose thread_affinity is NOT set; + // *found points to the last of these. Unlink and return it. + boost::fibers::fiber_base* ret = *found; + *found = ret->nxt_; + ret->nxt_ = 0; + // if that was the last item, reset tail_ + if (! *found) + rtail_ = &rhead_; + return boost::fibers::fiber(ret); } #ifdef BOOST_HAS_ABI_HEADERS diff --git a/examples/cpp03/migration/workstealing_round_robin.hpp b/examples/cpp03/migration/workstealing_round_robin.hpp index 7c70fd71..bf9dc1f5 100644 --- a/examples/cpp03/migration/workstealing_round_robin.hpp +++ b/examples/cpp03/migration/workstealing_round_robin.hpp @@ -11,11 +11,8 @@ #include #include -#include -#include -#include #include -#include +#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -26,15 +23,31 @@ # pragma warning(disable:4251 4275) # endif -class workstealing_round_robin : public boost::fibers::sched_algorithm +struct affinity: public boost::fibers::fiber_properties +{ + affinity(boost::fibers::fiber_properties::back_ptr p): + fiber_properties(p), + // By default, assume a given fiber CAN migrate to another thread. + thread_affinity(false) + {} + + bool thread_affinity; +}; + +class workstealing_round_robin : + public boost::fibers::sched_algorithm_with_properties { private: - typedef std::deque< boost::fibers::fiber_base * > rqueue_t; - boost::mutex mtx_; - rqueue_t rqueue_; + + // We should package these as a queue class. Better yet, we should + // refactor one of our existing (intrusive) queue classes to support the + // required operations generically. But for now... + boost::fibers::fiber_base *rhead_, **rtail_; public: + workstealing_round_robin(); + virtual void awakened( boost::fibers::fiber_base *); virtual boost::fibers::fiber_base * pick_next(); diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 007b6cd1..80510d1a 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -71,9 +71,9 @@ public: } // used for all internal calls - PROPS& properties(detail::worker_fiber* f) + PROPS& properties(fiber_base* f) { - return static_cast(*f->get_properties()); + return static_cast(*static_cast(f)->get_properties()); } // public-facing properties(fiber::id) method in case consumer retains a diff --git a/include/boost/fiber/properties.hpp b/include/boost/fiber/properties.hpp index e13da8c3..5ae52eab 100644 --- a/include/boost/fiber/properties.hpp +++ b/include/boost/fiber/properties.hpp @@ -45,9 +45,11 @@ protected: public: // fiber_properties, and by implication every subclass, must accept a back - // pointer to its worker_fiber. Any specific property setter method, after - // updating the relevant instance variable, can/should call notify(). - fiber_properties(detail::worker_fiber* f): + // pointer to its worker_fiber. + typedef detail::worker_fiber* back_ptr; + // Any specific property setter method, after updating the relevant + // instance variable, can/should call notify(). + fiber_properties(back_ptr f): fiber_(f), sched_algo_(0) {} From f1a34d297f2ba936e3b5fa23b1d4d0e968054c71 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 11 Nov 2014 18:00:18 -0500 Subject: [PATCH 12/30] Remove 'priority' for every fiber, and its support methods. Priority is another property that's only relevant for future sched_algorithm implementations. We don't even have an example yet. It's a good candidate for moving to a specific fiber_properties subclass for that specific sched_algorithm implementation. --- .../cpp03/migration/workstealing_round_robin.cpp | 10 ---------- .../cpp03/migration/workstealing_round_robin.hpp | 2 -- include/boost/fiber/algorithm.hpp | 2 -- include/boost/fiber/detail/worker_fiber.hpp | 7 ------- include/boost/fiber/fiber.hpp | 4 ---- include/boost/fiber/fiber_manager.hpp | 2 -- include/boost/fiber/round_robin.hpp | 2 -- src/detail/worker_fiber.cpp | 1 - src/fiber.cpp | 16 ---------------- src/fiber_manager.cpp | 10 ---------- src/round_robin.cpp | 10 ---------- test/test_fiber.cpp | 10 ---------- 12 files changed, 76 deletions(-) diff --git a/examples/cpp03/migration/workstealing_round_robin.cpp b/examples/cpp03/migration/workstealing_round_robin.cpp index 78e56191..fb79afa1 100644 --- a/examples/cpp03/migration/workstealing_round_robin.cpp +++ b/examples/cpp03/migration/workstealing_round_robin.cpp @@ -48,16 +48,6 @@ workstealing_round_robin::pick_next() return f; } -void -workstealing_round_robin::priority( boost::fibers::fiber_base * f, int prio) BOOST_NOEXCEPT -{ - BOOST_ASSERT( f); - - // set only priority to fiber - // round-robin does not respect priorities - static_cast(f)->priority( prio); -} - boost::fibers::fiber workstealing_round_robin::steal() BOOST_NOEXCEPT { diff --git a/examples/cpp03/migration/workstealing_round_robin.hpp b/examples/cpp03/migration/workstealing_round_robin.hpp index bf9dc1f5..e1fb2d87 100644 --- a/examples/cpp03/migration/workstealing_round_robin.hpp +++ b/examples/cpp03/migration/workstealing_round_robin.hpp @@ -52,8 +52,6 @@ public: virtual boost::fibers::fiber_base * pick_next(); - virtual void priority( boost::fibers::fiber_base *, int) BOOST_NOEXCEPT; - boost::fibers::fiber steal(); }; diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 80510d1a..848436d7 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -36,8 +36,6 @@ struct sched_algorithm virtual fiber_base * pick_next() = 0; - virtual void priority( fiber_base *, int) BOOST_NOEXCEPT = 0; - virtual void property_change( fiber_base *, fiber_properties* ) {} }; diff --git a/include/boost/fiber/detail/worker_fiber.hpp b/include/boost/fiber/detail/worker_fiber.hpp index 4108a9aa..ef9d03d1 100644 --- a/include/boost/fiber/detail/worker_fiber.hpp +++ b/include/boost/fiber/detail/worker_fiber.hpp @@ -97,7 +97,6 @@ private: coro_t::call_type caller_; atomic< state_t > state_; atomic< int > flags_; - atomic< int > priority_; exception_ptr except_; spinlock splk_; std::vector< worker_fiber * > waiting_; @@ -111,12 +110,6 @@ public: id get_id() const BOOST_NOEXCEPT { return id( const_cast< worker_fiber * >( this) ); } - int priority() const BOOST_NOEXCEPT - { return priority_; } - - void priority( int prio) BOOST_NOEXCEPT - { priority_ = prio; } - bool join( worker_fiber *); bool interruption_blocked() const BOOST_NOEXCEPT diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 6b7411b1..0261e5f5 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -308,10 +308,6 @@ public: id get_id() const BOOST_NOEXCEPT { return 0 != impl_ ? impl_->get_id() : id(); } - int priority() const BOOST_NOEXCEPT; - - void priority( int) BOOST_NOEXCEPT; - void detach() BOOST_NOEXCEPT; void join(); diff --git a/include/boost/fiber/fiber_manager.hpp b/include/boost/fiber/fiber_manager.hpp index 5a233497..5a8ff6a6 100644 --- a/include/boost/fiber/fiber_manager.hpp +++ b/include/boost/fiber/fiber_manager.hpp @@ -58,8 +58,6 @@ sched_algorithm* fm_get_sched_algo_(); void fm_spawn( detail::worker_fiber *); -void fm_priority( detail::worker_fiber *, int) BOOST_NOEXCEPT; - void fm_wait_interval( chrono::high_resolution_clock::duration const&) BOOST_NOEXCEPT; template< typename Rep, typename Period > void fm_wait_interval( chrono::duration< Rep, Period > const& wait_interval) BOOST_NOEXCEPT diff --git a/include/boost/fiber/round_robin.hpp b/include/boost/fiber/round_robin.hpp index 01ce10fe..6892a57f 100644 --- a/include/boost/fiber/round_robin.hpp +++ b/include/boost/fiber/round_robin.hpp @@ -36,8 +36,6 @@ public: virtual void awakened( fiber_base *); virtual fiber_base * pick_next(); - - virtual void priority( fiber_base *, int) BOOST_NOEXCEPT; }; }} diff --git a/src/detail/worker_fiber.cpp b/src/detail/worker_fiber.cpp index 4483c3fa..6843ae58 100644 --- a/src/detail/worker_fiber.cpp +++ b/src/detail/worker_fiber.cpp @@ -35,7 +35,6 @@ worker_fiber::worker_fiber( coro_t::yield_type * callee) : caller_(), state_( READY), flags_( 0), - priority_( 0), except_(), waiting_(), properties_(0) diff --git a/src/fiber.cpp b/src/fiber.cpp index 1c2a9883..77bd7a93 100644 --- a/src/fiber.cpp +++ b/src/fiber.cpp @@ -28,22 +28,6 @@ fiber::start_() fm_spawn( impl_.get() ); } -int -fiber::priority() const BOOST_NOEXCEPT -{ - BOOST_ASSERT( impl_); - - return impl_->priority(); -} - -void -fiber::priority( int prio) BOOST_NOEXCEPT -{ - BOOST_ASSERT( impl_); - - fm_priority( impl_.get(), prio); -} - void fiber::join() { diff --git a/src/fiber_manager.cpp b/src/fiber_manager.cpp index be22b478..8f738891 100644 --- a/src/fiber_manager.cpp +++ b/src/fiber_manager.cpp @@ -138,16 +138,6 @@ void fm_spawn( detail::worker_fiber * f) fm->sched_algo_->awakened( f); } -void fm_priority( detail::worker_fiber * f, - int prio) BOOST_NOEXCEPT -{ - fiber_manager * fm = detail::scheduler::instance(); - - BOOST_ASSERT( 0 != fm); - - fm->sched_algo_->priority( f, prio); -} - void fm_wait_interval( chrono::high_resolution_clock::duration const& wait_interval) BOOST_NOEXCEPT { fiber_manager * fm = detail::scheduler::instance(); diff --git a/src/round_robin.cpp b/src/round_robin.cpp index c81d361e..f8f019d1 100644 --- a/src/round_robin.cpp +++ b/src/round_robin.cpp @@ -32,16 +32,6 @@ round_robin::pick_next() return victim; } -void -round_robin::priority( fiber_base * f, int prio) BOOST_NOEXCEPT -{ - BOOST_ASSERT( f); - - // set only priority to fiber - // round-robin does not respect priorities - static_cast(f)->priority( prio); -} - }} #ifdef BOOST_HAS_ABI_HEADERS diff --git a/test/test_fiber.cpp b/test/test_fiber.cpp index 7df80941..1fde5f95 100644 --- a/test/test_fiber.cpp +++ b/test/test_fiber.cpp @@ -179,15 +179,6 @@ void test_move() } } -void test_priority() -{ - boost::fibers::fiber f( f1); - BOOST_CHECK_EQUAL( 0, f.priority() ); - f.priority( 7); - BOOST_CHECK_EQUAL( 7, f.priority() ); - f.join(); -} - void test_id() { boost::fibers::fiber s1; @@ -353,7 +344,6 @@ boost::unit_test::test_suite * init_unit_test_suite( int, char* []) test->add( BOOST_TEST_CASE( & test_move) ); test->add( BOOST_TEST_CASE( & test_id) ); - test->add( BOOST_TEST_CASE( & test_priority) ); test->add( BOOST_TEST_CASE( & test_detach) ); test->add( BOOST_TEST_CASE( & test_complete) ); test->add( BOOST_TEST_CASE( & test_join_in_thread) ); From 35958578490eb81009d666dfa86a1aa441a295ab Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 14 Nov 2014 09:58:48 -0500 Subject: [PATCH 13/30] property_change() method doesn't really belong in sched_algorithm. sched_algorithm knows nothing about properties, so that class isn't really the best place for the property_change() virtual method. Introduce intermediate sched_algorithm_with_properties_base class, which introduces property_change_() virtual method accepting fiber_properties*. Then the properties-specific sched_algorithm_with_properties implementation calls property_change() (no trailing underscore) with fiber_properties* cast to PROPS&. Thus the user- coded sched_algorithm implementation can override property_change() and accept PROPS& rather than the generic fiber_properties* pointer. But fiber_properties::notify() -- which doesn't know its own PROPS subclass -- can nonetheless call sched_algorithm_with_properties_base::property_change_(). --- include/boost/fiber/algorithm.hpp | 20 +++++++++++++++++--- src/properties.cpp | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 848436d7..d74235e7 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -35,8 +35,6 @@ struct sched_algorithm virtual void awakened( fiber_base *) = 0; virtual fiber_base * pick_next() = 0; - - virtual void property_change( fiber_base *, fiber_properties* ) {} }; namespace detail { @@ -45,8 +43,15 @@ inline fiber_base* extract_base(fiber_base::id id) { return id.impl_; } } // detail +struct sched_algorithm_with_properties_base: public sched_algorithm +{ + // called by fiber_properties::notify() -- don't directly call + virtual void property_change_( fiber_base *f, fiber_properties* props ) = 0; +}; + template -struct sched_algorithm_with_properties: public sched_algorithm +struct sched_algorithm_with_properties: + public sched_algorithm_with_properties_base { public: typedef sched_algorithm_with_properties super; @@ -81,6 +86,15 @@ public: return properties(extract(id)); } + // override this to be notified by PROPS::notify() + virtual void property_change( fiber_base* f, PROPS& props) {} + + // implementation for sched_algorithm_with_properties_base method + void property_change_( fiber_base *f, fiber_properties* props ) + { + property_change(f, *static_cast(props)); + } + private: // support sched_algorithm_with_properties::properties(fiber::id) detail::worker_fiber* extract(detail::worker_fiber::id id) diff --git a/src/properties.cpp b/src/properties.cpp index 226c7441..faa04ab0 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -18,7 +18,7 @@ namespace fibers { void fiber_properties::notify() { BOOST_ASSERT(sched_algo_); - sched_algo_->property_change(fiber_, this); + static_cast(sched_algo_)->property_change_(fiber_, this); } }} // boost::fiber From c9e4ec881b1ffd9ef546a865193a88c909665f8f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Nov 2014 10:20:52 -0500 Subject: [PATCH 14/30] Fix bug in fiber::properties() method. fm_properties() doesn't accept a boost::intrusive_ptr. Pass raw worker_fiber* instead. --- include/boost/fiber/fiber.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 0261e5f5..9459a0d0 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -317,7 +317,7 @@ public: template PROPS& properties() { - return fm_properties(impl_); + return fm_properties(impl_.get()); } }; From 95812f83211177bf97207a2e93e74a1de298695f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Nov 2014 10:25:31 -0500 Subject: [PATCH 15/30] Only notify() a sched_algorithm_with_properties if it is_ready(). While it's true that not every READY fiber is always in the scheduler's ready queue -- there's a time window in which a READY fiber can be on the fiber_manager's wait queue -- only READY fibers will ever be on the scheduler's ready queue. So if (! fiber.is_ready()), there's no point in bothering the sched_algorithm implementation with a change to its properties. --- src/properties.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/properties.cpp b/src/properties.cpp index faa04ab0..4a044ac1 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -18,10 +18,20 @@ namespace fibers { void fiber_properties::notify() { BOOST_ASSERT(sched_algo_); - static_cast(sched_algo_)->property_change_(fiber_, this); + // Application code might change an important property for any fiber at + // any time. The fiber in question might be ready, running or waiting. + // Significantly, only a fiber which is ready but not actually running is + // in the sched_algorithm's ready queue. Don't bother the sched_algorithm + // with a change to a fiber it's not currently tracking: it will do the + // right thing next time the fiber is passed to its awakened() method. + if (fiber_->is_ready()) + { + static_cast(sched_algo_)-> + property_change_(fiber_, this); + } } -}} // boost::fiber +}} // boost::fibers #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_SUFFIX From cb7b5ddd2584f6b388331836179039fbb14cf9ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Nov 2014 10:40:31 -0500 Subject: [PATCH 16/30] Introduce sched_algorithm_with_properties::awakened_props() method. Every sched_algorithm_with_properties subclass awakened() call must ensure that control reaches sched_algorithm_with_properties::awakened() _before_ any logic in the subclass method attempts to access properties. This turns out to be all too easy to forget. So instead, advise subclasses to overrride new awakened_props() method. Base- class method sets things up and then calls awakened_props(). Moreover, when the compiler supports it, sched_algorithm_with_properties::awakened() is now marked 'final' to remind subclass authors to override awakened_props() instead. --- .../migration/workstealing_round_robin.cpp | 5 +---- .../migration/workstealing_round_robin.hpp | 2 +- include/boost/fiber/algorithm.hpp | 20 +++++++++++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/cpp03/migration/workstealing_round_robin.cpp b/examples/cpp03/migration/workstealing_round_robin.cpp index fb79afa1..a6af2a2f 100644 --- a/examples/cpp03/migration/workstealing_round_robin.cpp +++ b/examples/cpp03/migration/workstealing_round_robin.cpp @@ -18,11 +18,8 @@ workstealing_round_robin::workstealing_round_robin(): {} void -workstealing_round_robin::awakened( boost::fibers::fiber_base * f) +workstealing_round_robin::awakened_props( boost::fibers::fiber_base * f) { - // forward this call to base-class method - boost::fibers::sched_algorithm_with_properties::awakened(f); - boost::mutex::scoped_lock lk( mtx_); // append this fiber_base* to ready queue BOOST_ASSERT(! f->nxt_); diff --git a/examples/cpp03/migration/workstealing_round_robin.hpp b/examples/cpp03/migration/workstealing_round_robin.hpp index e1fb2d87..a6278453 100644 --- a/examples/cpp03/migration/workstealing_round_robin.hpp +++ b/examples/cpp03/migration/workstealing_round_robin.hpp @@ -48,7 +48,7 @@ private: public: workstealing_round_robin(); - virtual void awakened( boost::fibers::fiber_base *); + virtual void awakened_props( boost::fibers::fiber_base *); virtual boost::fibers::fiber_base * pick_next(); diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index d74235e7..b919b10f 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -13,6 +13,14 @@ #include #include +#ifndef BOOST_FINAL +# ifdef BOOST_NO_CXX11_FINAL +# define BOOST_FINAL +# else +# define BOOST_FINAL final +# endif +#endif + #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX #endif @@ -56,9 +64,11 @@ struct sched_algorithm_with_properties: public: typedef sched_algorithm_with_properties super; - // Start every subclass awakened() override with: + // On C++11 compilers, mark this override 'final': 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); - virtual void awakened( fiber_base *fb) + virtual void awakened( fiber_base *fb) BOOST_FINAL { detail::worker_fiber* f = static_cast(fb); if (! f->get_properties()) @@ -71,8 +81,14 @@ public: // handles the case of a fiber migrating to a new thread with a new // sched_algorithm subclass instance. f->get_properties()->set_sched_algorithm(this); + + // Okay, now forward the call to subclass override. + awakened_props(fb); } + // subclasses override this method instead of the original awakened() + virtual void awakened_props( fiber_base *fb) {} + // used for all internal calls PROPS& properties(fiber_base* f) { From b5a75be2a01824fab54322b828183b2b3d6192cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Nov 2014 10:47:06 -0500 Subject: [PATCH 17/30] Add priority.cpp example program. This illustrates use of a sched_algorithm_with_properties subclass that defines, and supports, a priority property. The example includes a (somewhat contrived) case in which a fiber must be moved within the scheduler's ready queue due to a priority change. --- examples/cpp03/Jamfile.v2 | 1 + examples/cpp03/priority.cpp | 292 ++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 examples/cpp03/priority.cpp diff --git a/examples/cpp03/Jamfile.v2 b/examples/cpp03/Jamfile.v2 index 22b73ec6..325e4acc 100644 --- a/examples/cpp03/Jamfile.v2 +++ b/examples/cpp03/Jamfile.v2 @@ -36,6 +36,7 @@ exe futures_mt : futures_mt.cpp ; exe interrupt : interrupt.cpp ; exe join : join.cpp ; exe ping_pong : ping_pong.cpp ; +exe priority : priority.cpp ; exe segmented_stack : segmented_stack.cpp ; exe simple : simple.cpp test_fiber.cpp ; exe migrate_fibers diff --git a/examples/cpp03/priority.cpp b/examples/cpp03/priority.cpp new file mode 100644 index 00000000..1de01975 --- /dev/null +++ b/examples/cpp03/priority.cpp @@ -0,0 +1,292 @@ +// Copyright Nat Goodspeed 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include + +class priority_props: public boost::fibers::fiber_properties +{ +public: + priority_props(boost::fibers::fiber_properties::back_ptr p): + fiber_properties(p), + priority_(0) + {} + + int get_priority() const { return priority_; } + + // Call this method to alter priority, because we must notify + // priority_scheduler of any change. + void set_priority(int p) + { + // Of course, it's only worth reshuffling the queue and all if we're + // actually changing the priority. + if (p != priority_) + { + priority_ = p; + notify(); + } + } + + // The fiber name of course is solely for purposes of this example + // program; it has nothing to do with implementing scheduler priority. + // This is a public data member -- not requiring set/get access methods -- + // because we need not inform the scheduler of any change. + std::string name; + +private: + int priority_; +}; + +class priority_scheduler: + public boost::fibers::sched_algorithm_with_properties +{ +private: + // Much as we would like, we don't use std::priority_queue because it + // doesn't appear to provide any way to alter the priority (and hence + // queue position) of a particular item. + boost::fibers::fiber_base* head_; + +public: + priority_scheduler(): + head_(0) + {} + + // For a subclass of sched_algorithm_with_properties<>, it's important to + // override awakened_props(), NOT awakened(). + virtual void awakened_props(boost::fibers::fiber_base* f) + { + int f_priority = properties(f).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 + // we're handed a new fiber_base, put it at the end of the fibers with + // that same priority. In other words: search for the first fiber in + // the queue with LOWER priority, and insert before that one. + boost::fibers::fiber_base** fp = &head_; + for ( ; *fp; fp = &(*fp)->nxt_) + if (properties(*fp).get_priority() < f_priority) + break; + // It doesn't matter whether we hit the end of the list or found + // another fiber with lower priority. Either way, insert f here. + f->nxt_ = *fp; + *fp = f; + + std::cout << "awakened(" << properties(f).name << "): "; + describe_ready_queue(); + } + + virtual boost::fibers::fiber_base* pick_next() + { + // if ready queue is empty, just tell caller + if (! head_) + return 0; + // Here we have at least one ready fiber. Unlink and return that. + boost::fibers::fiber_base* f = head_; + head_ = f->nxt_; + f->nxt_ = 0; + + std::cout << "pick_next() resuming " << properties(f).name << ": "; + describe_ready_queue(); + return f; + } + + virtual void property_change(boost::fibers::fiber_base* f, priority_props& props) + { + // Although our priority_props class defines multiple properties, only + // one of them (priority) actually calls notify() when changed. The + // point of a property_change() override is to reshuffle the ready + // queue according to the updated priority value. + std::cout << "property_change(" << props.name << '(' << props.get_priority() + << ")): "; + + // Despite the added complexity of the loop body, make a single pass + // over the queue to find both the existing item and the new desired + // insertion point. + bool found = false; + boost::fibers::fiber_base **insert = 0, **fp = &head_; + for ( ; *fp; fp = &(*fp)->nxt_) + { + if (*fp == f) + { + // found the passed fiber in our list -- unlink it + found = true; + *fp = (*fp)->nxt_; + f->nxt_ = 0; + // If that was the last item in the list, stop. + if (! *fp) + break; + // If we've already found the new insertion point, no need to + // continue looping. + if (insert) + break; + } + // As in awakened(), we're looking for the first fiber in the + // queue with priority lower than the passed fiber. + if (properties(*fp).get_priority() < props.get_priority()) + { + insert = fp; + // If we've already found and unlinked the passed fiber, no + // need to continue looping. + if (found) + break; + } + } + // property_change() should only be called if f->is_ready(). However, + // a waiting fiber can change state to is_ready() while still on the + // fiber_manager's waiting queue. Every such fiber will be swept onto + // our ready queue before the next pick_next() call, but still it's + // possible to get a property_change() call for a fiber that + // is_ready() but is not yet on our ready queue. If it's not there, no + // action required: we'll handle it next time it hits awakened(). + if (! found) + { + // hopefully user will distinguish this case by noticing that + // the fiber with which we were called does not appear in the + // ready queue at all + describe_ready_queue(); + return; + } + // There might not be any ready fibers with lower priority. In that + // case, append to the end of the queue. + std::string where; + if (insert) + where = std::string("before ") + properties(*insert).name; + else + { + insert = fp; + where = "to end"; + } + // Insert f at the new insertion point in the queue. + f->nxt_ = *insert; + *insert = f; + + std::cout << "moving " << where << ": "; + describe_ready_queue(); + } + + void describe_ready_queue() + { + if (! head_) + std::cout << "[empty]"; + else + { + const char* delim = ""; + for (boost::fibers::fiber_base *f = head_; f; f = f->nxt_) + { + priority_props& props(properties(f)); + std::cout << delim << props.name << '(' << props.get_priority() << ')'; + delim = ", "; + } + } + std::cout << std::endl; + } +}; + +void init(const std::string& name, int priority) +{ + priority_props& props(boost::this_fiber::properties()); + props.name = name; + props.set_priority(priority); +} + +void yield_fn(const std::string& name, int priority) +{ + init(name, priority); + + for (int i = 0; i < 3; ++i) + { + std::cout << "fiber " << name << " running" << std::endl; + boost::this_fiber::yield(); + } +} + +void barrier_fn(const std::string& name, int priority, boost::fibers::barrier& barrier) +{ + init(name, priority); + + std::cout << "fiber " << name << " waiting on barrier" << std::endl; + barrier.wait(); + std::cout << "fiber " << name << " yielding" << std::endl; + boost::this_fiber::yield(); + std::cout << "fiber " << name << " done" << std::endl; +} + +void change_fn(const std::string& name, int priority, + boost::fibers::fiber& other, int other_priority, + boost::fibers::barrier& barrier) +{ + init(name, priority); + + std::cout << "fiber " << name << " waiting on barrier" << std::endl; + barrier.wait(); + // We assume a couple things about 'other': + // - that it was also waiting on the same barrier + // - that it has lower priority than this fiber. + // If both are true, 'other' is now ready to run but is sitting in + // priority_scheduler's ready queue. Change its priority. + priority_props& other_props(other.properties()); + std::cout << "fiber " << name << " changing priority of " << other_props.name + << " to " << other_priority << std::endl; + other_props.set_priority(other_priority); + std::cout << "fiber " << name << " done" << std::endl; +} + +int main(int argc, char *argv[]) +{ + // make sure we use our priority_scheduler rather than default round_robin + priority_scheduler psched; + boost::fibers::set_scheduling_algorithm(&psched); + + { + // verify that high-priority fiber always gets scheduled first + boost::fibers::fiber low(boost::bind(yield_fn, "low", 1)); + boost::fibers::fiber med(boost::bind(yield_fn, "medium", 2)); + boost::fibers::fiber hi(boost::bind(yield_fn, "high", 3)); + hi.join(); + med.join(); + low.join(); + std::cout << std::endl; + } + + { + // fibers of same priority are scheduled in round-robin order + boost::fibers::fiber a(boost::bind(yield_fn, "a", 0)); + boost::fibers::fiber b(boost::bind(yield_fn, "b", 0)); + boost::fibers::fiber c(boost::bind(yield_fn, "c", 0)); + a.join(); + b.join(); + c.join(); + std::cout << std::endl; + } + + { + // using a barrier wakes up all waiting fibers at the same time + boost::fibers::barrier barrier(3); + boost::fibers::fiber low(boost::bind(barrier_fn, "low", 1, boost::ref(barrier))); + boost::fibers::fiber med(boost::bind(barrier_fn, "medium", 2, boost::ref(barrier))); + boost::fibers::fiber hi(boost::bind(barrier_fn, "high", 3, boost::ref(barrier))); + low.join(); + med.join(); + hi.join(); + std::cout << std::endl; + } + + { + // change priority of a fiber in priority_scheduler's ready queue + boost::fibers::barrier barrier(3); + boost::fibers::fiber c(boost::bind(barrier_fn, "c", 1, boost::ref(barrier))); + boost::fibers::fiber a(boost::bind(change_fn, "a", 3, + boost::ref(c), 3, boost::ref(barrier))); + boost::fibers::fiber b(boost::bind(barrier_fn, "b", 2, boost::ref(barrier))); + a.join(); + b.join(); + c.join(); + std::cout << std::endl; + } + + return 0; +} From 1bec058e773647c568b51e78fa99714db6bcbd7a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 10 May 2015 22:59:38 -0400 Subject: [PATCH 18/30] Move priority.cpp example program to main examples directory. Since the library no longer supports C++03, the examples/cpp03 subdirectory has gone away, along with the cpp11 subdirectory. --- examples/{cpp03 => }/priority.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{cpp03 => }/priority.cpp (100%) diff --git a/examples/cpp03/priority.cpp b/examples/priority.cpp similarity index 100% rename from examples/cpp03/priority.cpp rename to examples/priority.cpp From 9c4cdf3438e5f9ea913704a3ef4b02e73bb900bc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 10 May 2015 23:35:15 -0400 Subject: [PATCH 19/30] Desk-checking pass through fiber_properties source code. --- examples/priority.cpp | 38 +++++++++++++-------------- include/boost/fiber/fiber.hpp | 14 +++------- include/boost/fiber/fiber_manager.hpp | 8 +++--- include/boost/fiber/operations.hpp | 3 +-- include/boost/fiber/properties.hpp | 15 +++++------ src/fiber.cpp | 2 +- 6 files changed, 33 insertions(+), 47 deletions(-) diff --git a/examples/priority.cpp b/examples/priority.cpp index 1de01975..64ef60a4 100644 --- a/examples/priority.cpp +++ b/examples/priority.cpp @@ -13,7 +13,7 @@ class priority_props: public boost::fibers::fiber_properties public: priority_props(boost::fibers::fiber_properties::back_ptr p): fiber_properties(p), - priority_(0) + priority_(nullptr) {} int get_priority() const { return priority_; } @@ -48,16 +48,16 @@ private: // Much as we would like, we don't use std::priority_queue because it // doesn't appear to provide any way to alter the priority (and hence // queue position) of a particular item. - boost::fibers::fiber_base* head_; + boost::fibers::fiber_context* head_; public: priority_scheduler(): - head_(0) + head_(nullptr) {} // For a subclass of sched_algorithm_with_properties<>, it's important to // override awakened_props(), NOT awakened(). - virtual void awakened_props(boost::fibers::fiber_base* f) + virtual void awakened_props(boost::fibers::fiber_context* f) { int f_priority = properties(f).get_priority(); // With this scheduler, fibers with higher priority values are @@ -66,35 +66,35 @@ public: // we're handed a new fiber_base, put it at the end of the fibers with // that same priority. In other words: search for the first fiber in // the queue with LOWER priority, and insert before that one. - boost::fibers::fiber_base** fp = &head_; - for ( ; *fp; fp = &(*fp)->nxt_) + boost::fibers::fiber_context** fp = &head_; + for ( ; *fp; fp = &(*fp)->nxt) if (properties(*fp).get_priority() < f_priority) break; // It doesn't matter whether we hit the end of the list or found // another fiber with lower priority. Either way, insert f here. - f->nxt_ = *fp; + f->nxt = *fp; *fp = f; std::cout << "awakened(" << properties(f).name << "): "; describe_ready_queue(); } - virtual boost::fibers::fiber_base* pick_next() + virtual boost::fibers::fiber_context* pick_next() { // if ready queue is empty, just tell caller if (! head_) - return 0; + return nullptr; // Here we have at least one ready fiber. Unlink and return that. - boost::fibers::fiber_base* f = head_; - head_ = f->nxt_; - f->nxt_ = 0; + boost::fibers::fiber_context* f = head_; + head_ = f->nxt; + f->nxt = nullptr; std::cout << "pick_next() resuming " << properties(f).name << ": "; describe_ready_queue(); return f; } - virtual void property_change(boost::fibers::fiber_base* f, priority_props& props) + virtual void property_change(boost::fibers::fiber_context* f, priority_props& props) { // Although our priority_props class defines multiple properties, only // one of them (priority) actually calls notify() when changed. The @@ -107,15 +107,15 @@ public: // over the queue to find both the existing item and the new desired // insertion point. bool found = false; - boost::fibers::fiber_base **insert = 0, **fp = &head_; - for ( ; *fp; fp = &(*fp)->nxt_) + boost::fibers::fiber_context **insert = nullptr, **fp = &head_; + for ( ; *fp; fp = &(*fp)->nxt) { if (*fp == f) { // found the passed fiber in our list -- unlink it found = true; - *fp = (*fp)->nxt_; - f->nxt_ = 0; + *fp = (*fp)->nxt; + f->nxt = nullptr; // If that was the last item in the list, stop. if (! *fp) break; @@ -161,7 +161,7 @@ public: where = "to end"; } // Insert f at the new insertion point in the queue. - f->nxt_ = *insert; + f->nxt = *insert; *insert = f; std::cout << "moving " << where << ": "; @@ -175,7 +175,7 @@ public: else { const char* delim = ""; - for (boost::fibers::fiber_base *f = head_; f; f = f->nxt_) + for (boost::fibers::fiber_context *f = head_; f; f = f->nxt) { priority_props& props(properties(f)); std::cout << delim << props.name << '(' << props.get_priority() << ')'; diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index c731bc3f..34f94df6 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -27,11 +28,6 @@ namespace boost { namespace fibers { -namespace detail { - -struct scheduler; - -} class fiber_context; @@ -51,7 +47,7 @@ private: // reserve space for control structure std::size_t size = sctx.size - sizeof( fiber_context); void * sp = static_cast< char * >( sctx.sp) - sizeof( fiber_context); - // placement new of worker_fiber on top of fiber's stack + // placement new of fiber_context on top of fiber's stack return ptr_t( new ( sp) fiber_context( context::preallocated( sp, size, sctx), salloc, std::forward< Fn >( fn), std::forward< Args >( args) ... ) ); @@ -120,14 +116,10 @@ public: return impl_ ? impl_->get_id() : id(); } - bool thread_affinity() const noexcept; - - void thread_affinity( bool) noexcept; - template PROPS& properties() { - return fm_properties(impl_.get()); + return detail::scheduler::instance()->properties(impl_.get()); } }; diff --git a/include/boost/fiber/fiber_manager.hpp b/include/boost/fiber/fiber_manager.hpp index 9dd46bb3..6b497891 100644 --- a/include/boost/fiber/fiber_manager.hpp +++ b/include/boost/fiber/fiber_manager.hpp @@ -50,8 +50,6 @@ public: virtual ~fiber_manager() noexcept; - sched_algorithm* get_sched_algo_(); - std::chrono::high_resolution_clock::time_point next_wakeup(); void spawn( fiber_context *); @@ -83,6 +81,8 @@ public: fiber_context * active() noexcept; + sched_algorithm* get_sched_algo_(); + void set_sched_algo( sched_algorithm *); void wait_interval( std::chrono::high_resolution_clock::duration const&) noexcept; @@ -94,16 +94,14 @@ public: std::chrono::high_resolution_clock::duration wait_interval() noexcept; -#error FIX ME // implementation for fiber::properties() template < class PROPS > - PROPS& properties( detail::worker_fiber * f ) + PROPS& properties( fiber_context * f ) { return dynamic_cast&>(*get_sched_algo_()) .properties(f); } -#error FIX ME // implementation for this_fiber::properties() template < class PROPS > PROPS& properties() diff --git a/include/boost/fiber/operations.hpp b/include/boost/fiber/operations.hpp index 455377e5..4ccdf245 100644 --- a/include/boost/fiber/operations.hpp +++ b/include/boost/fiber/operations.hpp @@ -52,8 +52,7 @@ void sleep_for( std::chrono::duration< Rep, Period > const& timeout_duration) { template < class PROPS > PROPS& properties() { - // fibers::detail::scheduler::instance()->active()->... ? - return fibers::fm_properties(); + return fibers::detail::scheduler::instance()->active()->properties(); } } // this_fiber diff --git a/include/boost/fiber/properties.hpp b/include/boost/fiber/properties.hpp index 5ae52eab..0f5bf403 100644 --- a/include/boost/fiber/properties.hpp +++ b/include/boost/fiber/properties.hpp @@ -24,17 +24,14 @@ namespace boost { namespace fibers { -namespace detail { -class worker_fiber; -} // detail - struct sched_algorithm; +class fiber_context; class fiber_properties { protected: // initialized by constructor - detail::worker_fiber* fiber_; + fiber_context* fiber_; // set every time this fiber becomes READY sched_algorithm* sched_algo_; @@ -45,17 +42,17 @@ protected: public: // fiber_properties, and by implication every subclass, must accept a back - // pointer to its worker_fiber. - typedef detail::worker_fiber* back_ptr; + // pointer to its fiber_context. + typedef fiber_context* back_ptr; // Any specific property setter method, after updating the relevant // instance variable, can/should call notify(). fiber_properties(back_ptr f): fiber_(f), - sched_algo_(0) + sched_algo_(nullptr) {} // We need a virtual destructor (hence a vtable) because fiber_properties - // is stored polymorphically (as fiber_properties*) in worker_fiber, and + // is stored polymorphically (as fiber_properties*) in fiber_context, and // destroyed via that pointer. ~fiber_properties() {} diff --git a/src/fiber.cpp b/src/fiber.cpp index 1add6ab1..dcc23ef6 100644 --- a/src/fiber.cpp +++ b/src/fiber.cpp @@ -34,7 +34,7 @@ fiber::join() { if ( boost::this_fiber::get_id() == get_id() ) { throw fiber_resource_error( static_cast< int >( std::errc::resource_deadlock_would_occur), - "boost fiber: trying joining itself"); + "boost fiber: trying to join itself"); } if ( ! joinable() ) { From 4eaed2a943ee77e71bf755f4c604f7b88f4ddb17 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2015 04:25:05 -0400 Subject: [PATCH 20/30] Start resolving build errors with newly-merged sched-props-dev branch. Get fiber_manager out of the business of casting returned properties: that introduces a circular dependency. Defer casting to fiber_properties subclass to consumers in fiber.hpp and operations.hpp. --- include/boost/fiber/algorithm.hpp | 2 +- include/boost/fiber/detail/waiting_queue.hpp | 2 +- include/boost/fiber/fiber.hpp | 2 +- include/boost/fiber/fiber_manager.hpp | 15 --------------- include/boost/fiber/operations.hpp | 2 +- 5 files changed, 4 insertions(+), 19 deletions(-) diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index f8652ef3..66178534 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -10,6 +10,7 @@ #include #include +#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -17,7 +18,6 @@ namespace boost { namespace fibers { -class fiber_context; struct BOOST_FIBERS_DECL sched_algorithm { virtual ~sched_algorithm() {} diff --git a/include/boost/fiber/detail/waiting_queue.hpp b/include/boost/fiber/detail/waiting_queue.hpp index 9409fc07..d65b0870 100644 --- a/include/boost/fiber/detail/waiting_queue.hpp +++ b/include/boost/fiber/detail/waiting_queue.hpp @@ -44,7 +44,7 @@ public: fiber_context * top() const noexcept { BOOST_ASSERT( ! empty() ); - return static_cast(head_); + return head_; } void move_to( sched_algorithm *); diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 34f94df6..5a4e0ad3 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -119,7 +119,7 @@ public: template PROPS& properties() { - return detail::scheduler::instance()->properties(impl_.get()); + return dynamic_cast(*impl_->get_properties()); } }; diff --git a/include/boost/fiber/fiber_manager.hpp b/include/boost/fiber/fiber_manager.hpp index 6b497891..43fcfd81 100644 --- a/include/boost/fiber/fiber_manager.hpp +++ b/include/boost/fiber/fiber_manager.hpp @@ -94,21 +94,6 @@ public: std::chrono::high_resolution_clock::duration wait_interval() noexcept; - // implementation for fiber::properties() - template < class PROPS > - PROPS& properties( fiber_context * f ) - { - return dynamic_cast&>(*get_sched_algo_()) - .properties(f); - } - - // implementation for this_fiber::properties() - template < class PROPS > - PROPS& properties() - { - return properties(active()); - } - void preserve_fpu( bool); }; diff --git a/include/boost/fiber/operations.hpp b/include/boost/fiber/operations.hpp index 4ccdf245..95f0e73b 100644 --- a/include/boost/fiber/operations.hpp +++ b/include/boost/fiber/operations.hpp @@ -52,7 +52,7 @@ void sleep_for( std::chrono::duration< Rep, Period > const& timeout_duration) { template < class PROPS > PROPS& properties() { - return fibers::detail::scheduler::instance()->active()->properties(); + return dynamic_cast(*fibers::detail::scheduler::instance()->active()->get_properties()); } } // this_fiber From 765014daee4a290c566a44a9b90941aed36a7b78 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2015 05:38:45 -0400 Subject: [PATCH 21/30] working our way through build errors --- build/Jamfile.v2 | 3 ++- include/boost/fiber/algorithm.hpp | 36 +++++++++++-------------- include/boost/fiber/fiber_context.hpp | 3 --- src/algorithm.cpp | 38 +++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 src/algorithm.cpp diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index 352f5735..3f2c1657 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -25,7 +25,8 @@ project boost/fiber ; lib boost_fiber - : barrier.cpp + : algorithm.cpp + barrier.cpp condition.cpp detail/fifo.cpp detail/spinlock.cpp diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index 66178534..c9fce2e7 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -10,7 +10,6 @@ #include #include -#include #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -18,6 +17,7 @@ namespace boost { namespace fibers { +class fiber_context; struct BOOST_FIBERS_DECL sched_algorithm { virtual ~sched_algorithm() {} @@ -27,23 +27,22 @@ struct BOOST_FIBERS_DECL sched_algorithm { virtual fiber_context * pick_next() = 0; }; -namespace detail { -// support sched_algorithm_with_properties::properties(fiber::id) -inline -fiber_context* extract_context(fiber_context::id id) { return id.impl_; } -} // detail - -struct BOOST_FIBERS_DECL sched_algorithm_with_properties_base: public sched_algorithm +class BOOST_FIBERS_DECL sched_algorithm_with_properties_base: public sched_algorithm { +public: // called by fiber_properties::notify() -- don't directly call virtual void property_change_( fiber_context *f, fiber_properties* props ) = 0; + +protected: + static fiber_properties* get_properties(fiber_context* f) noexcept; + static void set_properties(fiber_context* f, fiber_properties* p) noexcept; }; template struct sched_algorithm_with_properties: public sched_algorithm_with_properties_base { - typedef sched_algorithm_with_properties super; + 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 @@ -51,19 +50,21 @@ struct sched_algorithm_with_properties: // sched_algorithm_with_properties::awakened(fb); virtual void awakened( fiber_context *f) final { - if (! f->get_properties()) + 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 - f->set_properties(new PROPS(f)); + props = new PROPS(f); + super::set_properties(f, props); } // Set sched_algo_ again every time this fiber becomes READY. That // handles the case of a fiber migrating to a new thread with a new // sched_algorithm subclass instance. - f->get_properties()->set_sched_algorithm(this); + props->set_sched_algorithm(this); // Okay, now forward the call to subclass override. - awakened_props(fb); + awakened_props(f); } // subclasses override this method instead of the original awakened() @@ -72,14 +73,7 @@ struct sched_algorithm_with_properties: // used for all internal calls PROPS& properties(fiber_context* f) { - return static_cast(*f->get_properties()); - } - - // public-facing properties(fiber::id) method in case consumer retains a - // pointer to supplied sched_algorithm_with_properties subclass - PROPS& properties(fiber::id id) - { - return properties(detail::extract_context(id)); + return static_cast(*super::get_properties(f)); } // override this to be notified by PROPS::notify() diff --git a/include/boost/fiber/fiber_context.hpp b/include/boost/fiber/fiber_context.hpp index 6c0ec55f..8c9bfa47 100644 --- a/include/boost/fiber/fiber_context.hpp +++ b/include/boost/fiber/fiber_context.hpp @@ -153,9 +153,6 @@ public: private: fiber_context * impl_; - // support sched_algorithm_with_properties::properties(fiber::id) - friend fiber_context* detail::extract_base(id); - public: id() noexcept : impl_( nullptr) { diff --git a/src/algorithm.cpp b/src/algorithm.cpp new file mode 100644 index 00000000..4da29cea --- /dev/null +++ b/src/algorithm.cpp @@ -0,0 +1,38 @@ + +// Copyright Oliver Kowalke / Nat Goodspeed 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "boost/fiber/detail/algorithm.hpp" + +#include + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { + +//static +fiber_properties* +sched_algorithm_with_properties_base::get_properties(fiber_context* f) noexcept +{ + return f->get_properties(); +} + +//static +void +sched_algorithm_with_properties_base::set_properties(fiber_context* f, fiber_properties* props) noexcept +{ + f->set_properties(props); +} + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif From a4e77e502f52ce775101646ce27092af7a02b419 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2015 05:46:26 -0400 Subject: [PATCH 22/30] Re-add bool fiber_manager::preserve_fpu() to header file. --- include/boost/fiber/fiber_manager.hpp | 1 + src/fiber_manager.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/fiber/fiber_manager.hpp b/include/boost/fiber/fiber_manager.hpp index 43fcfd81..bcba2597 100644 --- a/include/boost/fiber/fiber_manager.hpp +++ b/include/boost/fiber/fiber_manager.hpp @@ -94,6 +94,7 @@ public: std::chrono::high_resolution_clock::duration wait_interval() noexcept; + bool preserve_fpu() const; void preserve_fpu( bool); }; diff --git a/src/fiber_manager.cpp b/src/fiber_manager.cpp index 813802c7..3352b8c8 100644 --- a/src/fiber_manager.cpp +++ b/src/fiber_manager.cpp @@ -251,7 +251,7 @@ fiber_manager::wait_interval() noexcept { } bool -fiber_manager::preserve_fpu() { +fiber_manager::preserve_fpu() const { return preserve_fpu_; } From 89b3dafd5cad5ffaa7797466a801704ce942abf7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 May 2015 09:48:23 -0400 Subject: [PATCH 23/30] Eliminate spurious whitespace differences from upstream develop. --- include/boost/fiber/algorithm.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/fiber/algorithm.hpp b/include/boost/fiber/algorithm.hpp index c9fce2e7..bf1188e7 100644 --- a/include/boost/fiber/algorithm.hpp +++ b/include/boost/fiber/algorithm.hpp @@ -10,13 +10,14 @@ #include #include - + #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX #endif namespace boost { namespace fibers { + class fiber_context; struct BOOST_FIBERS_DECL sched_algorithm { From 25ccb85d19efe31e963bbfc23a0c8259421f8c3d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:37:32 -0400 Subject: [PATCH 24/30] Initialize int priority_ properly to 0, not nullptr. --- examples/priority.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/priority.cpp b/examples/priority.cpp index 64ef60a4..890bfd73 100644 --- a/examples/priority.cpp +++ b/examples/priority.cpp @@ -13,7 +13,7 @@ class priority_props: public boost::fibers::fiber_properties public: priority_props(boost::fibers::fiber_properties::back_ptr p): fiber_properties(p), - priority_(nullptr) + priority_(0) {} int get_priority() const { return priority_; } From f5d136d9747ec7d267bfe015a4d13ef797589617 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:38:28 -0400 Subject: [PATCH 25/30] Restore (merge-clobbered?) detach, join, interrupt method decls. --- include/boost/fiber/fiber.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/boost/fiber/fiber.hpp b/include/boost/fiber/fiber.hpp index 5a4e0ad3..c4f78635 100644 --- a/include/boost/fiber/fiber.hpp +++ b/include/boost/fiber/fiber.hpp @@ -116,6 +116,12 @@ public: return impl_ ? impl_->get_id() : id(); } + void detach() noexcept; + + void join(); + + void interrupt() noexcept; + template PROPS& properties() { From 00098f171a7b417cc26bcf6b8f929f4f61236526 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:39:25 -0400 Subject: [PATCH 26/30] For compatibility with Boost 1.58, call execution_context::resume(). --- include/boost/fiber/fiber_context.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/boost/fiber/fiber_context.hpp b/include/boost/fiber/fiber_context.hpp index 953e83b8..4ed3b47a 100644 --- a/include/boost/fiber/fiber_context.hpp +++ b/include/boost/fiber/fiber_context.hpp @@ -298,7 +298,9 @@ public: void resume() { BOOST_ASSERT( is_running() ); // set by the scheduler-algorithm - ctx_(); + // FIXME: post-1.58 boost::context::execution_context has an + // operator()() method. This statement originally called that. + ctx_.resume(); } std::chrono::high_resolution_clock::time_point const& time_point() const noexcept { From 068aa87e8e0ac175787ead4c62c168425fa5505e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:40:45 -0400 Subject: [PATCH 27/30] fiber_properties must be polymorphic. Add virtual destructor. This was an inadvertent oversight. The existing destructor even had a comment explaining why it must be virtual! --- include/boost/fiber/properties.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/fiber/properties.hpp b/include/boost/fiber/properties.hpp index 0f5bf403..c1e556e0 100644 --- a/include/boost/fiber/properties.hpp +++ b/include/boost/fiber/properties.hpp @@ -54,7 +54,7 @@ public: // We need a virtual destructor (hence a vtable) because fiber_properties // is stored polymorphically (as fiber_properties*) in fiber_context, and // destroyed via that pointer. - ~fiber_properties() {} + virtual ~fiber_properties() {} // not really intended for public use, but sched_algorithm_with_properties // must be able to call this From 04be1f0f847177fd9014198846a0f66e0f287724 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:41:38 -0400 Subject: [PATCH 28/30] algorithm.hpp no longer lives in detail subdirectory. --- src/algorithm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algorithm.cpp b/src/algorithm.cpp index 4da29cea..609dcc6d 100644 --- a/src/algorithm.cpp +++ b/src/algorithm.cpp @@ -4,7 +4,7 @@ // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#include "boost/fiber/detail/algorithm.hpp" +#include "boost/fiber/algorithm.hpp" #include From f66cc3389d6f2152b4d7bd4b863c7b025c7c26a2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:44:14 -0400 Subject: [PATCH 29/30] Remove static fm_mumble() functions brought over during merge. Specifically, remove fm_get_sched_algo_() and two fm_wait_interval() overloads. --- src/fiber_manager.cpp | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/fiber_manager.cpp b/src/fiber_manager.cpp index 3352b8c8..47dffed9 100644 --- a/src/fiber_manager.cpp +++ b/src/fiber_manager.cpp @@ -75,15 +75,6 @@ fiber_manager::resume_( fiber_context * f) { active_fiber_->resume(); } -#error FIX ME -sched_algorithm* fm_get_sched_algo_() -{ - fiber_manager * fm = detail::scheduler::instance(); - - BOOST_ASSERT( 0 != fm); - return fm->sched_algo_; -} - std::chrono::high_resolution_clock::time_point fiber_manager::next_wakeup() { if ( wqueue_.empty() ) { @@ -107,26 +98,6 @@ fiber_manager::spawn( fiber_context * f) { sched_algo_->awakened( f); } -#error FIX ME -void fm_wait_interval( chrono::high_resolution_clock::duration const& wait_interval) BOOST_NOEXCEPT -{ - fiber_manager * fm = detail::scheduler::instance(); - - BOOST_ASSERT( 0 != fm); - - fm->wait_interval_ = wait_interval; -} - -#error FIX ME -chrono::high_resolution_clock::duration fm_wait_interval() BOOST_NOEXCEPT -{ - fiber_manager * fm = detail::scheduler::instance(); - - BOOST_ASSERT( 0 != fm); - - return fm->wait_interval_; -} - void fiber_manager::run() { for (;;) { From dadc42a028fb74aae69e644a533179735ffd44ce Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 May 2015 08:45:01 -0400 Subject: [PATCH 30/30] Add missing #include fiber_context.hpp. --- src/properties.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/properties.cpp b/src/properties.cpp index 4a044ac1..e40b1ecc 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -7,6 +7,7 @@ #include "boost/fiber/properties.hpp" #include "boost/fiber/algorithm.hpp" #include "boost/fiber/fiber_manager.hpp" +#include "boost/fiber/fiber_context.hpp" #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX