1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
/**
* @file lockstatic.h
* @author Nat Goodspeed
* @date 2019-12-03
* @brief LockStatic class provides mutex-guarded access to the specified
* static data.
*
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
* Copyright (c) 2019, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LOCKSTATIC_H)
#define LL_LOCKSTATIC_H
#include "mutex.h" // std::unique_lock
#include "llexception.h"
#include <typeinfo>
namespace llthread
{
class LockStaticBase
{
public:
// trying to lock Static after cleanup() has been called
struct Dead: public LLException
{
Dead(const std::string& what): LLException(what) {}
};
protected:
static void throwDead(const char* mangled);
};
// Instantiate this template to obtain a pointer to the canonical static
// instance of Static while holding a lock on that instance. Use of
// Static::mMutex presumes that Static declares some suitable mMutex.
template <typename Static>
class LockStatic: public LockStaticBase
{
typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
public:
LockStatic():
mData(getStatic()),
mLock(getLock(mData))
{}
Static* get() const { return mData; }
operator Static*() const { return get(); }
Static* operator->() const { return get(); }
// sometimes we must explicitly unlock...
void unlock()
{
// but once we do, access is no longer permitted
mData = nullptr;
mLock.unlock();
}
// explicit destruction
// We used to store a static instance of Static in getStatic(). The
// trouble with that is that at some point during final termination
// cleanup, the compiler calls ~Static(), destroying the mutex. If some
// later static object's destructor tries to lock our Static, we blow up
// trying to lock a destroyed mutex object. This can happen, for instance,
// if some class's destructor tries to reference an LLSingleton.
// Since a plain dumb pointer has no destructor, the compiler leaves it
// alone, so the referenced heap Static instance can survive until we
// explicitly call this method.
void cleanup()
{
// certainly don't claim to lock after this point!
mData = nullptr;
Static*& ptrref{ getStatic() };
Static* ptr{ ptrref };
ptrref = nullptr;
delete ptr;
}
protected:
Static* mData;
lock_t mLock;
private:
static lock_t getLock(Static* data)
{
// data can be false if cleanup() has already been called. If so, no
// code in the caller is valid that depends on this instance. We dare
// to throw an exception because trying to lock Static after it's been
// deleted is not part of normal processing. There are callers who
// want to handle this exception, but it should indeed be treated as
// exceptional.
if (! data)
{
throwDead(typeid(LockStatic<Static>).name());
}
// Usual case: data isn't nullptr, carry on.
return lock_t(data->mMutex);
}
Static*& getStatic()
{
// Our Static instance must be function-local static rather than
// class-static. Some of our consumers must function properly
// (therefore lock properly) even when the containing module's static
// variables have not yet been runtime-initialized. A mutex requires
// construction. A static class member might not yet have been
// constructed.
//
// We could store a dumb mutex_t* class member, notice when it's NULL
// and allocate a heap mutex -- but that's vulnerable to race
// conditions. And we can't defend the dumb pointer with another
// mutex.
//
// We could store a std::atomic<mutex_t*> -- but a default-constructed
// std::atomic<T> does not contain a valid T, even a default-constructed
// T! Which means std::atomic, too, requires runtime initialization.
//
// But a function-local static is guaranteed to be initialized exactly
// once: the first time control reaches that declaration. Importantly,
// since a plain dumb pointer has no destructor, the compiler lets our
// heap Static instance survive until someone calls cleanup() (above).
static Static* sData{ new Static };
return sData;
}
};
} // llthread namespace
#endif /* ! defined(LL_LOCKSTATIC_H) */
|