summaryrefslogtreecommitdiff
path: root/indra/llcommon/llcoros.h
blob: 1edcb7e387666a16b752a654c35a0e8551b37160 (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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/**
 * @file   llcoros.h
 * @author Nat Goodspeed
 * @date   2009-06-02
 * @brief  Manage running boost::coroutine instances
 *
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H

#include "llcoromutex.h"
#include "llevents.h"
#include "llexception.h"
#include "llinstancetracker.h"
#include "llsingleton.h"
#include <boost/fiber/fss.hpp>
#include <exception>
#include <functional>
#include <map>
#include <queue>
#include <string>
#include <unordered_map>

namespace llcoro
{
class scheduler;
}

/**
 * Registry of named Boost.Fiber instances
 *
 * When the viewer first introduced the semi-independent execution agents now
 * called fibers, the term "fiber" had not yet become current, and the only
 * available libraries used the term "coroutine" instead. Within the viewer we
 * continue to use the term "coroutines," though at present they are actually
 * boost::fibers::fiber instances.
 *
 * Coroutines provide an alternative to the @c Responder pattern. Our typical
 * coroutine has @c void return, invoked in fire-and-forget mode: the handler
 * for some user gesture launches the coroutine and promptly returns to the
 * main loop. The coroutine initiates some action that will take multiple
 * frames (e.g. a capability request), waits for its result, processes it and
 * silently steals away.
 *
 * LLCoros is a Singleton collection of currently-active coroutine instances.
 * Each has a name. You ask LLCoros to launch a new coroutine with a suggested
 * name prefix; from your prefix it generates a distinct name, registers the
 * new coroutine and returns the actual name.
 *
 * The name can provide diagnostic info: we can look up the name of the
 * currently-running coroutine.
 */
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
    LLSINGLETON(LLCoros);
    ~LLCoros();

    void cleanupSingleton() override;
public:
    // For debugging, return true if on the main coroutine for the current thread
    // Code that should not be executed from a coroutine should be protected by
    // llassert(LLCoros::on_main_coro())
    static bool on_main_coro();

    // For debugging, return true if on the main thread and not in a coroutine
    // Non-thread-safe code in the main loop should be protected by
    // llassert(LLCoros::on_main_thread_main_coro())
    static bool on_main_thread_main_coro();

    typedef boost::fibers::fiber coro;
    typedef coro::id id;
    /// Canonical callable type
    typedef std::function<void()> callable_t;

    /**
     * Create and start running a new coroutine with specified name. The name
     * string you pass is a suggestion; it will be tweaked for uniqueness. The
     * actual name is returned to you.
     *
     * Usage looks like this, for (e.g.) two coroutine parameters:
     * @code
     * class MyClass
     * {
     * public:
     *     ...
     *     // Do NOT NOT NOT accept reference params!
     *     // Pass by value only!
     *     void myCoroutineMethod(std::string, LLSD);
     *     ...
     * };
     * ...
     * std::string name = LLCoros::instance().launch(
     *    "mycoro", boost::bind(&MyClass::myCoroutineMethod, this,
     *                          "somestring", LLSD(17));
     * @endcode
     *
     * Your function/method can accept any parameters you want -- but ONLY BY
     * VALUE! Reference parameters are a BAD IDEA! You Have Been Warned. See
     * DEV-32777 comments for an explanation.
     *
     * Pass a nullary callable. It works to directly pass a nullary free
     * function (or static method); for other cases use a lambda expression,
     * std::bind() or boost::bind(). Of course, for a non-static class method,
     * the first parameter must be the class instance. Any other parameters
     * should be passed via the enclosing expression.
     *
     * launch() tweaks the suggested name so it won't collide with any
     * existing coroutine instance, creates the coroutine instance, registers
     * it with the tweaked name and runs it until its first wait. At that
     * point it returns the tweaked name.
     */
    std::string launch(const std::string& prefix, const callable_t& callable);

    /**
     * Ask the named coroutine to abort. Normally, when a coroutine either
     * runs to completion or terminates with an exception, LLCoros quietly
     * cleans it up. This is for use only when you must explicitly interrupt
     * one prematurely. Returns @c true if the specified name was found and
     * still running at the time.
     */
    bool killreq(const std::string& name);

    /**
     * From within a coroutine, look up the (tweaked) name string by which
     * this coroutine is registered.
     */
    static std::string getName();

    /**
     * Given an id, return the name of that coroutine.
     */
    static std::string getName(id);

    /**
     * rethrow() is called by the thread's main fiber to propagate an
     * exception from any coroutine into the main fiber, where it can engage
     * the normal unhandled-exception machinery, up to and including crash
     * reporting.
     *
     * LLCoros maintains a queue of otherwise-uncaught exceptions from
     * terminated coroutines. Each call to rethrow() pops the first of those
     * and rethrows it. When the queue is empty (normal case), rethrow() is a
     * no-op.
     */
    void rethrow();

    /**
     * For delayed initialization. To be clear, this will only affect
     * coroutines launched @em after this point. The underlying facility
     * provides no way to alter the stack size of any running coroutine.
     */
    void setStackSize(S32 stacksize);

    /// diagnostic
    void printActiveCoroutines(const std::string& when=std::string());

    /// get the current coro::id for those who really really care
    static id get_self();

    /**
     * Most coroutines, most of the time, don't "consume" the events for which
     * they're suspending. This way, an arbitrary number of listeners (whether
     * coroutines or simple callbacks) can be registered on a particular
     * LLEventPump, every listener responding to each of the events on that
     * LLEventPump. But a particular coroutine can assert that it will consume
     * each event for which it suspends. (See also llcoro::postAndSuspend(),
     * llcoro::VoidListener)
     */
    static void set_consuming(bool consuming);
    static bool get_consuming();

    /**
     * RAII control of the consuming flag
     */
    class OverrideConsuming
    {
    public:
        OverrideConsuming(bool consuming):
            mPrevConsuming(get_consuming())
        {
            set_consuming(consuming);
        }
        OverrideConsuming(const OverrideConsuming&) = delete;
        ~OverrideConsuming()
        {
            set_consuming(mPrevConsuming);
        }

    private:
        bool mPrevConsuming;
    };

    /// set string coroutine status for diagnostic purposes
    static void setStatus(const std::string& status);
    static std::string getStatus();

    /// RAII control of status
    class TempStatus
    {
    public:
        TempStatus(const std::string& status):
            mOldStatus(getStatus())
        {
            setStatus(status);
        }
        TempStatus(const TempStatus&) = delete;
        TempStatus& operator=(const TempStatus&) = delete;
        ~TempStatus()
        {
            setStatus(mOldStatus);
        }

    private:
        std::string mOldStatus;
    };

    /// thrown by checkStop()
    // It may sound ironic that Stop is derived from LLContinueError, but the
    // point is that LLContinueError is the category of exception that should
    // not immediately crash the viewer. Stop and its subclasses are to tell
    // coroutines to terminate, e.g. because the viewer is shutting down. We
    // do not want any such exception to crash the viewer.
    struct Stop: public LLContinueError
    {
        Stop(const std::string& what): LLContinueError(what) {}
    };

    /// someone wants to kill this specific coroutine
    struct Killed: public Stop
    {
        Killed(const std::string& what): Stop(what) {}
    };

    /// early shutdown stages
    struct Stopping: public Stop
    {
        Stopping(const std::string& what): Stop(what) {}
    };

    /// cleaning up
    struct Stopped: public Stop
    {
        Stopped(const std::string& what): Stop(what) {}
    };

    /// cleaned up -- not much survives!
    struct Shutdown: public Stop
    {
        Shutdown(const std::string& what): Stop(what) {}
    };

    /// Call this intermittently if there's a chance your coroutine might
    /// still be running at application shutdown. Throws one of the Stop
    /// subclasses if the caller needs to terminate. Pass a cleanup function
    /// if you need to execute that cleanup before terminating.
    /// Of course, if your cleanup function throws, that will be the exception
    /// propagated by checkStop().
    static void checkStop(callable_t cleanup={});

    /// Call getStopListener() at the source end of a queue, promise or other
    /// resource on which coroutines will wait, so that shutdown can wake up
    /// consuming coroutines. @a caller should distinguish who's calling. The
    /// passed @a cleanup function must close the queue, break the promise or
    /// otherwise cause waiting consumers to wake up in an abnormal way. It's
    /// advisable to store the returned LLBoundListener in an
    /// LLTempBoundListener, or otherwise arrange to disconnect it.
    static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup);

    /// This getStopListener() overload is like the two-argument one, for use
    /// when we know the name of the only coroutine that will wait on the
    /// resource in question. Pass @a consumer as the empty string if the
    /// consumer coroutine is the same as the calling coroutine. Unlike the
    /// two-argument getStopListener(), this one also responds to
    /// killreq(target).
    static LLBoundListener getStopListener(const std::string& caller,
                                           const std::string& consumer,
                                           LLVoidListener cleanup);

    /**
     * LLCoros aliases for promise and future, for backwards compatibility.
     * These have been hoisted out to the llcoro namespace.
     */
    template <typename T>
    using Promise = llcoro::Promise<T>;
    template <typename T>
    using Future = llcoro::Future<T>;
    template <typename T>
    static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); }

    // use mutex, lock, condition_variable suitable for coroutines
    using Mutex = llcoro::Mutex;
    using RMutex = llcoro::RMutex;
    // LockType is deprecated; see llcoromutex.h
    using LockType = llcoro::LockType;
    using cv_status = llcoro::cv_status;
    using ConditionVariable = llcoro::ConditionVariable;

    /// for data local to each running coroutine
    template <typename T>
    using local_ptr = boost::fibers::fiber_specific_ptr<T>;

