summaryrefslogtreecommitdiff
path: root/indra/llcommon/llcond.h
blob: 2df1719941fbad20cc3ea2ac88b1d600c6cb01a5 (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
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
/**
 * @file   llcond.h
 * @author Nat Goodspeed
 * @date   2019-07-10
 * @brief  LLCond is a wrapper around condition_variable to encapsulate the
 *         obligatory condition_variable usage pattern. We also provide
 *         simplified versions LLScalarCond, LLBoolCond and LLOneShotCond.
 *
 * $LicenseInfo:firstyear=2019&license=viewerlgpl$
 * Copyright (c) 2019, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLCOND_H)
#define LL_LLCOND_H

#include "llunits.h"
#include "llcoros.h"
#include LLCOROS_MUTEX_HEADER
#include "mutex.h"
#include <chrono>

/**
 * LLCond encapsulates the pattern required to use a condition_variable. It
 * bundles subject data, a mutex and a condition_variable: the three required
 * data objects. It provides wait() methods analogous to condition_variable,
 * but using the contained condition_variable and the contained mutex. It
 * provides modify() methods accepting an invocable to safely modify the
 * contained data and notify waiters. These methods implicitly perform the
 * required locking.
 *
 * The generic LLCond template assumes that DATA might be a struct or class.
 * For a scalar DATA type, consider LLScalarCond instead. For specifically
 * bool, consider LLBoolCond.
 *
 * Use of LLCoros::ConditionVariable makes LLCond work between
 * coroutines as well as between threads.
 */
template <typename DATA>
class LLCond
{
public:
    typedef DATA value_type;

private:
    // This is the DATA controlled by the condition_variable.
    value_type mData;
    // condition_variable must be used in conjunction with a mutex. Use
    // LLCoros::Mutex instead of std::mutex because the latter blocks
    // the entire calling thread, whereas the former blocks only the current
    // coroutine within the calling thread. Yet LLCoros::Mutex is safe to
    // use across threads as well: it subsumes std::mutex functionality.
    LLCoros::Mutex mMutex;
    // Use LLCoros::ConditionVariable for the same reason.
    LLCoros::ConditionVariable mCond;
    using LockType = LLCoros::LockType;
    using cv_status = LLCoros::cv_status;

public:
    /// LLCond can be explicitly initialized with a specific value for mData if
    /// desired.
    LLCond(const value_type& init=value_type()):
        mData(init)
    {}

    /// LLCond is move-only
    LLCond(const LLCond&) = delete;
    LLCond& operator=(const LLCond&) = delete;

    /**
     * get() returns the stored DATA by value -- so to use get(), DATA must
     * be copyable. The only way to get a non-const reference -- to modify
     * the stored DATA -- is via update_one() or update_all().
     */
    value_type get()
    {
        LockType lk(mMutex);
        return mData;
    }

    /**
     * get(functor) returns whatever the functor returns. It allows us to peek
     * at the stored DATA without copying the whole thing. The functor must
     * accept a const reference to DATA. If you want to modify DATA, call
     * update_one() or update_all() instead.
     */
    template <typename FUNC>
    auto get(FUNC&& func)
    {
        LockType lk(mMutex);
        return std::forward<FUNC>(func)(const_data());
    }

    /**
     * Pass update_one() an invocable accepting non-const (DATA&). The
     * invocable will presumably modify the referenced DATA. update_one()
     * will lock the mutex, call the invocable and then call notify_one() on
     * the condition_variable.
     *
     * For scalar DATA, it's simpler to use LLScalarCond::set_one(). Use
     * update_one() when DATA is a struct or class.
     */
    template <typename MODIFY>
    void update_one(MODIFY&& modify)
    {
        { // scope of lock can/should end before notify_one()
            LockType lk(mMutex);
            std::forward<MODIFY>(modify)(mData);
        }
        mCond.notify_one();
    }

    /**
     * Pass update_all() an invocable accepting non-const (DATA&). The
     * invocable will presumably modify the referenced DATA. update_all()
     * will lock the mutex, call the invocable and then call notify_all() on
     * the condition_variable.
     *
     * For scalar DATA, it's simpler to use LLScalarCond::set_all(). Use
     * update_all() when DATA is a struct or class.
     */
    template <typename MODIFY>
    void update_all(MODIFY&& modify)
    {
        { // scope of lock can/should end before notify_all()
            LockType lk(mMutex);
            std::forward<MODIFY>(modify)(mData);
        }
        mCond.notify_all();
    }

    /**
     * Pass wait() a predicate accepting (const DATA&), returning bool. The
     * predicate returns true when the condition for which it is waiting has
     * been satisfied, presumably determined by examining the referenced DATA.
     * wait() locks the mutex and, until the predicate returns true, calls
     * wait() on the condition_variable.
     */
    template <typename Pred>
    void wait(Pred&& pred)
    {
        LockType lk(mMutex);
        // We must iterate explicitly since the predicate accepted by
        // condition_variable::wait() requires a different signature:
        // condition_variable::wait() calls its predicate with no arguments.
        // Fortunately, the loop is straightforward.
        // We advise the caller to pass a predicate accepting (const DATA&).
        // But what if they instead pass a predicate accepting non-const
        // (DATA&)? Such a predicate could modify mData, which would be Bad.
        // Forbid that.
        while (! std::forward<Pred>(pred)(const_data()))
        {
            mCond.wait(lk);
        }
    }

    /**
     * Pass wait_for() a chrono::duration, indicating how long we're willing
     * to wait, and a predicate accepting (const DATA&), returning bool. The
     * predicate returns true when the condition for which it is waiting has
     * been satisfied, presumably determined by examining the referenced DATA.
     * wait_for() locks the mutex and, until the predicate returns true, calls
     * wait_for() on the condition_variable. wait_for() returns false if
     * condition_variable::wait_for() timed out without the predicate
     * returning true.
     */
    template <typename Rep, typename Period, typename Pred>
    bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration, Pred&& pred)
    {
        // Instead of replicating wait_until() logic, convert duration to
        // time_point and just call wait_until().
        // An implementation in which we repeatedly called
        // condition_variable::wait_for() with our passed duration would be
        // wrong! We'd keep pushing the timeout time farther and farther into
        // the future. This way, we establish a definite timeout time and
        // stick to it.
        return wait_until(std::chrono::steady_clock::now() + timeout_duration,
                          std::forward<Pred>(pred));
    }

    /**
     * This wait_for() overload accepts F32Milliseconds as the duration. Any
     * duration unit defined in llunits.h is implicitly convertible to
     * F32Milliseconds. The semantics of this method are the same as the
     * generic wait_for() method.
     */
    template <typename Pred>
    bool wait_for(F32Milliseconds timeout_duration, Pred&& pred)
    {
        return wait_for(convert(timeout_duration), std::forward<Pred>(pred));
    }

protected:
    // convert F32Milliseconds to a chrono::duration
    auto convert(F32Milliseconds duration)
    {
        // std::chrono::milliseconds doesn't like to be constructed from a
        // float (F32), rubbing our nose in the thought that
        // std::chrono::duration::rep is probably integral. Therefore
        // converting F32Milliseconds to std::chrono::milliseconds would lose
        // precision. Use std::chrono::microseconds instead. Extract the F32
        // milliseconds from F32Milliseconds, scale to microseconds, construct
        // std::chrono::microseconds from that value.
        return std::chrono::microseconds{ std::chrono::microseconds::rep(duration.value() * 1000) };
    }

private:
    // It's important to pass a const ref to certain user-specified functors
    // that aren't supposed to be able to modify mData.
    const value_type& const_data() const { return mData; }

    /**
     * Pass wait_until() a chrono::time_point, indicating the time at which we
     * should stop waiting, and a predicate accepting (const DATA&), returning
     * bool. The predicate returns true when the condition for which it is
     * waiting has been satisfied, presumably determined by examining the
     * referenced DATA. wait_until() locks the mutex and, until the predicate
     * returns true, calls wait_until() on the condition_variable.
     * wait_until() returns false if condition_variable::wait_until() timed
     * out without the predicate returning true.
     *
     * Originally this class and its subclasses published wait_until() methods
     * corresponding to each wait_for() method. But that raised all sorts of
     * fascinating questions about the time zone of the passed time_point:
     * local time? server time? UTC? The bottom line is that for LLCond
     * timeout purposes, we really shouldn't have to care -- timeout duration
     * is all we need. This private method remains because it's the simplest
     * way to support iteratively waiting across spurious wakeups while
     * honoring a fixed timeout.
     */
    template <typename Clock, typename Duration, typename Pred>
    bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred&& pred)
    {
        LockType lk(mMutex);
        // We advise the caller to pass a predicate accepting (const DATA&).
        // But what if they instead pass a predicate accepting non-const
        // (DATA&)? Such a predicate could modify mData, which would be Bad.
        // Forbid that.
        while (! std::forward<Pred>(pred)(const_data()))
        {
            if (cv_status::timeout == mCond.wait_until(lk, timeout_time))
            {
                // It's possible that wait_until() timed out AND the predicate
                // became true more or less simultaneously. Even though
                // wait_until() timed out, check the predicate one more time.
                return std::forward<Pred>(pred)(const_data());
            }
        }
        return true;
    }
};

