From 3e9594635081fd093fca660dedf718449f887a20 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 15 Aug 2015 10:48:58 -0400 Subject: [PATCH 1/4] Add tests for bounded_channel::lower_bound(). We intend to document that bounded_channel(hwm) is equivalent to bounded_channel(hwm, (hwm-1)). That may or may not simplify the code, but it certainly simplifies the reader's mental model of the relationship between the two constructors. However, that assertion requires that for bounded_channel(hwm), lower_bound() in fact return (hwm-1). Test for that. Also use BOOST_CHECK_EQUAL(a, b) instead of BOOST_CHECK(a == b) where applicable, since the former is more informative when the test fails. Sadly, tests on channel_op_status must still use BOOST_CHECK(a == b) -- apparently because channel_op_status, as an enum class, cannot be streamed to std::ostream? --- test/test_bounded_channel.cpp | 51 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/test/test_bounded_channel.cpp b/test/test_bounded_channel.cpp index 39526d3a..ab5aaaa8 100644 --- a/test/test_bounded_channel.cpp +++ b/test/test_bounded_channel.cpp @@ -83,6 +83,8 @@ void test_hwm_less_lwm() void test_push() { boost::fibers::bounded_channel< int > c( 10); + BOOST_CHECK_EQUAL( c.upper_bound(), 10u ); + BOOST_CHECK_EQUAL( c.lower_bound(), 9u ); BOOST_CHECK( boost::fibers::channel_op_status::success == c.push( 1) ); } @@ -110,6 +112,7 @@ void test_try_push_closed() void test_try_push_full() { boost::fibers::bounded_channel< int > c( 1); + BOOST_CHECK_EQUAL( c.lower_bound(), 0u ); BOOST_CHECK( boost::fibers::channel_op_status::success == c.try_push( 1) ); BOOST_CHECK( boost::fibers::channel_op_status::full == c.try_push( 2) ); } @@ -390,23 +393,23 @@ void test_wm_1() }); boost::fibers::fiber f2([&c,&ids](){ ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 1 == c.value_pop() ); + BOOST_CHECK_EQUAL( 1, c.value_pop() ); // let other fiber run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 2 == c.value_pop() ); + BOOST_CHECK_EQUAL( 2, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 3 == c.value_pop() ); + BOOST_CHECK_EQUAL( 3, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 4 == c.value_pop() ); + BOOST_CHECK_EQUAL( 4, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); // would block because channel is empty - BOOST_CHECK( 5 == c.value_pop() ); + BOOST_CHECK_EQUAL( 5, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); }); @@ -414,7 +417,7 @@ void test_wm_1() boost::fibers::fiber::id id2 = f2.get_id(); f1.join(); f2.join(); - BOOST_CHECK( 12 == ids.size() ); + BOOST_CHECK_EQUAL( 12u, ids.size() ); BOOST_CHECK_EQUAL( id1, ids[0]); // f1 pushes 1 BOOST_CHECK_EQUAL( id1, ids[1]); // f1 pushes 2 BOOST_CHECK_EQUAL( id1, ids[2]); // f1 pushes 3 @@ -455,25 +458,25 @@ void test_wm_2() }); boost::fibers::fiber f2([&c,&ids](){ ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 1 == c.value_pop() ); + BOOST_CHECK_EQUAL( 1, c.value_pop() ); // let other fiber run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 2 == c.value_pop() ); + BOOST_CHECK_EQUAL( 2, c.value_pop() ); // let other fiber run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 3 == c.value_pop() ); + BOOST_CHECK_EQUAL( 3, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 4 == c.value_pop() ); + BOOST_CHECK_EQUAL( 4, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 5 == c.value_pop() ); + BOOST_CHECK_EQUAL( 5, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); }); @@ -481,7 +484,7 @@ void test_wm_2() boost::fibers::fiber::id id2 = f2.get_id(); f1.join(); f2.join(); - BOOST_CHECK( 12 == ids.size() ); + BOOST_CHECK_EQUAL( 12u, ids.size() ); BOOST_CHECK_EQUAL( id1, ids[0]); // f1 pushes 1 BOOST_CHECK_EQUAL( id1, ids[1]); // f1 pushes 2 BOOST_CHECK_EQUAL( id1, ids[2]); // f1 pushes 3 @@ -499,6 +502,8 @@ void test_wm_2() void test_wm_3() { boost::fibers::bounded_channel< int > c( 3, 1); + BOOST_CHECK_EQUAL( c.upper_bound(), 3u ); + BOOST_CHECK_EQUAL( c.lower_bound(), 1u ); std::vector< boost::fibers::fiber::id > ids; boost::fibers::fiber f1([&c,&ids](){ ids.push_back( boost::this_fiber::get_id() ); @@ -521,25 +526,25 @@ void test_wm_3() }); boost::fibers::fiber f2([&c,&ids](){ ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 1 == c.value_pop() ); + BOOST_CHECK_EQUAL( 1, c.value_pop() ); // let other fiber run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 2 == c.value_pop() ); + BOOST_CHECK_EQUAL( 2, c.value_pop() ); // let other fiber run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 3 == c.value_pop() ); + BOOST_CHECK_EQUAL( 3, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 4 == c.value_pop() ); + BOOST_CHECK_EQUAL( 4, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 5 == c.value_pop() ); + BOOST_CHECK_EQUAL( 5, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); }); @@ -547,7 +552,7 @@ void test_wm_3() boost::fibers::fiber::id id2 = f2.get_id(); f1.join(); f2.join(); - BOOST_CHECK( 12 == ids.size() ); + BOOST_CHECK_EQUAL( 12u, ids.size() ); BOOST_CHECK_EQUAL( id1, ids[0]); // f1 pushes 1 BOOST_CHECK_EQUAL( id1, ids[1]); // f1 pushes 2 BOOST_CHECK_EQUAL( id1, ids[2]); // f1 pushes 3 @@ -584,26 +589,26 @@ void test_wm_4() }); boost::fibers::fiber f2([&c,&ids](){ ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 1 == c.value_pop() ); + BOOST_CHECK_EQUAL( 1, c.value_pop() ); // let potential other fibers run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 2 == c.value_pop() ); + BOOST_CHECK_EQUAL( 2, c.value_pop() ); // let potential other fibers run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); - BOOST_CHECK( 3 == c.value_pop() ); + BOOST_CHECK_EQUAL( 3, c.value_pop() ); // let potential other fibers run boost::this_fiber::yield(); ids.push_back( boost::this_fiber::get_id() ); // would block because channel is empty - BOOST_CHECK( 4 == c.value_pop() ); + BOOST_CHECK_EQUAL( 4, c.value_pop() ); ids.push_back( boost::this_fiber::get_id() ); }); @@ -611,7 +616,7 @@ void test_wm_4() boost::fibers::fiber::id id2 = f2.get_id(); f1.join(); f2.join(); - BOOST_CHECK( 10 == ids.size() ); + BOOST_CHECK_EQUAL( 10u, ids.size() ); BOOST_CHECK_EQUAL( id1, ids[0]); // f1 pushes 1 BOOST_CHECK_EQUAL( id1, ids[1]); // f1 pushes 2 BOOST_CHECK_EQUAL( id1, ids[2]); // f1 pushes 3 From b51d78877c9d326154fb3cc3bbe56d5dff9ba38c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 15 Aug 2015 11:12:34 -0400 Subject: [PATCH 2/4] Test that bounded_channel(n, n) throws an exception. Given the documented behavior of bounded_channel(hwm, lwm), it follows that bounded_channel(n, n) is invalid. If the number of items in the channel == hwm, a push will block. But if the number of items in the channel <= lwm, a push will succeed without blocking. Therefore, setting hwm == lwm results in a contradiction. --- test/test_bounded_channel.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_bounded_channel.cpp b/test/test_bounded_channel.cpp index ab5aaaa8..844e0691 100644 --- a/test/test_bounded_channel.cpp +++ b/test/test_bounded_channel.cpp @@ -80,6 +80,17 @@ void test_hwm_less_lwm() BOOST_CHECK( thrown); } +void test_hwm_equal_lwm() +{ + bool thrown = false; + try { + boost::fibers::bounded_channel< int > c( 3, 3); + } catch ( boost::fibers::fiber_exception const&) { + thrown = true; + } + BOOST_CHECK( thrown); +} + void test_push() { boost::fibers::bounded_channel< int > c( 10); @@ -655,6 +666,7 @@ boost::unit_test::test_suite * init_unit_test_suite( int, char* []) test->add( BOOST_TEST_CASE( & test_zero_wm_1) ); test->add( BOOST_TEST_CASE( & test_zero_wm_2) ); test->add( BOOST_TEST_CASE( & test_hwm_less_lwm) ); + test->add( BOOST_TEST_CASE( & test_hwm_equal_lwm) ); test->add( BOOST_TEST_CASE( & test_push) ); test->add( BOOST_TEST_CASE( & test_push_closed) ); test->add( BOOST_TEST_CASE( & test_try_push) ); From 95cc663e4312e2837ef726a5ac582e2e1cce55d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 15 Aug 2015 11:20:19 -0400 Subject: [PATCH 3/4] Correctly describe bounded_channel's behavior with lwm items. I originally assumed that a blocked bounded_channel::push() would only unblock once the number of items in the channel dropped _below_ lwm, and described it that way. But in fact the push unblocks as soon as the number of items in the channel drops as low as lwm. Update documentation accordingly. --- doc/channel.qbk | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/channel.qbk b/doc/channel.qbk index 8122f42e..4e393f0a 100644 --- a/doc/channel.qbk +++ b/doc/channel.qbk @@ -266,16 +266,15 @@ time as (system time + `timeout_duration`). bounded_channel( std::size_t hwm, std::size_t lwm); [variablelist -[[Preconditions:] [`hwm >= lwm`]] +[[Preconditions:] [`hwm > lwm`]] [[Effects:] [Constructs an object of class `bounded_channel`. The constructor with two arguments constructs an object of class `bounded_channel` with a high-watermark of `hwm` and a low-watermark of `lwm` items. The constructor -with one argument effectively sets both `hwm` and `lwm` to the same value -`wm`.]] -[[Throws:] [`invalid_argument` if `lwm > hwm`.]] +with one argument is effectively the same as `bounded_channel(wm, (wm-1))`.]] +[[Throws:] [`invalid_argument` if `lwm >= hwm`.]] [[Notes:] [Once the number of values in the channel reaches `hwm`, any call to `push()`, `push_wait_for()` or `push_wait_until()` will block until the number -of values in the channel has dropped below `lwm`. That is, if `lwm < hwm`, the +of values in the channel is at most `lwm`. That is, if `lwm < (hwm-1)`, the channel can be in a state in which `push()`, `push_wait_for()` or `push_wait_until()` calls will block (channel is full) even though the number of values in the channel is less than `hwm`.]] @@ -303,7 +302,7 @@ in the channel is less than `hwm`.]] [template bounded_channel_push_effects[or] [xchannel_push_effects If channel is not full, enqueues] Otherwise the calling fiber is suspended until -the number of values in the channel drops below `lwm` (return value +the number of values in the channel drops to `lwm` (return value `success`)[or] the channel is `close()`d (return value `closed`)] [member_heading bounded_channel..push] @@ -361,7 +360,7 @@ time_point (return value `timeout`).]] ] [template bounded_pop_unblocking[] Once the number of items remaining in the -channel drops below `lwm`, any fibers blocked on `push()`, `push_wait_for()` +channel drops to `lwm`, any fibers blocked on `push()`, `push_wait_for()` or `push_wait_until()` may resume.] [xchannel_pop bounded_channel... [bounded_pop_unblocking]] From fd9f16776a4f19e9297ed9e5aa6dac919f80dd1e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 16 Aug 2015 16:38:21 -0400 Subject: [PATCH 4/4] Flesh out Customization section with step-by-step directions. Mark up examples/priority.cpp with code snippets for use in Customization. Also clarify the effect of changing wait_interval(). --- doc/customization.qbk | 125 ++++++++++++++++++++++++++++++++++++++---- doc/fiber.qbk | 22 +++++--- doc/fibers.qbk | 2 +- doc/scheduling.qbk | 16 +++--- examples/priority.cpp | 79 +++++++++++++++++++++++--- 5 files changed, 207 insertions(+), 37 deletions(-) diff --git a/doc/customization.qbk b/doc/customization.qbk index f37c67e1..14ac1e9e 100644 --- a/doc/customization.qbk +++ b/doc/customization.qbk @@ -5,24 +5,125 @@ http://www.boost.org/LICENSE_1_0.txt ] +[import ../examples/priority.cpp] + [#custom] [section:custom Customization] -__boost_fiber__ allows to customize the scheduling algorithm by a user-defined -implementation. -A fiber-scheduler must implement interface __algo__. __boost_fiber__ provides -scheduler [class_link round_robin]. +[heading Overview] - void thread_fn() { - my_fiber_scheduler mfs; - boost::fibers::set_scheduling_algorithm( & mfs); - ... - } +As noted in the [link scheduling Scheduling] section, by default +__boost_fiber__ uses its own [class_link round_robin] scheduler for each +thread. To control the way __boost_fiber__ schedules ready fibers on a +particular thread, in general you must follow several steps. This section +discusses those steps, whereas [link scheduling Scheduling] serves as a +reference for the classes involved. -[heading fiber_context] +The library's fiber manager keeps track of suspended (blocked) fibers. Only +when a fiber becomes ready to run is it passed to the scheduler. Of course, if +there are fewer than two ready fibers, the scheduler's job is trivial. Only +when there are two or more ready fibers does the particular scheduler +implementation start to influence the overall sequence of fiber execution. -The fiber_context class is mostly supposed to be opaque. However, certain -members are available for use by a custom scheduling algorithm. +In this section we illustrate a simple custom scheduler that honors an integer +fiber priority. We will implement it such that a fiber with higher priority is +preferred over a fiber with lower priority. Any fibers with equal priority +values are serviced on a round-robin basis. +The full source code for the examples below is found in +[@boost:/libs/fiber/examples/priority.cpp priority.cpp]. + +[heading Custom Property Class] + +The first essential point is that we must associate an integer priority with +each fiber.[footnote A previous version of the Fiber library implicitly +tracked an int priority for each fiber, even though the default scheduler +ignored it. This has been dropped, since the library now supports arbitrary +scheduler-specific fiber properties.] + +One might suggest deriving a custom [class_link fiber] subclass to store such +properties. There are a couple of reasons for the present mechanism. + +# __boost_fiber__ provides a number of different ways to launch a fiber. + (Consider [ns_function_link fibers..async].) Higher-level libraries might + introduce additional such wrapper functions. A custom scheduler must + associate its custom properties with ['every] fiber in the thread, not only + the ones explicitly launched by instantiating a custom `fiber` subclass. +# Consider a large existing program that launches fibers in many different + places in the code. We discover a need to introduce a custom scheduler for a + particular thread. If supporting that scheduler's custom properties required + a particular `fiber` subclass, we would have to hunt down and modify every + place that launches a fiber on that thread. + +The present mechanism allows you to "drop in" a custom scheduler with its +attendant custom properties ['without] altering the rest of your application. + +Instead of deriving a custom scheduler fiber properties subclass from +[class_link fiber], you must instead derive it from [class_link +fiber_properties]. + +[priority_props] + +[heading Custom Scheduler Class] + +Now we can derive a custom scheduler from [template_link +sched_algorithm_with_properties], specifying our custom property class +`priority_props` as the template parameter. + +Your custom scheduler can track ready [^[class_link fiber_context]*]s using +whatever container tactic you prefer. In this example, we use +[data_member_link fiber_context..nxt] to hand-implement an intrusive +singly-linked list. + +[priority_scheduler] + +Our example `priority_scheduler` doesn't override [member_link +sched_algorithm_with_properties..new_properties]: we're content with +allocating `priority_props` instances on the heap. + +[heading Replace Default Scheduler] + +You must call [function_link set_scheduling_algorithm] at the start of each +thread on which you want __boost_fiber__ to use your custom scheduler rather +than its own default [class_link round_robin]. Specifically, you must call +`set_scheduling_algorithm()` before performing any other __boost_fiber__ +operations on that thread. + +It works to instantiate your custom [template_link +sched_algorithm_with_properties] subclass on the stack at the start of your +thread function, passing the instance pointer to `set_scheduling_algorithm()`. +This ensures that your scheduler instance will live as long as the thread +itself. (Of course `main()` is, in effect, the thread function for the +application's main thread.) + +[main] + +[heading Use Properties] + +The running fiber can access its own [class_link fiber_properties] subclass +instance by calling [ns_function_link this_fiber..properties]. Although +`properties<>()` is a nullary function, you must pass, as a template +parameter, the `fiber_properties` subclass. + +[init] + +Given a [class_link fiber] instance still connected with a running fiber (that +is, not [member_link fiber..detach]ed), you may access that fiber's properties +using [template_member_link fiber..properties]. As with +`this_fiber::properties<>()`, you must pass your `fiber_properties` subclass +as the template parameter. + +[change_fn] + +Since launching a new fiber schedules that fiber right away, code such as the +following: + + boost::fibers::fiber newfiber(fiber_function); + newfiber.properties().name = "newfiber"; + +will not necessarily set the property as soon as you expect. It is generally +preferable to pass the initial property values to your `fiber_function()` and +have it set them itself. In the example above, `change_fn()` accepts its own +`name` and `priority` and calls `init()` to set the corresponding properties. [endsect] diff --git a/doc/fiber.qbk b/doc/fiber.qbk index 11fa63f0..795a65a6 100644 --- a/doc/fiber.qbk +++ b/doc/fiber.qbk @@ -440,7 +440,7 @@ template parameter than `PROPS`.]] user-coded scheduler to associate extended properties, such as priority, with a fiber instance. This method allows access to those user-provided properties -- but only after this fiber has been scheduled for the first time.]] -[[See also:] [[link custom]]] +[[See also:] [[link custom Customization]]] ] [member_heading fiber..operator bool] @@ -507,7 +507,7 @@ lifespan of the referenced `scheduler` object. The caller must eventually destroy the passed `scheduler`, just as it must allocate it in the first place.]] [[Throws:] [Nothing]] -[[See also:] [[link scheduling], [link custom]]] +[[See also:] [[link scheduling Scheduling], [link custom Customization]]] ] [function_heading ready_fibers] @@ -515,7 +515,7 @@ place.]] std::size_t ready_fibers(); [variablelist -[[Returns:] [Returns the amount of fibers ready to run.]] +[[Returns:] [Returns the number of fibers ready to run.]] [[Throws:] [Nothing]] ] @@ -531,9 +531,17 @@ place.]] fibers are ready to run.]] [[Throws:] [Nothing]] [[Notes:] [This setting can be used to adjust the "niceness" of an idle -thread: a thread whose fibers are all currently blocked for various reasons. A -longer `wait_interval()` causes an idle thread to use less CPU. The default -for each thread, if not specified, is `std::chrono::milliseconds(10)`.]] +thread: a thread whose fibers are all currently blocked for various reasons. +The default for each thread, if not specified, is +`std::chrono::milliseconds(10)`. A longer `wait_interval()` causes an idle +thread to use less CPU. A shorter `wait_interval()` is more responsive to +fibers becoming ready. When a fiber becomes ready to resume, e.g. a mutex on +which it is blocked becomes available, control is not immediately passed to +that fiber; instead it is marked "ready to run." It won't actually resume +until the fiber manager wakes up to consult the scheduler. It could take as +long as `wait_interval()` before the fiber manager notices the fiber's +readiness. It is likely that the optimal setting for `wait_interval()` is +application-specific.]] ] @@ -747,7 +755,7 @@ template parameter than `PROPS`.]] user-coded scheduler to associate extended properties, such as priority, with a fiber instance. This function allows access to those user-provided properties.]] -[[See also:] [[link custom]]] +[[See also:] [[link custom Customization]]] ] [ns_function_heading this_fiber..interruption_point] diff --git a/doc/fibers.qbk b/doc/fibers.qbk index 31553f6a..92fc980b 100644 --- a/doc/fibers.qbk +++ b/doc/fibers.qbk @@ -65,7 +65,7 @@ [template data_member_heading[class_name member_name] [hding [class_name]_[member_name]..Data member [`[member_name]]] ] -[template data_member_link[class_name member_name] [member_link [class_name]..[member_name]]] +[template data_member_link[class_name member_name] [dblink [class_name]_[member_name]..[`[class_name]::[member_name]]]] [template function_heading[function_name] [hding [function_name]..Non-member function [`[function_name]()]] diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 3c83e7e1..236b3af4 100644 --- a/doc/scheduling.qbk +++ b/doc/scheduling.qbk @@ -15,7 +15,7 @@ manager. Each time a fiber suspends (or yields), the fiber manager consults a scheduler to determine which fiber will run next. __boost_fiber__ provides the fiber manager, but the scheduler is a -customization point. (See [link custom].) +customization point. (See [link custom Customization].) Each thread has its own scheduler. By default, __boost_fiber__ implicitly instantiates [class_link round_robin] as the scheduler for each thread. @@ -83,8 +83,7 @@ queue.]] virtual std::size_t ready_fibers() const noexcept = 0; [variablelist -[[Effects:] [Returns 0 if scheduling algorithm has no fibers ready to run, -otherwise nonzero.]] +[[Effects:] [Returns the number of fibers ready to run.]] ] @@ -126,8 +125,7 @@ of that queue, shares the thread between ready fibers in round-robin fashion.]] virtual std::size_t ready_fibers() const noexcept; [variablelist -[[Returns:] [0 if scheduling algorithm has no fibers ready to run, otherwise -nonzero.]] +[[Returns:] [Returns the number of fibers ready to run.]] ] @@ -150,7 +148,7 @@ A custom fiber properties class must be derived from `fiber_properties`. class fiber_properties { public: - fiber_properties( back_ptr f); + fiber_properties( fiber_context* f); virtual ~fiber_properties() {} @@ -160,12 +158,12 @@ A custom fiber properties class must be derived from `fiber_properties`. [heading Constructor] - fiber_properties( back_ptr f); + fiber_properties( fiber_context* f); [variablelist [[Effects:] [Constructs base-class component of custom subclass.]] -[[Note:] [Your subclass constructor must accept a `back_ptr` and pass it to -the base-class `fiber_properties` constructor.]] +[[Note:] [Your subclass constructor must accept a `fiber_context*` and pass it +to the base-class `fiber_properties` constructor.]] ] [member_heading fiber_properties..notify] diff --git a/examples/priority.cpp b/examples/priority.cpp index 2a9bc879..1e9bab4c 100644 --- a/examples/priority.cpp +++ b/examples/priority.cpp @@ -8,19 +8,28 @@ #include #include +//[priority_props class priority_props: public boost::fibers::fiber_properties { public: priority_props(boost::fibers::fiber_context* p): - fiber_properties(p), + fiber_properties(p), /*< Your subclass constructor must accept a + [^[class_link fiber_context]*] and pass it to + the `fiber_properties` constructor. >*/ priority_(0) {} - int get_priority() const { return priority_; } + int get_priority() const { return priority_; } /*< Provide read access + methods at your own + discretion. >*/ // Call this method to alter priority, because we must notify // priority_scheduler of any change. - void set_priority(int p) + void set_priority(int p) /*< It's important to call notify() on any + change in a property that can affect the + scheduler's behavior. Therefore, such + modifications should only be performed + through an access method. >*/ { // Of course, it's only worth reshuffling the queue and all if we're // actually changing the priority. @@ -35,12 +44,15 @@ public: // 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; + std::string name; /*< A property that does not affect the scheduler does + not need access methods. >*/ private: int priority_; }; +//] +//[priority_scheduler class priority_scheduler: public boost::fibers::sched_algorithm_with_properties { @@ -57,9 +69,14 @@ public: // For a subclass of sched_algorithm_with_properties<>, it's important to // override the correct awakened() overload. + /*<< You must override the [member_link sched_algorithm_with_properties..awakened] + method. This is how your scheduler receives notification of a + fiber that has become ready to run. >>*/ virtual void awakened(boost::fibers::fiber_context* f, priority_props& props) { - int f_priority = props.get_priority(); + int f_priority = props.get_priority(); /*< `props` is the instance of + priority_props associated + with the passed fiber `f`. >*/ // 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 @@ -68,17 +85,24 @@ public: // the queue with LOWER priority, and insert before that one. boost::fibers::fiber_context** fp = &head_; for ( ; *fp; fp = &(*fp)->nxt) - if (properties(*fp).get_priority() < f_priority) + if (properties(*fp).get_priority() < f_priority) /*< Use the + [member_link sched_algorithm_with_properties..properties] + method to access properties for any ['other] fiber. >*/ 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; /*< Note use of the [data_member_link fiber_context..nxt] member. >*/ *fp = f; +//<- std::cout << "awakened(" << props.name << "): "; describe_ready_queue(); +//-> } + /*<< You must override the [member_link sched_algorithm_with_properties..pick_next] + method. This is how your scheduler actually advises the fiber manager + of the next fiber to run. >>*/ virtual boost::fibers::fiber_context* pick_next() { // if ready queue is empty, just tell caller @@ -89,11 +113,15 @@ public: head_ = f->nxt; f->nxt = nullptr; +//<- std::cout << "pick_next() resuming " << properties(f).name << ": "; describe_ready_queue(); +//-> return f; } + /*<< You must override [member_link sched_algorithm_with_properties..ready_fibers] + to inform the fiber manager of the size of your ready queue. >>*/ virtual std::size_t ready_fibers() const noexcept { std::size_t count = 0; @@ -104,14 +132,20 @@ public: return count; } + /*<< Overriding [member_link sched_algorithm_with_properties..property_change] + is optional. This override handles the case in which the running + fiber changes the priority of another ready fiber: a fiber already in + our queue. In that case, move the updated fiber within the queue. >>*/ 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 // 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 @@ -152,31 +186,44 @@ public: // 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) + if (! found) /*< Your `property_change()` override must be able to + handle the case in which the passed `f` is not in + your ready queue. It might be running, or it might be + blocked. >*/ { +//<- // 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. + /*=if (! insert)*/ +//<- 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() { @@ -194,14 +241,18 @@ public: } std::cout << std::endl; } +//-> }; +//] +//[init 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) { @@ -225,13 +276,16 @@ void barrier_fn(const std::string& name, int priority, boost::fibers::barrier& b std::cout << "fiber " << name << " done" << std::endl; } +//[change_fn 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 @@ -239,17 +293,26 @@ void change_fn(const std::string& name, int priority, // 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; +//-> } +//] +//[main 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