blob: 4db3e29a3b11eee5cf4705658764d157d65b07b0 [file] [log] [blame]
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef WTF_Condition_h
#define WTF_Condition_h
#include <chrono>
#include <functional>
#include <wtf/CurrentTime.h>
#include <wtf/Noncopyable.h>
#include <wtf/ParkingLot.h>
namespace WTF {
// This is a condition variable that is suitable for use with any lock-like object, including
// our own WTF::Lock. It features standard wait()/notifyOne()/notifyAll() methods in addition to
// a variety of wait-with-timeout methods. This includes methods that use WTF's own notion of
// time, like wall-clock time (i.e. currentTime()) and monotonic time (i.e.
// monotonicallyIncreasingTime()). This is a very efficient condition variable. It only requires
// one byte of memory. notifyOne() and notifyAll() require just a load and branch for the fast
// case where no thread is waiting. This condition variable, when used with WTF::Lock, can
// outperform a system condition variable and lock by up to 58x.
// This is a struct without a constructor or destructor so that it can be statically initialized.
// Use Lock in instance variables.
struct ConditionBase {
typedef ParkingLot::Clock Clock;
// Wait on a parking queue while releasing the given lock. It will unlock the lock just before
// parking, and relock it upon wakeup. Returns true if we woke up due to some call to
// notifyOne() or notifyAll(). Returns false if we woke up due to a timeout. Note that this form
// of waitUntil() has some quirks:
//
// No spurious wake-up: in order for this to return before the timeout, some notifyOne() or
// notifyAll() call must have happened. No scenario other than timeout or notify can lead to this
// method returning. This means, for example, that you can't use pthread cancelation or signals to
// cause early return.
//
// Past timeout: it's possible for waitUntil() to be called with a timeout in the past. In that
// case, waitUntil() will still release the lock and reacquire it. waitUntil() will always return
// false in that case. This is subtly different from some pthread_cond_timedwait() implementations,
// which may not release the lock for past timeout. But, this behavior is consistent with OpenGroup
// documentation for timedwait().
template<typename LockType>
bool waitUntil(LockType& lock, Clock::time_point timeout)
{
bool result;
if (timeout < Clock::now()) {
lock.unlock();
result = false;
} else {
result = ParkingLot::parkConditionally(
&m_hasWaiters,
[this] () -> bool {
// Let everyone know that we will be waiting. Do this while we hold the queue lock,
// to prevent races with notifyOne().
m_hasWaiters.store(true);
return true;
},
[&lock] () { lock.unlock(); },
timeout).wasUnparked;
}
lock.lock();
return result;
}
// Wait until the given predicate is satisfied. Returns true if it is satisfied in the end.
// May return early due to timeout.
template<typename LockType, typename Functor>
bool waitUntil(LockType& lock, Clock::time_point timeout, const Functor& predicate)
{
while (!predicate()) {
if (!waitUntil(lock, timeout))
return predicate();
}
return true;
}
// Wait until the given predicate is satisfied. Returns true if it is satisfied in the end.
// May return early due to timeout.
template<typename LockType, typename DurationType, typename Functor>
bool waitFor(
LockType& lock, const DurationType& relativeTimeout, const Functor& predicate)
{
return waitUntil(lock, absoluteFromRelative(relativeTimeout), predicate);
}
template<typename LockType>
void wait(LockType& lock)
{
waitUntil(lock, Clock::time_point::max());
}
template<typename LockType, typename Functor>
void wait(LockType& lock, const Functor& predicate)
{
while (!predicate())
wait(lock);
}
template<typename LockType, typename TimeType>
bool waitUntil(LockType& lock, const TimeType& timeout)
{
if (timeout == TimeType::max()) {
wait(lock);
return true;
}
return waitForImpl(lock, timeout - TimeType::clock::now());
}
template<typename LockType>
bool waitUntilWallClockSeconds(LockType& lock, double absoluteTimeoutSeconds)
{
return waitForSecondsImpl(lock, absoluteTimeoutSeconds - currentTime());
}
template<typename LockType>
bool waitUntilMonotonicClockSeconds(LockType& lock, double absoluteTimeoutSeconds)
{
return waitForSecondsImpl(lock, absoluteTimeoutSeconds - monotonicallyIncreasingTime());
}
template<typename LockType, typename Functor>
bool waitForSeconds(LockType& lock, double relativeTimeoutSeconds, const Functor& predicate)
{
double relativeTimeoutNanoseconds = relativeTimeoutSeconds * (1000.0 * 1000.0 * 1000.0);
if (!(relativeTimeoutNanoseconds > 0)) {
// This handles insta-timeouts as well as NaN.
lock.unlock();
lock.lock();
return false;
}
if (relativeTimeoutNanoseconds > static_cast<double>(std::numeric_limits<int64_t>::max())) {
// If the timeout in nanoseconds cannot be expressed using a 64-bit integer, then we
// might as well wait forever.
wait(lock, predicate);
return true;
}
auto relativeTimeout =
std::chrono::nanoseconds(static_cast<int64_t>(relativeTimeoutNanoseconds));
return waitFor(lock, relativeTimeout, predicate);
}
// Note that this method is extremely fast when nobody is waiting. It is not necessary to try to
// avoid calling this method.
void notifyOne()
{
if (!m_hasWaiters.load()) {
// At this exact instant, there is nobody waiting on this condition. The way to visualize
// this is that if unparkOne() ran to completion without obstructions at this moment, it
// wouldn't wake anyone up. Hence, we have nothing to do!
return;
}
ParkingLot::unparkOne(
&m_hasWaiters,
[this] (ParkingLot::UnparkResult result) -> intptr_t {
if (!result.mayHaveMoreThreads)
m_hasWaiters.store(false);
return 0;
});
}
void notifyAll()
{
if (!m_hasWaiters.load()) {
// See above.
return;
}
// It's totally safe for us to set this to false without any locking, because this thread is
// guaranteed to then unparkAll() anyway. So, if there is a race with some thread calling
// wait() just before this store happens, that thread is guaranteed to be awoken by the call to
// unparkAll(), below.
m_hasWaiters.store(false);
ParkingLot::unparkAll(&m_hasWaiters);
}
protected:
template<typename LockType>
bool waitForSecondsImpl(LockType& lock, double relativeTimeoutSeconds)
{
double relativeTimeoutNanoseconds = relativeTimeoutSeconds * (1000.0 * 1000.0 * 1000.0);
if (!(relativeTimeoutNanoseconds > 0)) {
// This handles insta-timeouts as well as NaN.
lock.unlock();
lock.lock();
return false;
}
if (relativeTimeoutNanoseconds > static_cast<double>(std::numeric_limits<int64_t>::max())) {
// If the timeout in nanoseconds cannot be expressed using a 64-bit integer, then we
// might as well wait forever.
wait(lock);
return true;
}
auto relativeTimeout =
std::chrono::nanoseconds(static_cast<int64_t>(relativeTimeoutNanoseconds));
return waitForImpl(lock, relativeTimeout);
}
template<typename LockType, typename DurationType>
bool waitForImpl(LockType& lock, const DurationType& relativeTimeout)
{
return waitUntil(lock, absoluteFromRelative(relativeTimeout));
}
template<typename DurationType>
Clock::time_point absoluteFromRelative(const DurationType& relativeTimeout)
{
if (relativeTimeout < DurationType::zero())
return Clock::time_point::min();
if (relativeTimeout > Clock::duration::max()) {
// This is highly unlikely. But if it happens, we want to not do anything dumb. Sleeping
// without a timeout seems sensible when the timeout duration is greater than what can be
// expressed using steady_clock.
return Clock::time_point::max();
}
Clock::duration myRelativeTimeout =
std::chrono::duration_cast<Clock::duration>(relativeTimeout);
return Clock::now() + myRelativeTimeout;
}
Atomic<bool> m_hasWaiters;
};
class Condition : public ConditionBase {
WTF_MAKE_NONCOPYABLE(Condition);
public:
Condition()
{
m_hasWaiters.store(false);
}
};
typedef ConditionBase StaticCondition;
} // namespace WTF
using WTF::Condition;
using WTF::StaticCondition;
#endif // WTF_Condition_h