DRTVWR-476: Review response: support LLDate and llunits.h durations.
Also introduce value_type typedef.meow-7.2.2
parent
9a527c9f42
commit
495c8bc2c3
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* @file llcond.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-07-17
|
||||
* @brief Implementation for llcond.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llcond.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
|
||||
namespace // anonymous
|
||||
{
|
||||
|
||||
// See comments in LLCond::convert(const LLDate&) below
|
||||
std::time_t compute_lldate_epoch()
|
||||
{
|
||||
LLDate lldate_epoch;
|
||||
std::tm tm;
|
||||
// It should be noted that calling LLDate::split() to write directly
|
||||
// into a std::tm struct depends on S32 being a typedef for int in
|
||||
// stdtypes.h: split() takes S32*, whereas tm fields are documented to
|
||||
// be int. If you get compile errors here, somebody changed the
|
||||
// definition of S32. You'll have to declare six S32 variables,
|
||||
// split() into them, then assign them into the relevant tm fields.
|
||||
if (! lldate_epoch.split(&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
|
||||
&tm.tm_hour, &tm.tm_min, &tm.tm_sec))
|
||||
{
|
||||
// Theoretically split() could return false. In that case, we
|
||||
// don't have valid data, so we can't compute offset, so skip the
|
||||
// rest of this.
|
||||
return 0;
|
||||
}
|
||||
|
||||
tm.tm_isdst = 0;
|
||||
std::time_t lldate_epoch_time = std::mktime(&tm);
|
||||
if (lldate_epoch_time == -1)
|
||||
{
|
||||
// Theoretically mktime() could return -1, meaning that the contents
|
||||
// of the passed std::tm cannot be represented as a time_t. (Worrisome
|
||||
// if LLDate's epoch happened to be exactly 1 tick before
|
||||
// std::time_t's epoch...)
|
||||
// In the error case, assume offset 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// But if we got this far, lldate_epoch_time is the time_t we want.
|
||||
return lldate_epoch_time;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// convert LLDate to a chrono::time_point
|
||||
std::chrono::system_clock::time_point LLCond::convert(const LLDate& lldate)
|
||||
{
|
||||
// std::chrono::system_clock's epoch MAY be the Unix epoch, namely
|
||||
// midnight UTC on 1970-01-01, in fact it probably is. But until C++20,
|
||||
// system_clock does not guarantee that. Unfortunately time_t doesn't
|
||||
// specify its epoch either, other than to note that it "almost always" is
|
||||
// the Unix epoch (https://en.cppreference.com/w/cpp/chrono/c/time_t).
|
||||
// LLDate, being based on apr_time_t, does guarantee 1970-01-01T00:00 UTC.
|
||||
// http://apr.apache.org/docs/apr/1.5/group__apr__time.html#gadb4bde16055748190eae190c55aa02bb
|
||||
|
||||
// The easy, efficient conversion would be
|
||||
// std::chrono::system_clock::from_time_t(std::time_t(LLDate::secondsSinceEpoch())).
|
||||
// But that assumes that both time_t and system_clock have the same epoch
|
||||
// as LLDate -- an assumption that will work until it unexpectedly doesn't.
|
||||
|
||||
// It would be more formally correct to break out the year, month, day,
|
||||
// hour, minute, second (UTC) using LLDate::split() and recombine them
|
||||
// into std::time_t using std::mktime(). However, both split() and
|
||||
// mktime() have integer second granularity, whereas callers of
|
||||
// wait_until() are very likely to be interested in sub-second precision.
|
||||
// In that sense std::chrono::system_clock::from_time_t() is still
|
||||
// preferred.
|
||||
|
||||
// So use the split() / mktime() mechanism to determine the numeric value
|
||||
// of the LLDate / apr_time_t epoch as expressed in time_t. (We assume
|
||||
// that the epoch offset can be expressed as integer seconds, per split()
|
||||
// and mktime(), which seems plausible.)
|
||||
|
||||
// n.b. A function-static variable is initialized only once in a
|
||||
// thread-safe way.
|
||||
static std::time_t lldate_epoch_time = compute_lldate_epoch();
|
||||
|
||||
// LLDate::secondsSinceEpoch() gets us, of course, how long it has
|
||||
// been since lldate_epoch_time. So adding lldate_epoch_time should
|
||||
// give us the correct time_t representation of a given LLDate even if
|
||||
// time_t's epoch differs from LLDate's.
|
||||
// We don't have to worry about the relative epochs of time_t and
|
||||
// system_clock because from_time_t() takes care of that!
|
||||
return std::chrono::system_clock::from_time_t(lldate_epoch_time +
|
||||
lldate.secondsSinceEpoch());
|
||||
}
|
||||
|
||||
// convert F32Milliseconds to a chrono::duration
|
||||
std::chrono::milliseconds LLCond::convert(F32Milliseconds)
|
||||
{
|
||||
// extract the F32 milliseconds from F32Milliseconds, construct
|
||||
// std::chrono::milliseconds from that value
|
||||
return std::chrono::milliseconds(timeout_duration.value());
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@
|
|||
#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>
|
||||
|
|
@ -37,9 +39,12 @@
|
|||
template <typename DATA>
|
||||
class LLCond
|
||||
{
|
||||
public:
|
||||
typedef value_type DATA;
|
||||
|
||||
private:
|
||||
// This is the DATA controlled by the condition_variable.
|
||||
DATA mData;
|
||||
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
|
||||
|
|
@ -52,7 +57,7 @@ private:
|
|||
public:
|
||||
/// LLCond can be explicitly initialized with a specific value for mData if
|
||||
/// desired.
|
||||
LLCond(DATA&& init=DATA()):
|
||||
LLCond(value_type&& init=value_type()):
|
||||
mData(init)
|
||||
{}
|
||||
|
||||
|
|
@ -63,7 +68,7 @@ public:
|
|||
/// 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 DATA& get() const { return mData; }
|
||||
const value_type& get() const { return mData; }
|
||||
|
||||
/**
|
||||
* Pass update_one() an invocable accepting non-const (DATA&). The
|
||||
|
|
@ -122,7 +127,7 @@ public:
|
|||
// 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 DATA&>(mData)))
|
||||
while (! pred(const_cast<const value_type&>(mData)))
|
||||
{
|
||||
mCond.wait(lk);
|
||||
}
|
||||
|
|
@ -143,19 +148,29 @@ public:
|
|||
{
|
||||
std::unique_lock<boost::fibers::mutex> lk(mMutex);
|
||||
// see wait() for comments about this const_cast
|
||||
while (! pred(const_cast<const DATA&>(mData)))
|
||||
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 DATA&>(mData));
|
||||
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
|
||||
|
|
@ -178,6 +193,24 @@ public:
|
|||
// 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>
|
||||
|
|
@ -186,26 +219,32 @@ 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(DATA&& init=DATA()):
|
||||
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(DATA&& value)
|
||||
void set_one(value_type&& value)
|
||||
{
|
||||
super::update_one([](DATA& data){ data = 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(DATA&& value)
|
||||
void set_all(value_type&& value)
|
||||
{
|
||||
super::update_all([](DATA& data){ data = value; });
|
||||
super::update_all([](value_type& data){ data = value; });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -213,9 +252,9 @@ public:
|
|||
* mutex and, until the stored DATA equals that value, calls wait() on the
|
||||
* condition_variable.
|
||||
*/
|
||||
void wait_equal(const DATA& value)
|
||||
void wait_equal(const value_type& value)
|
||||
{
|
||||
super::wait([&value](const DATA& data){ return (data == value); });
|
||||
super::wait([&value](const value_type& data){ return (data == value); });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -228,10 +267,19 @@ public:
|
|||
*/
|
||||
template <typename Clock, typename Duration>
|
||||
bool wait_until_equal(const std::chrono::time_point<Clock, Duration>& timeout_time,
|
||||
const DATA& value)
|
||||
const value_type& value)
|
||||
{
|
||||
return super::wait_until(timeout_time,
|
||||
[&value](const DATA& data){ return (data == value); });
|
||||
[&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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -244,10 +292,21 @@ public:
|
|||
*/
|
||||
template <typename Rep, typename Period>
|
||||
bool wait_for_equal(const std::chrono::duration<Rep, Period>& timeout_duration,
|
||||
const DATA& value)
|
||||
const value_type& value)
|
||||
{
|
||||
return super::wait_for(timeout_duration,
|
||||
[&value](const DATA& data){ return (data == value); });
|
||||
[&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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -255,9 +314,9 @@ public:
|
|||
* locks the mutex and, until the stored DATA no longer equals that value,
|
||||
* calls wait() on the condition_variable.
|
||||
*/
|
||||
void wait_unequal(const DATA& value)
|
||||
void wait_unequal(const value_type& value)
|
||||
{
|
||||
super::wait([&value](const DATA& data){ return (data != value); });
|
||||
super::wait([&value](const value_type& data){ return (data != value); });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -270,10 +329,19 @@ public:
|
|||
*/
|
||||
template <typename Clock, typename Duration>
|
||||
bool wait_until_unequal(const std::chrono::time_point<Clock, Duration>& timeout_time,
|
||||
const DATA& value)
|
||||
const value_type& value)
|
||||
{
|
||||
return super::wait_until(timeout_time,
|
||||
[&value](const DATA& data){ return (data != value); });
|
||||
[&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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -286,22 +354,48 @@ public:
|
|||
*/
|
||||
template <typename Rep, typename Period>
|
||||
bool wait_for_unequal(const std::chrono::duration<Rep, Period>& timeout_duration,
|
||||
const DATA& value)
|
||||
const value_type& value)
|
||||
{
|
||||
return super::wait_for(timeout_duration,
|
||||
[&value](const DATA& data){ return (data != value); });
|
||||
[&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? Or full suite?
|
||||
/// 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) {}
|
||||
|
||||
|
|
@ -323,7 +417,7 @@ public:
|
|||
*/
|
||||
void wait()
|
||||
{
|
||||
super::wait_equal(true);
|
||||
super::wait_unequal(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -336,7 +430,16 @@ public:
|
|||
template <typename Clock, typename Duration>
|
||||
bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time)
|
||||
{
|
||||
return super::wait_until_equal(timeout_time, true);
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -349,7 +452,18 @@ public:
|
|||
template <typename Rep, typename Period>
|
||||
bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration)
|
||||
{
|
||||
return super::wait_for_equal(timeout_duration, true);
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue