What is the recommended usage of static variables with Relacy?
For example, I have some simplified code like this:
http://relacy.pastebin.com/m48ce35bd
The problem is that because the static variable theMutexOwner is
constructed outside of rl::simulate(), I get a crash. I can't think of
a clean way of ensuring that my static variable is only initialized
within rl::simulate(). For now, I've added hacky workarounds by
manually re-instancing the static variable every time in before()
using intrinsic knowledge of Relacy. Any ideas?
Thanks,
-Edward
Hi Edward,
Sorry for delay.
Well, initially Relacy was not intended for verification of programs
with static/thread local data. The idea was that a user may just
declare a variable in test class to simulate static/global variable,
or declare an array in test class to simulate thread local variable.
You may use a function rl::thread_index() to get a thread index (value
passed into test::thread(unsigned) function), thread index is a value
in 0..thread_count-1.
Btw, does theMutexOwner variable in your test must be static? AFAIU,
it may be plain array of variables. I.e.:
class AbortableLock
{
public:
// ....
private:
MUTEX myMutex;
std::atomic<int> theMutexOwner [MAX_THREAD_COUNT];
std::atomic<int>& get()
{
return theMutexOwner[rl::thread_index()];
}
};
--
Dmitriy V'jukov
> > What is the recommended usage of static variables with Relacy?
>
> > For example, I have some simplified code like this:
> > http://relacy.pastebin.com/m48ce35bd
>
> > The problem is that because the static variable theMutexOwner is
> > constructed outside of rl::simulate(), I get a crash. I can't think of
> > a clean way of ensuring that my static variable is only initialized
> > within rl::simulate(). For now, I've added hacky workarounds by
> > manually re-instancing the static variable every time in before()
> > using intrinsic knowledge of Relacy. Any ideas?
>
> Hi Edward,
> Sorry for delay.
>
> Well, initially Relacy was not intended for verification of programs
> with static/thread local data. The idea was that a user may just
> declare a variable in test class to simulate static/global variable,
> or declare an array in test class to simulate thread local variable.
Relacy 2.0 includes TLS variable simulation, it may be just what you
need.
Declare a var as TLS_T(T), it may be automatic var, class member,
static class member or global variable. Then use TLS(v) for accesses:
TLS_T(unsigned) tls_global_test_x;
struct tls_global_test : rl::test_suite<tls_global_test, 3,
rl::test_result_user_assert_failed>
{
void thread(unsigned index)
{
RL_ASSERT(VAR(tls_global_test_x) == 0);
VAR(tls_global_test_x) = index + 10;
RL_ASSERT(VAR(tls_global_test_x) == index + 10);
RL_ASSERT(false);
}
};
TLS variables are reset to 0 before each iteration.
--
Dmitriy V'jukov
I also require random access to the TLS variable values. eg. given a
thread id, return the TLS value of that thread. Do you have any
suggestions for what sort of TLS API I could emulate to augment Relacy
with this ability?
I actually misnamed "theMutexOwner" in my attempt to simplify the code
for posting. Allow me to give some more background. I have an
AbortableLock is a class that performs deadlock detection amongst all
instances of the class. It does so by maintaining two pieces of
information:
- For each instance of AbortableLock, it keeps track of the thread
id that has currently acquired it
- Inside AbortableLock::lock(), it saves into a TLS variable that
pointer of the AbortableLock instance that is currently trying to
acquire the mutex. This gives us a map from the thread id to the lock
that is trying to acquire (or "wait on") the mutex. Once it is done
trying to acquire the mutex, it resets the TLS variable to NULL.
Hopefully my previous questions now make sense. :) Inside
AbortableLock::lock(), we do a timed lock on the underlying mutex.
When it times out, we attempt to perform deadlock detection. This
consists of walking the "wait graph" by successively looking up these
two pieces of information like so:
- From the current AbortableLock instance, obtain its mutex owner
thread id
- From the owner thread id, use the TLS variable to find which
AbortableLock instance it is currently waiting on
- Now we have a new AbortableLock instance that we repeat the above
two steps with. If we find a repeated thread id, then a deadlock is
assumed.
Thanks,
-Edward
Hi Edward,
As far as I see you just need global variable where you will put array
of per-thread data. Right?
Since the array itself is actually constant for a duration of an
execution, you may simulate this in the following way. The trick is
just to introduce another level of indirection.
std::atomic<int>* g_tls;
struct test : rl::test_suite<test, THREAD_COUNT>
{
void before()
{
g_tls = new std::atomic<int> [THREAD_COUNT];
for (size_t i = 0; i != THREAD_COUNT; i += 1)
g_tls[i].store(0, std::memory_order_relaxed);
}
void after()
{
delete [] g_tls;
}
};
void some_freestanding_func()
{
std::atomic<int>& my_slot = g_tls[rl::thread_index()];
...
}
--
Dmitriy V'jukov
On Jan 10, 8:14 am, Dmitriy Vyukov <dvyu...@gmail.com> wrote:
> As far as I see you just need global variable where you will put array
> of per-thread data. Right?
>
> Since the array itself is actually constant for a duration of an
> execution, you may simulate this in the following way. The trick is
> just to introduce another level of indirection.
>
> std::atomic<int>* g_tls;
Yes, that is what I've been doing, which I originally referred to as a
"hacky workaround". The question is how does one go about doing it
cleanly in C++ so that the synchronization class code knows nothing
about Relacy yet while being able to easily test with Relacy when
needed. When not using Relacy, your proposal still requires that extra
level of indirection, plus it needs some way to know that we're not
compiling for Relacy so that it can initialize g_tls properly.
-Edward
Hi Edward,
That makes sense. I will think about support for global atomic/var
variables.
Initially, RRD was intended for small synthetic tests only. I assumed
that people will code an implementation specifically for RDD (so such
things was not a problem). Driven by my experience and user feedback,
RRD develop toward verification of production code more and more.
First it was reduction in manual instrumentation, then tls, then
dynamic threads...
I have limited experience with checking of a full-fledged work-
dispatching system, I was able to "boot" the scheduler, dispatch some
work, then "shutdown" the scheduler, all under Relacy. However, the
work-dispatching library uses only thread-local global variables
(__declspec(thread)), I especially tried to avoid using global vars as
much as possible, that's why currently Relacy has support only for
global thread-local vars, and not atomic/var...
--
Dmitriy V'jukov