1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qthreadstorage.h" |
5 | |
6 | #include "private/qcoreapplication_p.h" |
7 | #include "qthread.h" |
8 | #include "qthread_p.h" |
9 | #include "qmutex.h" |
10 | |
11 | #include <string.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | // #define THREADSTORAGE_DEBUG |
16 | #ifdef THREADSTORAGE_DEBUG |
17 | # define DEBUG_MSG qtsDebug |
18 | |
19 | # include <stdio.h> |
20 | # include <stdarg.h> |
21 | void qtsDebug(const char *fmt, ...) |
22 | { |
23 | va_list va; |
24 | va_start(va, fmt); |
25 | |
26 | fprintf(stderr, "QThreadStorage: " ); |
27 | vfprintf(stderr, fmt, va); |
28 | fprintf(stderr, "\n" ); |
29 | |
30 | va_end(va); |
31 | } |
32 | #else |
33 | # define DEBUG_MSG if (false)qDebug |
34 | #endif |
35 | |
36 | Q_CONSTINIT static QBasicMutex destructorsMutex; |
37 | typedef QList<void (*)(void *)> DestructorMap; |
38 | Q_GLOBAL_STATIC(DestructorMap, destructors) |
39 | |
40 | QThreadStorageData::QThreadStorageData(void (*func)(void *)) |
41 | { |
42 | QMutexLocker locker(&destructorsMutex); |
43 | DestructorMap *destr = destructors(); |
44 | if (!destr) { |
45 | /* |
46 | the destructors vector has already been destroyed, yet a new |
47 | QThreadStorage is being allocated. this can only happen during global |
48 | destruction, at which point we assume that there is only one thread. |
49 | in order to keep QThreadStorage working, we need somewhere to store |
50 | the data, best place we have in this situation is at the tail of the |
51 | current thread's tls vector. the destructor is ignored, since we have |
52 | no where to store it, and no way to actually call it. |
53 | */ |
54 | QThreadData *data = QThreadData::current(); |
55 | id = data->tls.size(); |
56 | DEBUG_MSG(msg: "QThreadStorageData: Allocated id %d, destructor %p cannot be stored" , id, func); |
57 | return; |
58 | } |
59 | for (id = 0; id < destr->size(); id++) { |
60 | if (destr->at(i: id) == nullptr) |
61 | break; |
62 | } |
63 | if (id == destr->size()) { |
64 | destr->append(t: func); |
65 | } else { |
66 | (*destr)[id] = func; |
67 | } |
68 | DEBUG_MSG(msg: "QThreadStorageData: Allocated id %d, destructor %p" , id, func); |
69 | } |
70 | |
71 | QThreadStorageData::~QThreadStorageData() |
72 | { |
73 | DEBUG_MSG(msg: "QThreadStorageData: Released id %d" , id); |
74 | QMutexLocker locker(&destructorsMutex); |
75 | if (destructors()) |
76 | (*destructors())[id] = nullptr; |
77 | } |
78 | |
79 | void **QThreadStorageData::get() const |
80 | { |
81 | QThreadData *data = QThreadData::current(); |
82 | if (!data) { |
83 | qWarning(msg: "QThreadStorage::get: QThreadStorage can only be used with threads started with QThread" ); |
84 | return nullptr; |
85 | } |
86 | QList<void *> &tls = data->tls; |
87 | if (tls.size() <= id) |
88 | tls.resize(size: id + 1); |
89 | void **v = &tls[id]; |
90 | |
91 | DEBUG_MSG(msg: "QThreadStorageData: Returning storage %d, data %p, for thread %p" , |
92 | id, |
93 | *v, |
94 | data->thread.loadRelaxed()); |
95 | |
96 | return *v ? v : nullptr; |
97 | } |
98 | |
99 | void **QThreadStorageData::set(void *p) |
100 | { |
101 | QThreadData *data = QThreadData::current(); |
102 | if (!data) { |
103 | qWarning(msg: "QThreadStorage::set: QThreadStorage can only be used with threads started with QThread" ); |
104 | return nullptr; |
105 | } |
106 | QList<void *> &tls = data->tls; |
107 | if (tls.size() <= id) |
108 | tls.resize(size: id + 1); |
109 | |
110 | void *&value = tls[id]; |
111 | // delete any previous data |
112 | if (value != nullptr) { |
113 | DEBUG_MSG(msg: "QThreadStorageData: Deleting previous storage %d, data %p, for thread %p" , |
114 | id, |
115 | value, |
116 | data->thread.loadRelaxed()); |
117 | |
118 | QMutexLocker locker(&destructorsMutex); |
119 | DestructorMap *destr = destructors(); |
120 | void (*destructor)(void *) = destr ? destr->value(i: id) : nullptr; |
121 | locker.unlock(); |
122 | |
123 | void *q = value; |
124 | value = nullptr; |
125 | |
126 | if (destructor) |
127 | destructor(q); |
128 | } |
129 | |
130 | // store new data |
131 | value = p; |
132 | DEBUG_MSG(msg: "QThreadStorageData: Set storage %d for thread %p to %p" , id, data->thread.loadRelaxed(), p); |
133 | return &value; |
134 | } |
135 | |
136 | void QThreadStorageData::finish(void **p) |
137 | { |
138 | QList<void *> *tls = reinterpret_cast<QList<void *> *>(p); |
139 | if (!tls || tls->isEmpty() || !destructors()) |
140 | return; // nothing to do |
141 | |
142 | DEBUG_MSG(msg: "QThreadStorageData: Destroying storage for thread %p" , QThread::currentThread()); |
143 | while (!tls->isEmpty()) { |
144 | void *&value = tls->last(); |
145 | void *q = value; |
146 | value = nullptr; |
147 | int i = tls->size() - 1; |
148 | tls->resize(size: i); |
149 | |
150 | if (!q) { |
151 | // data already deleted |
152 | continue; |
153 | } |
154 | |
155 | QMutexLocker locker(&destructorsMutex); |
156 | void (*destructor)(void *) = destructors()->value(i); |
157 | locker.unlock(); |
158 | |
159 | if (!destructor) { |
160 | if (QCoreApplicationPrivate::isAlive()) |
161 | qWarning(msg: "QThreadStorage: entry %d destroyed before end of thread %p" , |
162 | i, QThread::currentThread()); |
163 | continue; |
164 | } |
165 | destructor(q); //crash here might mean the thread exited after qthreadstorage was destroyed |
166 | |
167 | if (tls->size() > i) { |
168 | //re reset the tls in case it has been recreated by its own destructor. |
169 | (*tls)[i] = nullptr; |
170 | } |
171 | } |
172 | tls->clear(); |
173 | } |
174 | |
175 | /*! |
176 | \class QThreadStorage |
177 | \inmodule QtCore |
178 | \brief The QThreadStorage class provides per-thread data storage. |
179 | |
180 | \threadsafe |
181 | |
182 | \ingroup thread |
183 | |
184 | QThreadStorage is a template class that provides per-thread data |
185 | storage. |
186 | |
187 | The setLocalData() function stores a single thread-specific value |
188 | for the calling thread. The data can be accessed later using |
189 | localData(). |
190 | |
191 | The hasLocalData() function allows the programmer to determine if |
192 | data has previously been set using the setLocalData() function. |
193 | This is also useful for lazy initialization. |
194 | |
195 | If T is a pointer type, QThreadStorage takes ownership of the data |
196 | (which must be created on the heap with \c new) and deletes it when |
197 | the thread exits, either normally or via termination. |
198 | |
199 | For example, the following code uses QThreadStorage to store a |
200 | single cache for each thread that calls the cacheObject() and |
201 | removeFromCache() functions. The cache is automatically |
202 | deleted when the calling thread exits. |
203 | |
204 | \snippet threads/threads.cpp 7 |
205 | \snippet threads/threads.cpp 8 |
206 | \snippet threads/threads.cpp 9 |
207 | |
208 | \section1 Caveats |
209 | |
210 | \list |
211 | |
212 | \li The QThreadStorage destructor does not delete per-thread data. |
213 | QThreadStorage only deletes per-thread data when the thread exits |
214 | or when setLocalData() is called multiple times. |
215 | |
216 | \li QThreadStorage can be used to store data for the \c main() |
217 | thread. QThreadStorage deletes all data set for the \c main() |
218 | thread when QApplication is destroyed, regardless of whether or |
219 | not the \c main() thread has actually finished. |
220 | |
221 | \endlist |
222 | |
223 | \sa QThread |
224 | */ |
225 | |
226 | /*! |
227 | \fn template <class T> QThreadStorage<T>::QThreadStorage() |
228 | |
229 | Constructs a new per-thread data storage object. |
230 | */ |
231 | |
232 | /*! |
233 | \fn template <class T> QThreadStorage<T>::~QThreadStorage() |
234 | |
235 | Destroys the per-thread data storage object. |
236 | |
237 | Note: The per-thread data stored is not deleted. Any data left |
238 | in QThreadStorage is leaked. Make sure that all threads using |
239 | QThreadStorage have exited before deleting the QThreadStorage. |
240 | |
241 | \sa hasLocalData() |
242 | */ |
243 | |
244 | /*! |
245 | \fn template <class T> bool QThreadStorage<T>::hasLocalData() const |
246 | |
247 | If T is a pointer type, returns \c true if the calling thread has |
248 | non-zero data available. |
249 | |
250 | If T is a value type, returns whether the data has already been |
251 | constructed by calling setLocalData or localData. |
252 | |
253 | \sa localData() |
254 | */ |
255 | |
256 | /*! |
257 | \fn template <class T> T &QThreadStorage<T>::localData() |
258 | |
259 | Returns a reference to the data that was set by the calling |
260 | thread. |
261 | |
262 | If no data has been set, this will create a default constructed |
263 | instance of type T. |
264 | |
265 | \sa hasLocalData() |
266 | */ |
267 | |
268 | /*! |
269 | \fn template <class T> const T QThreadStorage<T>::localData() const |
270 | \overload |
271 | |
272 | Returns a copy of the data that was set by the calling thread. |
273 | |
274 | \sa hasLocalData() |
275 | */ |
276 | |
277 | /*! |
278 | \fn template <class T> void QThreadStorage<T>::setLocalData(T data) |
279 | |
280 | Sets the local data for the calling thread to \a data. It can be |
281 | accessed later using the localData() functions. |
282 | |
283 | If T is a pointer type, QThreadStorage takes ownership of the data |
284 | and deletes it automatically either when the thread exits (either |
285 | normally or via termination) or when setLocalData() is called again. |
286 | |
287 | \sa localData(), hasLocalData() |
288 | */ |
289 | |
290 | QT_END_NAMESPACE |
291 | |