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 | |
12 | QT_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> |
20 | void 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 | |
35 | Q_CONSTINIT static QBasicMutex destructorsMutex; |
36 | typedef QList<void (*)(void *)> DestructorMap; |
37 | Q_GLOBAL_STATIC(DestructorMap, destructors) |
38 | |
39 | QThreadStorageData::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 | |
70 | QThreadStorageData::~QThreadStorageData() |
71 | { |
72 | DEBUG_MSG(msg: "QThreadStorageData: Released id %d" , id); |
73 | QMutexLocker locker(&destructorsMutex); |
74 | if (destructors()) |
75 | (*destructors())[id] = nullptr; |
76 | } |
77 | |
78 | void **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 | |
98 | void **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 | |
135 | void 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 | |
289 | QT_END_NAMESPACE |
290 | |