private:
    friend class llcoro::scheduler;

    std::string generateDistinctName(const std::string& prefix) const;
    void toplevel(std::string name, callable_t callable);
    struct CoroData;
    static CoroData& get_CoroData();
    static CoroData& get_CoroData(id);
    static CoroData& main_CoroData();
    void saveException(const std::string& name, std::exception_ptr exc);

    LLTempBoundListener mConn;

    struct ExceptionData
    {
        ExceptionData(const std::string& nm, std::exception_ptr exc):
            name(nm),
            exception(exc)
        {}
        // name of coroutine that originally threw this exception
        std::string name;
        // the thrown exception
        std::exception_ptr exception;
    };
    std::queue<ExceptionData> mExceptionQueue;

    S32 mStackSize;

    // coroutine-local storage, as it were: one per coro we track
    struct CoroData: public LLInstanceTracker<CoroData, id>
    {
        using super = LLInstanceTracker<CoroData, id>;

        CoroData(const std::string& name);
        ~CoroData();

        std::string getName() const;

        bool isMain{ false };
        // tweaked name of the current coroutine
        std::string mName;
        // set_consuming() state -- don't consume events unless specifically directed
        bool mConsuming{ false };
        // killed by which coroutine
        std::string mKilledBy;
        // setStatus() state
        std::string mStatus;
        F64 mCreationTime; // since epoch
        // Histogram of how many times this coroutine's timeslice exceeds
        // certain thresholds. mHistogram is pre-populated with those
        // thresholds as keys. If k0 is one threshold key and k1 is the next,
        // mHistogram[k0] is the number of times a coroutine timeslice tn ran
        // (k0 <= tn < k1). A timeslice less than mHistogram.begin()->first is
        // fine; we don't need to record those.
        std::map<F64, U32> mHistogram;
    };

    // Identify the current coroutine's CoroData. This local_ptr isn't static
    // because it's a member of an LLSingleton, and we rely on it being
    // cleaned up in proper dependency order.
    local_ptr<CoroData> mCurrent;

    // ensure name uniqueness
    static thread_local std::unordered_map<std::string, int> mPrefixMap;
    // lookup by name
    static thread_local std::unordered_map<std::string, id> mNameMap;
};

#endif /* ! defined(LL_LLCOROS_H) */