summaryrefslogtreecommitdiff
path: root/indra/llcommon/coro_scheduler.h
blob: a7572ccf4d2cecf6d68de07965be824cba3d8750 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
 * @file   coro_scheduler.h
 * @author Nat Goodspeed
 * @date   2024-08-05
 * @brief  Custom scheduler for viewer's Boost.Fibers (aka coroutines)
 * 
 * $LicenseInfo:firstyear=2024&license=viewerlgpl$
 * Copyright (c) 2024, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_CORO_SCHEDULER_H)
#define LL_CORO_SCHEDULER_H

#include "workqueue.h"
#include <boost/fiber/fiber.hpp>
#include <boost/fiber/algo/round_robin.hpp>

/**
 * llcoro::scheduler is specifically intended for the viewer's main thread.
 * Its role is to ensure that the main coroutine, responsible for UI
 * operations and coordinating everything else, doesn't get starved by
 * secondary coroutines -- however many of those there might be.
 *
 * The simple boost::fibers::algo::round_robin scheduler could result in
 * arbitrary time lag between resumptions of the main coroutine. Of course
 * every well-behaved viewer coroutine must be coded to yield before too much
 * real time has elapsed, but sheer volume of secondary coroutines could still
 * consume unreasonable real time before cycling back to the main coroutine.
 */

namespace llcoro
{

class scheduler: public boost::fibers::algo::round_robin
{
    using super = boost::fibers::algo::round_robin;
public:
    // If the main fiber is ready, and it's been at least this long since the
    // main fiber last ran, jump the main fiber to the head of the queue.
    static const F32 DEFAULT_TIMESLICE;

    scheduler();
    void awakened( boost::fibers::context*) noexcept override;
    boost::fibers::context* pick_next() noexcept override;

    static void use();

private:
    // This is the fiber::id of the main fiber. We use this to discover
    // whether the fiber passed to awakened() is in fact the main fiber.
    boost::fibers::fiber::id mMainID;
    // This context* is nullptr until awakened() notices that the main fiber
    // has become ready, at which point it contains the main fiber's context*.
    boost::fibers::context* mMainCtx{};
    // Set when pick_next() returns the main fiber.
    bool mMainRunning{ false };
    // If it's been at least this long since the last time the main fiber got
    // control, jump it to the head of the queue.
    F32 mTimeslice{ DEFAULT_TIMESLICE };
    // Timestamp as of the last time we suspended the main fiber.
    F32 mMainLast{ 0 };
    // Timestamp of start time
    F32 mStart{ 0 };
    // count context switches
    U64 mSwitches{ 0 };
    // WorkQueue for deferred logging
    LL::WorkQueue::weak_t mQueue;
};

} // namespace llcoro

#endif /* ! defined(LL_CORO_SCHEDULER_H) */