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

source code of qtbase/src/corelib/thread/qthreadstorage.cpp