Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qthreadstorage.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7
8#include "private/qcoreapplication_p.h"
9#include "qthread.h"
10#include "qthread_p.h"
11#include "qmutex.h"
12
13#include <string.h>
14
15QT_BEGIN_NAMESPACE
16
17// #define THREADSTORAGE_DEBUG
18#ifdef THREADSTORAGE_DEBUG
19# define DEBUG_MSG qtsDebug
20
21# include <stdio.h>
22# include <stdarg.h>
23void qtsDebug(const char *fmt, ...)
24{
25 va_list va;
26 va_start(va, fmt);
27
28 fprintf(stderr, "QThreadStorage: ");
29 vfprintf(stderr, fmt, va);
30 fprintf(stderr, "\n");
31
32 va_end(va);
33}
34#else
35# define DEBUG_MSG if (false)qDebug
36#endif
37
38Q_CONSTINIT static QBasicMutex destructorsMutex;
39typedef QList<void (*)(void *)> DestructorMap;
40Q_GLOBAL_STATIC(DestructorMap, destructors)
41
42QThreadStorageData::QThreadStorageData(void (*func)(void *))
43{
44 QMutexLocker locker(&destructorsMutex);
45 DestructorMap *destr = destructors();
46 if (!destr) {
47 /*
48 the destructors vector has already been destroyed, yet a new
49 QThreadStorage is being allocated. this can only happen during global
50 destruction, at which point we assume that there is only one thread.
51 in order to keep QThreadStorage working, we need somewhere to store
52 the data, best place we have in this situation is at the tail of the
53 current thread's tls vector. the destructor is ignored, since we have
54 no where to store it, and no way to actually call it.
55 */
56 QThreadData *data = QThreadData::current();
57 id = data->tls.size();
58 DEBUG_MSG("QThreadStorageData: Allocated id %d, destructor %p cannot be stored", id, func);
59 return;
60 }
61 for (id = 0; id < destr->size(); id++) {
62 if (destr->at(id) == nullptr)
63 break;
64 }
65 if (id == destr->size()) {
66 destr->append(func);
67 } else {
68 (*destr)[id] = func;
69 }
70 DEBUG_MSG("QThreadStorageData: Allocated id %d, destructor %p", id, func);
71}
72
73QThreadStorageData::~QThreadStorageData()
74{
75 DEBUG_MSG("QThreadStorageData: Released id %d", id);
76 QMutexLocker locker(&destructorsMutex);
77 if (destructors())
78 (*destructors())[id] = nullptr;
79}
80
81void **QThreadStorageData::get() const
82{
83 QThreadData *data = QThreadData::current();
84 QList<void *> &tls = data->tls;
85 if (tls.size() <= id)
86 tls.resize(id + 1);
87 void **v = &tls[id];
88
89 DEBUG_MSG("QThreadStorageData: Returning storage %d, data %p, for thread %p",
90 id,
91 *v,
92 data->thread.loadRelaxed());
93
94 return *v ? v : nullptr;
95}
96
97void **QThreadStorageData::set(void *p)
98{
99 QThreadData *data = QThreadData::current();
100 QList<void *> &tls = data->tls;
101 if (tls.size() <= id)
102 tls.resize(id + 1);
103
104 void* *ptr = &tls[id];
105 if (*ptr == p) // pointer-based setLocalData(localData()) → must be no-op
106 return ptr;
107
108 // delete any previous data
109 if (*ptr != nullptr) {
110 DEBUG_MSG("QThreadStorageData: Deleting previous storage %d, data %p, for thread %p",
111 id,
112 *ptr,
113 data->thread.loadRelaxed());
114
115 QMutexLocker locker(&destructorsMutex);
116 DestructorMap *destr = destructors();
117 void (*destructor)(void *) = destr ? destr->value(id) : nullptr;
118 locker.unlock();
119
120 if (destructor) {
121 void *q = std::exchange(*ptr, nullptr);
122 destructor(q);
123 // `destructor` may have re-entered and grown `tls`; re-seat `ptr`:
124 ptr = &tls[id];
125 }
126 }
127
128 // store new data
129 *ptr = p;
130 DEBUG_MSG("QThreadStorageData: Set storage %d for thread %p to %p", id, data->thread.loadRelaxed(), p);
131 return ptr;
132}
133
134void QThreadStorageData::clear()
135{
136 // Skip if this thread doesn't have a QThreadData (anymore): there's
137 // nothing to clear, then, and set() would otherwise re-create it, here,
138 // during shutdown, with nothing left to destroy it again.
139 if (QThreadData::currentThreadData())
140 set(nullptr);
141}
142
143void QThreadStoragePrivate::init()
144{
145 // Make sure the Q_GLOBAL_STATIC is initialized, ensuring consistent
146 // destruction order.
147 destructors();
148}
149
150void QThreadStoragePrivate::finish(QList<void *> *tls, bool suppressWarnings)
151{
152 if (tls->isEmpty() || !destructors())
153 return; // nothing to do
154 if (!QCoreApplication::instanceExists())
155 suppressWarnings = true;
156
157 DEBUG_MSG("QThreadStorageData: Destroying storage for thread %p", QThread::currentThread());
158 while (!tls->isEmpty()) {
159 void *&value = tls->last();
160 void *q = value;
161 value = nullptr;
162 int i = tls->size() - 1;
163 tls->resize(i);
164
165 if (!q) {
166 // data already deleted
167 continue;
168 }
169
170 QMutexLocker locker(&destructorsMutex);
171 void (*destructor)(void *) = destructors()->value(i);
172 locker.unlock();
173
174 if (!destructor) {
175 if (!suppressWarnings)
176 qWarning("QThreadStorage: entry %d destroyed before end of thread %p",
177 i, QThread::currentThread());
178 continue;
179 }
180 destructor(q); //crash here might mean the thread exited after qthreadstorage was destroyed
181
182 if (tls->size() > i) {
183 //re reset the tls in case it has been recreated by its own destructor.
184 (*tls)[i] = nullptr;
185 }
186 }
187 tls->clear();
188}
189
190/*!
191 \class QThreadStorage
192 \inmodule QtCore
193 \brief The QThreadStorage class provides per-thread data storage.
194
195 \threadsafe
196
197 \ingroup thread
198
199 QThreadStorage is a template class where the template parameter \a T
200 specifies the type of data stored per-thread.
201
202 The setLocalData() function stores a single thread-specific value
203 for the calling thread. The data can be accessed later using
204 localData().
205
206 The hasLocalData() function allows the programmer to determine if
207 data has previously been set using the setLocalData() function.
208 This is also useful for lazy initialization.
209
210 If T is a pointer type, QThreadStorage takes ownership of the data
211 (which must be created on the heap with \c new) and deletes it when
212 the thread exits, either normally or via termination.
213
214 For example, the following code uses QThreadStorage to store a
215 single cache for each thread that calls the cacheObject() and
216 removeFromCache() functions. The cache is automatically
217 deleted when the calling thread exits.
218
219 \snippet threads/threads.cpp 7
220 \snippet threads/threads.cpp 8
221 \snippet threads/threads.cpp 9
222
223 \section1 Caveats
224
225 \list
226
227 \target qthreadstorage-shutdown-caveat
228 \li Calling hasLocalData(), localData() or setLocalData() during thread or
229 program shutdown may re-create the thread-local storage for the current
230 thread with nothing left to destroy it afterwards. The clearLocalData()
231 function is the only one that is unconditionally safe to call during
232 shutdown.
233
234 \li The QThreadStorage destructor does not delete per-thread data.
235 QThreadStorage only deletes per-thread data when the thread exits
236 or when setLocalData() is called multiple times.
237
238 \li QThreadStorage can be used to store data for the \c main()
239 thread. QThreadStorage deletes all data set for the \c main()
240 thread when QApplication is destroyed, regardless of whether or
241 not the \c main() thread has actually finished.
242
243 \endlist
244
245 \sa QThread
246*/
247
248/*!
249 \fn template <class T> QThreadStorage<T>::QThreadStorage()
250
251 Constructs a new per-thread data storage object.
252*/
253
254/*!
255 \fn template <class T> QThreadStorage<T>::~QThreadStorage()
256
257 Destroys the per-thread data storage object.
258
259 Note: The per-thread data stored is not deleted. Any data left
260 in QThreadStorage is leaked. Make sure that all threads using
261 QThreadStorage have exited before deleting the QThreadStorage.
262
263 \sa hasLocalData()
264*/
265
266/*!
267 \fn template <class T> bool QThreadStorage<T>::hasLocalData() const
268
269 If T is a pointer type, returns \c true if the calling thread has
270 non-zero data available.
271
272 If T is a value type, returns whether the data has already been
273 constructed by calling setLocalData or localData.
274
275 \sa localData()
276*/
277
278/*!
279 \fn template <class T> T &QThreadStorage<T>::localData()
280
281 Returns a reference to the data that was set by the calling
282 thread.
283
284 If no data has been set, this will create a default constructed
285 instance of type T.
286
287 \sa hasLocalData()
288*/
289
290/*!
291 \fn template <class T> const T QThreadStorage<T>::localData() const
292 \overload
293
294 Returns a copy of the data that was set by the calling thread.
295
296 \sa hasLocalData()
297*/
298
299/*!
300 \fn template <class T> void QThreadStorage<T>::setLocalData(T data)
301
302 Sets the local data for the calling thread to \a data. It can be
303 accessed later using the localData() functions.
304
305 If T is a pointer type, QThreadStorage takes ownership of the data
306 and deletes it automatically either when the thread exits (either
307 normally or via termination) or when setLocalData() is called again.
308
309 \sa localData(), hasLocalData(), clearLocalData()
310*/
311
312/*!
313 \fn template <class T> void QThreadStorage<T>::clearLocalData()
314 \since 6.13
315
316 Resets the local data (if any) for the calling thread.
317
318 If the thread had no localData(), does nothing. After the call,
319 hasLocalData() is \c{false} (unless the destructor of the previous value
320 stored a new one).
321
322 \note If \c{T} is a pointer type, this is mostly the same as
323 \c{setLocalData(nullptr)}. For non-pointers, this functionality was not
324 accessible before Qt 6.13.
325
326 This function differs from setLocalData() in that it will not create
327 thread-local storage when it doesn't exist for the current thread (yet, or
328 anymore). This makes calling the function safe in shutdown code, where
329 thread-local storage may already have been destroyed, and setLocalData()
330 would re-create it with nothing left to destroy it afterwards.
331
332 \sa setLocalData(), hasLocalData(), {qthreadstorage-shutdown-caveat}{Caveats}
333*/
334
335QT_END_NAMESPACE
Definition qlist.h:82
QMutex QBasicMutex
Definition qmutex.h:360
static Q_CONSTINIT QBasicMutex destructorsMutex
#define DEBUG_MSG
QList< void(*)(void *)> DestructorMap