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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
|
/**
* @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 "lldate.h"
#include <boost/fiber/condition_variable.hpp>
#include <mutex>
#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 boost::fibers::condition_variable makes LLCond work between
* coroutines as well as between threads.
*/
template <typename DATA>
class LLCond
{
public:
typedef value_type DATA;
private:
// This is the DATA controlled by the condition_variable.
value_type mData;
// condition_variable must be used in conjunction with a mutex. Use
// boost::fibers::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 boost::fiber::mutex is safe to
// use across threads as well: it subsumes std::mutex functionality.
boost::fibers::mutex mMutex;
// Use boost::fibers::condition_variable for the same reason.
boost::fibers::condition_variable mCond;
public:
/// LLCond can be explicitly initialized with a specific value for mData if
/// desired.
LLCond(value_type&& init=value_type()):
mData(init)
{}
/// LLCond is move-only
LLCond(const LLCond&) = delete;
LLCond& operator=(const LLCond&) = delete;
/// get() returns a const reference to the stored DATA. The only way to
/// get a non-const reference -- to modify the stored DATA -- is via
/// update_one() or update_all().
const value_type& get() const { return mData; }
/**
* 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()
std::unique_lock<boost::fibers::mutex> lk(mMutex);
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()
std::unique_lock<boost::fibers::mutex> lk(mMutex);
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)
{
std::unique_lock<boost::fibers::mutex> 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 (! pred(const_cast<const value_type&>(mData)))
{
mCond.wait(lk);
}
}
/**
* 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.
*/
template <typename Clock, typename Duration, typename Pred>
bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred)
{
std::unique_lock<boost::fibers::mutex> lk(mMutex);
// see wait() for comments about this const_cast
while (! pred(const_cast<const value_type&>(mData)))
{
if (boost::fibers::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 pred(const_cast<const value_type&>(mData));
}
}
return true;
}
/**
* This wait_until() overload accepts LLDate as the time_point. Its
* semantics are the same as the generic wait_until() method.
*/
template <typename Pred>
bool wait_until(const LLDate& timeout_time, Pred pred)
{
return wait_until(convert(timeout_time), pred);
}
/**
* 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, 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), pred);
}
protected:
// convert LLDate to a chrono::time_point
std::chrono::system_clock::time_point convert(const LLDate&);
// convert F32Milliseconds to a chrono::duration
std::chrono::milliseconds convert(F32Milliseconds);
};
template <typename DATA>
class LLScalarCond: public LLCond<DATA>
{
using super = LLCond<DATA>;
public:
using super::value_type;
using super::get;
using super::wait;
using super::wait_until;
using super::wait_for;
/// LLScalarCond can be explicitly initialized with a specific value for
/// mData if desired.
LLCond(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(value_type&& value)
{
super::update_one([](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(value_type&& value)
{
super::update_all([](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_until_equal() a chrono::time_point, indicating the time at
* which we should stop waiting, and a value for which to wait.
* wait_until_equal() locks the mutex and, until the stored DATA equals
* that value, calls wait_until() on the condition_variable.
* wait_until_equal() returns false if condition_variable::wait_until()
* timed out without the stored DATA being equal to the passed value.
*/
template <typename Clock, typename Duration>
bool wait_until_equal(const std::chrono::time_point<Clock, Duration>& timeout_time,
const value_type& value)
{
return super::wait_until(timeout_time,
[&value](const value_type& data){ return (data == value); });
}
/**
* This wait_until_equal() overload accepts LLDate as the time_point. Its
* semantics are the same as the generic wait_until_equal() method.
*/
bool wait_until_equal(const LLDate& timeout_time, const value_type& value)
{
return wait_until_equal(super::convert(timeout_time), 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_until_unequal() a chrono::time_point, indicating the time at
* which we should stop waiting, and a value from which to move away.
* wait_until_unequal() locks the mutex and, until the stored DATA no
* longer equals that value, calls wait_until() on the condition_variable.
* wait_until_unequal() returns false if condition_variable::wait_until()
* timed out with the stored DATA still being equal to the passed value.
*/
template <typename Clock, typename Duration>
bool wait_until_unequal(const std::chrono::time_point<Clock, Duration>& timeout_time,
const value_type& value)
{
return super::wait_until(timeout_time,
[&value](const value_type& data){ return (data != value); });
}
/**
* This wait_until_unequal() overload accepts LLDate as the time_point.
* Its semantics are the same as the generic wait_until_unequal() method.
*/
bool wait_until_unequal(const LLDate& timeout_time, const value_type& value)
{
return wait_until_unequal(super::convert(timeout_time), 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 super::value_type;
using super::get;
using super::wait;
using super::wait_until;
using super::wait_for;
using super::wait_equal;
using super::wait_until_equal;
using super::wait_for_equal;
using super::wait_unequal;
using super::wait_until_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_until() a chrono::time_point, indicating the time at which we
* should stop waiting. wait_until() locks the mutex and, until the stored
* bool is true, calls wait_until() on the condition_variable.
* wait_until() returns false if condition_variable::wait_until() timed
* out without the stored bool being true.
*/
template <typename Clock, typename Duration>
bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time)
{
return super::wait_until_unequal(timeout_time, false);
}
/**
* This wait_until() overload accepts LLDate as the time_point.
* Its semantics are the same as the generic wait_until() method.
*/
bool wait_until(const LLDate& timeout_time)
{
return wait_until(super::convert(timeout_time));
}
/**
* 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) */
|