template <typename DATA>
class LLScalarCond: public LLCond<DATA>
{
    using super = LLCond<DATA>;

public:
    using typename super::value_type;
    using super::get;
    using super::wait;
    using super::wait_for;

    /// LLScalarCond can be explicitly initialized with a specific value for
    /// mData if desired.
    LLScalarCond(const value_type& init=value_type()):
        super(init)
    {}

    /// Pass set_one() a new value to which to update mData. set_one() will
    /// lock the mutex, update mData and then call notify_one() on the
    /// condition_variable.
    void set_one(const value_type& value)
    {
        super::update_one([&value](value_type& data){ data = value; });
    }

    /// Pass set_all() a new value to which to update mData. set_all() will
    /// lock the mutex, update mData and then call notify_all() on the
    /// condition_variable.
    void set_all(const value_type& value)
    {
        super::update_all([&value](value_type& data){ data = value; });
    }

    /**
     * Pass wait_equal() a value for which to wait. wait_equal() locks the
     * mutex and, until the stored DATA equals that value, calls wait() on the
     * condition_variable.
     */
    void wait_equal(const value_type& value)
    {
        super::wait([&value](const value_type& data){ return (data == value); });
    }

    /**
     * Pass wait_for_equal() a chrono::duration, indicating how long we're
     * willing to wait, and a value for which to wait. wait_for_equal() locks
     * the mutex and, until the stored DATA equals that value, calls
     * wait_for() on the condition_variable. wait_for_equal() returns false if
     * condition_variable::wait_for() timed out without the stored DATA being
     * equal to the passed value.
     */
    template <typename Rep, typename Period>
    bool wait_for_equal(const std::chrono::duration<Rep, Period>& timeout_duration,
                        const value_type& value)
    {
        return super::wait_for(timeout_duration,
                               [&value](const value_type& data){ return (data == value); });
    }

