diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2019-09-11 09:33:07 -0400 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2020-03-25 18:44:04 -0400 |
commit | d7c2e4a77bed665d7ab626d9955c35db8c318e95 (patch) | |
tree | 0c044579a3ce66717a18e1dc44f1822e19b3e894 /indra/test | |
parent | 98cfe13c2a3d5184e3c79043c817611edf49f74d (diff) |
DRTVWR-476: Add Sync class to help with stepwise coroutine tests.
Sync is specifically intended for test programs. It is based on an
LLScalarCond<int>. The idea is that each of two coroutines can watch for the
other to get a chance to run, indicated by incrementing the wrapped int and
notifying the wrapped condition_variable. This is less hand-wavy than calling
llcoro::suspend() and hoping that the other routine will have had a chance to
run.
Use Sync in lleventcoro_test.cpp.
Also refactor lleventcoro_test.cpp so that instead of a collection of static
data requiring a clear() call at start of each individual test function, the
relevant data is all part of the test_data struct common to all test
functions. Make the helper coroutine functions members of test_data too.
Introduce llcoro::logname(), a convenience function to log the name of the
currently executing coroutine or "main" if in the thread's main coroutine.
Diffstat (limited to 'indra/test')
-rw-r--r-- | indra/test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | indra/test/sync.h | 85 |
2 files changed, 86 insertions, 0 deletions
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 0f14862cba..87536e146b 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -67,6 +67,7 @@ set(test_HEADER_FILES llpipeutil.h llsdtraits.h lltut.h + sync.h ) if (NOT WINDOWS) diff --git a/indra/test/sync.h b/indra/test/sync.h new file mode 100644 index 0000000000..cafbc034b4 --- /dev/null +++ b/indra/test/sync.h @@ -0,0 +1,85 @@ +/** + * @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) */ |