| Home | Libraries | People | FAQ | More

## Customization

#### Overview

As noted in the Scheduling section, by default Boost.Fiber uses its own `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 Scheduling serves as a reference for the classes involved.

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.

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 priority.cpp.

#### Custom Property Class

The first essential point is that we must associate an integer priority with each fiber.[11]

One might suggest deriving a custom `fiber` subclass to store such properties. There are a couple of reasons for the present mechanism.

1. Boost.Fiber provides a number of different ways to launch a fiber. (Consider `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.
2. 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.
3. The `fiber` class is actually just a handle to internal `context` data. A subclass of `fiber` would not add data to `context`.

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 `fiber`, you must instead derive it from `fiber_properties`.

    class priority_props : public boost::fibers::fiber_properties {
    public:
        priority_props( boost::fibers::context * ctx):
            fiber_properties( ctx),
            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_;
    };

| Your subclass constructor must accept a ``context`*` and pass it to the `fiber_properties` constructor.
| Provide read access methods at your own discretion.
| 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.
| A property that does not affect the scheduler does not need access methods.

#### Custom Scheduler Class

Now we can derive a custom scheduler from `algorithm_with_properties<>`, specifying our custom property class `priority_props` as the template parameter.

    class priority_scheduler :
        public boost::fibers::algo::algorithm_with_properties< priority_props > {
    private:
        typedef boost::fibers::scheduler::ready_queue_type   rqueue_t;

        rqueue_t                                rqueue_;
        std::mutex                  mtx_{};
        std::condition_variable     cnd_{};
        bool                        flag_{ false };

    public:
        priority_scheduler() :
            rqueue_() {
        }

        // For a subclass of algorithm_with_properties<>, it's important to
        // override the correct awakened() overload.
        virtual void awakened( boost::fibers::context * ctx, priority_props & props) noexcept {
            int ctx_priority = props.get_priority();
            // With this scheduler, fibers with higher priority values are
            // preferred over fibers with lower priority values. But fibers with
            // equal priority values are processed in round-robin fashion. So when
            // we're handed a new context*, 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.
            rqueue_t::iterator i( std::find_if( rqueue_.begin(), rqueue_.end(),
                [ctx_priority,this]( boost::fibers::context & c)
                { return properties( &c ).get_priority() < ctx_priority; }));
            // Now, whether or not we found a fiber with lower priority,
            // insert this new fiber here.
            rqueue_.insert( i, * ctx);
        }

        virtual boost::fibers::context * pick_next() noexcept {
            // if ready queue is empty, just tell caller
            if ( rqueue_.empty() ) {
                return nullptr;
            }
            boost::fibers::context * ctx( & rqueue_.front() );
            rqueue_.pop_front();
            return ctx;
        }

        virtual bool has_ready_fibers() const noexcept {
            return ! rqueue_.empty();
        }

        virtual void property_change( boost::fibers::context * ctx, priority_props & props) noexcept {
            // 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.

            // 'ctx' might not be in our queue at all, if caller is changing the
            // priority of (say) the running fiber. If it's not there, no need to
            // move it: we'll handle it next time it hits awakened().
            if ( ! ctx->ready_is_linked()) {
                return;
            }

            // Found ctx: unlink it
            ctx->ready_unlink();

            // Here we know that ctx was in our ready queue, but we've unlinked
            // it. We happen to have a method that will (re-)add a context* to the
            // right place in the ready queue.
            awakened( ctx, props);
        }

        void suspend_until( std::chrono::steady_clock::time_point const& time_point) noexcept {
            if ( (std::chrono::steady_clock::time_point::max)() == time_point) {
                std::unique_lock< std::mutex > lk( mtx_);
                cnd_.wait( lk, [this](){ return flag_; });
                flag_ = false;
            } else {
                std::unique_lock< std::mutex > lk( mtx_);
                cnd_.wait_until( lk, time_point, [this](){ return flag_; });
                flag_ = false;
            }
        }

        void notify() noexcept {
            std::unique_lock< std::mutex > lk( mtx_);
            flag_ = true;
            lk.unlock();
            cnd_.notify_all();
        }
    };

| See ready_queue_t.
| You must override the `algorithm_with_properties::awakened()` method. This is how your scheduler receives notification of a fiber that has become ready to run.
| `props` is the instance of priority_props associated with the passed fiber `ctx`.
| You must override the `algorithm_with_properties::pick_next()` method. This is how your scheduler actually advises the fiber manager of the next fiber to run.
| You must override `algorithm_with_properties::has_ready_fibers()` to inform the fiber manager of the state of your ready queue.
| Overriding `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.
| Your `property_change()` override must be able to handle the case in which the passed `ctx` is not in your ready queue. It might be running, or it might be blocked.

Our example `priority_scheduler` doesn't override `algorithm_with_properties::new_properties()`: we're content with allocating `priority_props` instances on the heap.

#### Replace Default Scheduler

You must call `use_scheduling_algorithm()` at the start of each thread on which you want Boost.Fiber to use your custom scheduler rather than its own default `round_robin`. Specifically, you must call `use_scheduling_algorithm()` before performing any other Boost.Fiber operations on that thread.

    int main( int argc, char *argv[]) {
        // make sure we use our priority_scheduler rather than default round_robin
        boost::fibers::use_scheduling_algorithm< priority_scheduler >();
        ...
    }

#### Use Properties

The running fiber can access its own `fiber_properties` subclass instance by calling `this_fiber::properties()`. Although `properties<>()` is a nullary function, you must pass, as a template parameter, the `fiber_properties` subclass.

    boost::this_fiber::properties< priority_props >().name = "main";

Given a `fiber` instance still connected with a running fiber (that is, not `fiber::detach()`ed), you may access that fiber's properties using `fiber::properties()`. As with `boost::this_fiber::properties<>()`, you must pass your `fiber_properties` subclass as the template parameter.

    template< typename Fn >
    boost::fibers::fiber launch( Fn && func, std::string const& name, int priority) {
        boost::fibers::fiber fiber( func);
        priority_props & props( fiber.properties< priority_props >() );
        props.name = name;
        props.set_priority( priority);
        return fiber;
    }

Launching a new fiber schedules that fiber as ready, but does not immediately enter its fiber-function. The current fiber retains control until it blocks (or yields, or terminates) for some other reason. As shown in the `launch()` function above, it is reasonable to launch a fiber and immediately set relevant properties -- such as, for instance, its priority. Your custom scheduler can then make use of this information next time the fiber manager calls `algorithm_with_properties::pick_next()`.

[11] 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.