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 QThreadStoragePrivate::init()
135{
136 // Make sure the Q_GLOBAL_STATIC is initialized, ensuring consistent
137 // destruction order.
138 destructors();
139}
140
141void QThreadStoragePrivate::finish(QList<void *> *tls, bool suppressWarnings)
142{
143 if (tls->isEmpty() || !destructors())
144 return; // nothing to do
145 if (!QCoreApplication::instanceExists())
146 suppressWarnings = true;
147
148 DEBUG_MSG("QThreadStorageData: Destroying storage for thread %p", QThread::currentThread());
149 while (!tls->isEmpty()) {
150 void *&value = tls->last();
151 void *q = value;
152 value = nullptr;
153 int i = tls->size() - 1;
154 tls->resize(i);
155
156 if (!q) {
157 // data already deleted
158 continue;
159 }
160
161 QMutexLocker locker(&destructorsMutex);
162 void (*destructor)(void *) = destructors()->value(i);
163 locker.unlock();
164
165 if (!destructor) {
166 if (!suppressWarnings)
167 qWarning("QThreadStorage: entry %d destroyed before end of thread %p",
168 i, QThread::currentThread());
169 continue;
170 }
171 destructor(q); //crash here might mean the thread exited after qthreadstorage was destroyed
172
173 if (tls->size() > i) {
174 //re reset the tls in case it has been recreated by its own destructor.
175 (*tls)[i] = nullptr;
176 }
177 }
178 tls->clear();
179}
180
181/*!
182 \class QThreadStorage
183 \inmodule QtCore
184 \brief The QThreadStorage class provides per-thread data storage.
185
186 \threadsafe
187
188 \ingroup thread
189
190 QThreadStorage is a template class where the template parameter \a T
191 specifies the type of data stored per-thread.
192
193 The setLocalData() function stores a single thread-specific value
194 for the calling thread. The data can be accessed later using
195 localData().
196
197 The hasLocalData() function allows the programmer to determine if
198 data has previously been set using the setLocalData() function.
199 This is also useful for lazy initialization.
200
201 If T is a pointer type, QThreadStorage takes ownership of the data
202 (which must be created on the heap with \c new) and deletes it when
203 the thread exits, either normally or via termination.
204
205 For example, the following code uses QThreadStorage to store a
206 single cache for each thread that calls the cacheObject() and
207 removeFromCache() functions. The cache is automatically
208 deleted when the calling thread exits.
209
210 \snippet threads/threads.cpp 7
211 \snippet threads/threads.cpp 8
212 \snippet threads/threads.cpp 9
213
214 \section1 Caveats
215
216 \list
217
218 \li Calling hasLocalData(), localData() or setLocalData() during thread or
219 program shutdown may re-create the thread-local storage for the current
220 thread with nothing left to destroy it afterwards.
221
222 \li The QThreadStorage destructor does not delete per-thread data.
223 QThreadStorage only deletes per-thread data when the thread exits
224 or when setLocalData() is called multiple times.
225
226 \li QThreadStorage can be used to store data for the \c main()
227 thread. QThreadStorage deletes all data set for the \c main()
228 thread when QApplication is destroyed, regardless of whether or
229 not the \c main() thread has actually finished.
230
231 \endlist
232
233 \sa QThread
234*/
235
236/*!
237 \fn template <class T> QThreadStorage<T>::QThreadStorage()
238
239 Constructs a new per-thread data storage object.
240*/
241
242/*!
243 \fn template <class T> QThreadStorage<T>::~QThreadStorage()
244
245 Destroys the per-thread data storage object.
246
247 Note: The per-thread data stored is not deleted. Any data left
248 in QThreadStorage is leaked. Make sure that all threads using
249 QThreadStorage have exited before deleting the QThreadStorage.
250
251 \sa hasLocalData()
252*/
253
254/*!
255 \fn template <class T> bool QThreadStorage<T>::hasLocalData() const
256
257 If T is a pointer type, returns \c true if the calling thread has
258 non-zero data available.
259
260 If T is a value type, returns whether the data has already been
261 constructed by calling setLocalData or localData.
262
263 \sa localData()
264*/
265
266/*!
267 \fn template <class T> T &QThreadStorage<T>::localData()
268
269 Returns a reference to the data that was set by the calling
270 thread.
271
272 If no data has been set, this will create a default constructed
273 instance of type T.
274
275 \sa hasLocalData()
276*/
277
278/*!
279 \fn template <class T> const T QThreadStorage<T>::localData() const
280 \overload
281
282 Returns a copy of the data that was set by the calling thread.
283
284 \sa hasLocalData()
285*/
286
287/*!
288 \fn template <class T> void QThreadStorage<T>::setLocalData(T data)
289
290 Sets the local data for the calling thread to \a data. It can be
291 accessed later using the localData() functions.
292
293 If T is a pointer type, QThreadStorage takes ownership of the data
294 and deletes it automatically either when the thread exits (either
295 normally or via termination) or when setLocalData() is called again.
296
297 \sa localData(), hasLocalData()
298*/
299
300QT_END_NAMESPACE
Definition qlist.h:82
QMutex QBasicMutex
Definition qmutex.h:360
static Q_CONSTINIT QBasicMutex destructorsMutex
#define DEBUG_MSG
QList< void(*)(void *)> DestructorMap