    /**
     * This wait_for_equal() overload accepts F32Milliseconds as the duration.
     * Any duration unit defined in llunits.h is implicitly convertible to
     * F32Milliseconds. The semantics of this method are the same as the
     * generic wait_for_equal() method.
     */
    bool wait_for_equal(F32Milliseconds timeout_duration, const value_type& value)
    {
        return wait_for_equal(super::convert(timeout_duration), value);
    }

    /**
     * Pass wait_unequal() a value from which to move away. wait_unequal()
     * locks the mutex and, until the stored DATA no longer equals that value,
     * calls wait() on the condition_variable.
     */
    void wait_unequal(const value_type& value)
    {
        super::wait([&value](const value_type& data){ return (data != value); });
    }

    /**
     * Pass wait_for_unequal() a chrono::duration, indicating how long we're
     * willing to wait, and a value from which to move away.
     * wait_for_unequal() locks the mutex and, until the stored DATA no longer
     * equals that value, calls wait_for() on the condition_variable.
     * wait_for_unequal() returns false if condition_variable::wait_for()
     * timed out with the stored DATA still being equal to the passed value.
     */
    template <typename Rep, typename Period>
    bool wait_for_unequal(const std::chrono::duration<Rep, Period>& timeout_duration,
                          const value_type& value)
    {
        return super::wait_for(timeout_duration,
                               [&value](const value_type& data){ return (data != value); });
    }

    /**
     * This wait_for_unequal() overload accepts F32Milliseconds as the duration.
     * Any duration unit defined in llunits.h is implicitly convertible to
     * F32Milliseconds. The semantics of this method are the same as the
     * generic wait_for_unequal() method.
     */
    bool wait_for_unequal(F32Milliseconds timeout_duration, const value_type& value)
    {
        return wait_for_unequal(super::convert(timeout_duration), value);
    }

protected:
    using super::convert;
};

/// Using bool as LLScalarCond's DATA seems like a particularly useful case
using LLBoolCond = LLScalarCond<bool>;

/// LLOneShotCond -- init false, set (and wait for) true
class LLOneShotCond: public LLBoolCond
{
    using super = LLBoolCond;

public:
    using typename super::value_type;
    using super::get;
    using super::wait;
    using super::wait_for;
    using super::wait_equal;
    using super::wait_for_equal;
    using super::wait_unequal;
    using super::wait_for_unequal;

    /// The bool stored in LLOneShotCond is initially false
    LLOneShotCond(): super(false) {}

    /// LLOneShotCond assumes that nullary set_one() means to set its bool true
    void set_one(bool value=true)
    {
        super::set_one(value);
    }

    /// LLOneShotCond assumes that nullary set_all() means to set its bool true
    void set_all(bool value=true)
    {
        super::set_all(value);
    }

    /**
     * wait() locks the mutex and, until the stored bool is true, calls wait()
     * on the condition_variable.
     */
    void wait()
    {
        super::wait_unequal(false);
    }

    /**
     * Pass wait_for() a chrono::duration, indicating how long we're willing
     * to wait. wait_for() locks the mutex and, until the stored bool is true,
     * calls wait_for() on the condition_variable. wait_for() returns false if
     * condition_variable::wait_for() timed out without the stored bool being
     * true.
     */
    template <typename Rep, typename Period>
    bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration)
    {
        return super::wait_for_unequal(timeout_duration, false);
    }

    /**
     * This wait_for() overload accepts F32Milliseconds as the duration.
     * Any duration unit defined in llunits.h is implicitly convertible to
     * F32Milliseconds. The semantics of this method are the same as the
     * generic wait_for() method.
     */
    bool wait_for(F32Milliseconds timeout_duration)
    {
        return wait_for(super::convert(timeout_duration));
    }
};

#endif /* ! defined(LL_LLCOND_H) */