86 lines
3.0 KiB
C++
86 lines
3.0 KiB
C++
/**
|
|
* @file sync.h
|
|
* @author Nat Goodspeed
|
|
* @date 2019-03-13
|
|
* @brief Synchronize coroutines within a test program so we can observe side
|
|
* effects. Certain test programs test coroutine synchronization
|
|
* mechanisms. Such tests usually want to interleave coroutine
|
|
* executions in strictly stepwise fashion. This class supports that
|
|
* paradigm.
|
|
*
|
|
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
|
* Copyright (c) 2019, Linden Research, Inc.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#if ! defined(LL_SYNC_H)
|
|
#define LL_SYNC_H
|
|
|
|
#include "llcond.h"
|
|
#include "lltut.h"
|
|
#include "stringize.h"
|
|
#include "llerror.h"
|
|
#include "llcoros.h"
|
|
|
|
/**
|
|
* Instantiate Sync in any test in which we need to suspend one coroutine
|
|
* until we're sure that another has had a chance to run. Simply calling
|
|
* llcoro::suspend() isn't necessarily enough; that provides a chance for the
|
|
* other to run, but doesn't guarantee that it has. If each coroutine is
|
|
* consistent about calling Sync::bump() every time it wakes from any
|
|
* suspension, Sync::yield() and yield_until() should at least ensure that
|
|
* somebody else has had a chance to run.
|
|
*/
|
|
class Sync
|
|
{
|
|
LLScalarCond<int> mCond{0};
|
|
F32Milliseconds mTimeout;
|
|
|
|
public:
|
|
Sync(F32Milliseconds timeout=F32Milliseconds(10.0f)):
|
|
mTimeout(timeout)
|
|
{}
|
|
|
|
/// Bump mCond by n steps -- ideally, do this every time a participating
|
|
/// coroutine wakes up from any suspension. The choice to bump() after
|
|
/// resumption rather than just before suspending is worth calling out:
|
|
/// this practice relies on the fact that condition_variable::notify_all()
|
|
/// merely marks a suspended coroutine ready to run, rather than
|
|
/// immediately resuming it. This way, though, even if a coroutine exits
|
|
/// before reaching its next suspend point, the other coroutine isn't
|
|
/// left waiting forever.
|
|
void bump(int n=1)
|
|
{
|
|
LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << (mCond.get() + n) << LL_ENDL;
|
|
mCond.set_all(mCond.get() + n);
|
|
}
|
|
|
|
/// suspend until "somebody else" has bumped mCond by n steps
|
|
void yield(int n=1)
|
|
{
|
|
return yield_until(STRINGIZE("Sync::yield_for(" << n << ") timed out after "
|
|
<< int(mTimeout.value()) << "ms"),
|
|
mCond.get() + n);
|
|
}
|
|
|
|
/// suspend until "somebody else" has bumped mCond to a specific value
|
|
void yield_until(int until)
|
|
{
|
|
return yield_until(STRINGIZE("Sync::yield_until(" << until << ") timed out after "
|
|
<< int(mTimeout.value()) << "ms"),
|
|
until);
|
|
}
|
|
|
|
private:
|
|
void yield_until(const std::string& desc, int until)
|
|
{
|
|
std::string name(llcoro::logname());
|
|
LL_DEBUGS() << name << " yield_until(" << until << ") suspending" << LL_ENDL;
|
|
tut::ensure(name + ' ' + desc, mCond.wait_for_equal(mTimeout, until));
|
|
// each time we wake up, bump mCond
|
|
bump();
|
|
}
|
|
};
|
|
|
|
#endif /* ! defined(LL_SYNC_H) */
|