1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <qcoreapplication.h> |
32 | #include <qmutex.h> |
33 | #include <qthread.h> |
34 | #include <qwaitcondition.h> |
35 | #include <qthreadstorage.h> |
36 | #include <qdir.h> |
37 | #include <qfileinfo.h> |
38 | |
39 | #ifdef Q_OS_UNIX |
40 | #include <pthread.h> |
41 | #endif |
42 | #ifdef Q_OS_WIN |
43 | # include <process.h> |
44 | # include <qt_windows.h> |
45 | #endif |
46 | |
47 | class tst_QThreadStorage : public QObject |
48 | { |
49 | Q_OBJECT |
50 | private slots: |
51 | void hasLocalData(); |
52 | void localData(); |
53 | void localData_const(); |
54 | void setLocalData(); |
55 | void autoDelete(); |
56 | void adoptedThreads(); |
57 | void ensureCleanupOrder(); |
58 | void crashOnExit(); |
59 | void leakInDestructor(); |
60 | void resetInDestructor(); |
61 | void valueBased(); |
62 | }; |
63 | |
64 | class Pointer |
65 | { |
66 | public: |
67 | static int count; |
68 | inline Pointer() { ++count; } |
69 | inline ~Pointer() { --count; } |
70 | }; |
71 | int Pointer::count = 0; |
72 | |
73 | void tst_QThreadStorage::hasLocalData() |
74 | { |
75 | QThreadStorage<Pointer *> pointers; |
76 | QVERIFY(!pointers.hasLocalData()); |
77 | pointers.setLocalData(new Pointer); |
78 | QVERIFY(pointers.hasLocalData()); |
79 | pointers.setLocalData(0); |
80 | QVERIFY(!pointers.hasLocalData()); |
81 | } |
82 | |
83 | void tst_QThreadStorage::localData() |
84 | { |
85 | QThreadStorage<Pointer*> pointers; |
86 | Pointer *p = new Pointer; |
87 | QVERIFY(!pointers.hasLocalData()); |
88 | pointers.setLocalData(p); |
89 | QVERIFY(pointers.hasLocalData()); |
90 | QCOMPARE(pointers.localData(), p); |
91 | pointers.setLocalData(0); |
92 | QCOMPARE(pointers.localData(), (Pointer *)0); |
93 | QVERIFY(!pointers.hasLocalData()); |
94 | } |
95 | |
96 | void tst_QThreadStorage::localData_const() |
97 | { |
98 | QThreadStorage<Pointer *> pointers; |
99 | const QThreadStorage<Pointer *> &const_pointers = pointers; |
100 | Pointer *p = new Pointer; |
101 | QVERIFY(!pointers.hasLocalData()); |
102 | pointers.setLocalData(p); |
103 | QVERIFY(pointers.hasLocalData()); |
104 | QCOMPARE(const_pointers.localData(), p); |
105 | pointers.setLocalData(0); |
106 | QCOMPARE(const_pointers.localData(), (Pointer *)0); |
107 | QVERIFY(!pointers.hasLocalData()); |
108 | } |
109 | |
110 | void tst_QThreadStorage::setLocalData() |
111 | { |
112 | QThreadStorage<Pointer *> pointers; |
113 | QVERIFY(!pointers.hasLocalData()); |
114 | pointers.setLocalData(new Pointer); |
115 | QVERIFY(pointers.hasLocalData()); |
116 | pointers.setLocalData(0); |
117 | QVERIFY(!pointers.hasLocalData()); |
118 | } |
119 | |
120 | class Thread : public QThread |
121 | { |
122 | public: |
123 | QThreadStorage<Pointer *> &pointers; |
124 | |
125 | QMutex mutex; |
126 | QWaitCondition cond; |
127 | |
128 | Thread(QThreadStorage<Pointer *> &p) |
129 | : pointers(p) |
130 | { } |
131 | |
132 | void run() |
133 | { |
134 | pointers.setLocalData(new Pointer); |
135 | |
136 | QMutexLocker locker(&mutex); |
137 | cond.wakeOne(); |
138 | cond.wait(lockedMutex: &mutex); |
139 | } |
140 | }; |
141 | |
142 | void tst_QThreadStorage::autoDelete() |
143 | { |
144 | QThreadStorage<Pointer *> pointers; |
145 | QVERIFY(!pointers.hasLocalData()); |
146 | |
147 | Thread thread(pointers); |
148 | int c = Pointer::count; |
149 | { |
150 | QMutexLocker locker(&thread.mutex); |
151 | thread.start(); |
152 | thread.cond.wait(lockedMutex: &thread.mutex); |
153 | // QCOMPARE(Pointer::count, c + 1); |
154 | thread.cond.wakeOne(); |
155 | } |
156 | thread.wait(); |
157 | QCOMPARE(Pointer::count, c); |
158 | } |
159 | |
160 | bool threadStorageOk; |
161 | void testAdoptedThreadStorageWin(void *p) |
162 | { |
163 | QThreadStorage<Pointer *> *pointers = reinterpret_cast<QThreadStorage<Pointer *> *>(p); |
164 | if (pointers->hasLocalData()) { |
165 | threadStorageOk = false; |
166 | return; |
167 | } |
168 | |
169 | Pointer *pointer = new Pointer(); |
170 | pointers->setLocalData(pointer); |
171 | |
172 | if (pointers->hasLocalData() == false) { |
173 | threadStorageOk = false; |
174 | return; |
175 | } |
176 | |
177 | if (pointers->localData() != pointer) { |
178 | threadStorageOk = false; |
179 | return; |
180 | } |
181 | QObject::connect(sender: QThread::currentThread(), SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop())); |
182 | } |
183 | #ifdef Q_OS_WINRT |
184 | unsigned __stdcall testAdoptedThreadStorageWinRT(void *p) |
185 | { |
186 | testAdoptedThreadStorageWin(p); |
187 | return 0; |
188 | } |
189 | #endif |
190 | void *testAdoptedThreadStorageUnix(void *pointers) |
191 | { |
192 | testAdoptedThreadStorageWin(p: pointers); |
193 | return 0; |
194 | } |
195 | void tst_QThreadStorage::adoptedThreads() |
196 | { |
197 | QTestEventLoop::instance(); // Make sure the instance is created in this thread. |
198 | QThreadStorage<Pointer *> pointers; |
199 | int c = Pointer::count; |
200 | threadStorageOk = true; |
201 | { |
202 | #ifdef Q_OS_UNIX |
203 | pthread_t thread; |
204 | const int state = pthread_create(newthread: &thread, attr: 0, start_routine: testAdoptedThreadStorageUnix, arg: &pointers); |
205 | QCOMPARE(state, 0); |
206 | pthread_join(th: thread, thread_return: 0); |
207 | #elif defined Q_OS_WINRT |
208 | HANDLE thread; |
209 | thread = (HANDLE) _beginthreadex(NULL, 0, testAdoptedThreadStorageWinRT, &pointers, 0, 0); |
210 | QVERIFY(thread); |
211 | WaitForSingleObjectEx(thread, INFINITE, FALSE); |
212 | #elif defined Q_OS_WIN |
213 | HANDLE thread; |
214 | thread = (HANDLE)_beginthread(testAdoptedThreadStorageWin, 0, &pointers); |
215 | QVERIFY(thread); |
216 | WaitForSingleObject(thread, INFINITE); |
217 | #endif |
218 | } |
219 | QVERIFY(threadStorageOk); |
220 | |
221 | QTestEventLoop::instance().enterLoop(secs: 2); |
222 | QVERIFY(!QTestEventLoop::instance().timeout()); |
223 | |
224 | QTRY_COMPARE(Pointer::count, c); |
225 | } |
226 | |
227 | QBasicAtomicInt cleanupOrder = Q_BASIC_ATOMIC_INITIALIZER(0); |
228 | |
229 | class First |
230 | { |
231 | public: |
232 | ~First() |
233 | { |
234 | order = cleanupOrder.fetchAndAddRelaxed(valueToAdd: 1); |
235 | } |
236 | static int order; |
237 | }; |
238 | int First::order = -1; |
239 | |
240 | class Second |
241 | { |
242 | public: |
243 | ~Second() |
244 | { |
245 | order = cleanupOrder.fetchAndAddRelaxed(valueToAdd: 1); |
246 | } |
247 | static int order; |
248 | }; |
249 | int Second::order = -1; |
250 | |
251 | void tst_QThreadStorage::ensureCleanupOrder() |
252 | { |
253 | class Thread : public QThread |
254 | { |
255 | public: |
256 | QThreadStorage<First *> &first; |
257 | QThreadStorage<Second *> &second; |
258 | |
259 | Thread(QThreadStorage<First *> &first, |
260 | QThreadStorage<Second *> &second) |
261 | : first(first), second(second) |
262 | { } |
263 | |
264 | void run() |
265 | { |
266 | // set in reverse order, but shouldn't matter, the data |
267 | // will be deleted in the order the thread storage objects |
268 | // were created |
269 | second.setLocalData(new Second); |
270 | first.setLocalData(new First); |
271 | } |
272 | }; |
273 | |
274 | QThreadStorage<Second *> second; |
275 | QThreadStorage<First *> first; |
276 | Thread thread(first, second); |
277 | thread.start(); |
278 | thread.wait(); |
279 | |
280 | QVERIFY(First::order < Second::order); |
281 | } |
282 | |
283 | #if QT_CONFIG(process) |
284 | static inline bool runCrashOnExit(const QString &binary, QString *errorMessage) |
285 | { |
286 | const int timeout = 60000; |
287 | QProcess process; |
288 | process.start(command: binary); |
289 | if (!process.waitForStarted()) { |
290 | *errorMessage = QString::fromLatin1(str: "Could not start '%1': %2" ).arg(args: binary, args: process.errorString()); |
291 | return false; |
292 | } |
293 | if (!process.waitForFinished(msecs: timeout)) { |
294 | process.kill(); |
295 | *errorMessage = QString::fromLatin1(str: "Timeout (%1ms) waiting for %2." ).arg(a: timeout).arg(a: binary); |
296 | return false; |
297 | } |
298 | if (process.exitStatus() != QProcess::NormalExit) { |
299 | *errorMessage = binary + QStringLiteral(" crashed." ); |
300 | return false; |
301 | } |
302 | return true; |
303 | } |
304 | #endif |
305 | |
306 | void tst_QThreadStorage::crashOnExit() |
307 | { |
308 | #if !QT_CONFIG(process) |
309 | QSKIP("No qprocess support" , SkipAll); |
310 | #else |
311 | QString errorMessage; |
312 | |
313 | // Add the executable's directory to path so that we can find the test helper next to it |
314 | // in a cross-platform way. We must do this because the CWD is not pointing to this directory |
315 | // in debug-and-release builds. |
316 | QByteArray path = qgetenv(varName: "PATH" ); |
317 | qputenv(varName: "PATH" , |
318 | value: path + QDir::listSeparator().toLatin1() |
319 | + QCoreApplication::applicationDirPath().toLocal8Bit()); |
320 | auto restore = qScopeGuard(f: [&] { qputenv(varName: "PATH" , value: path); }); |
321 | |
322 | QString binary = QStringLiteral("crashOnExit_helper" ); |
323 | QVERIFY2(runCrashOnExit(binary, &errorMessage), |
324 | qPrintable(errorMessage)); |
325 | #endif |
326 | } |
327 | |
328 | // S stands for thread Safe. |
329 | class SPointer |
330 | { |
331 | public: |
332 | static QBasicAtomicInt count; |
333 | inline SPointer() { count.ref(); } |
334 | inline ~SPointer() { count.deref(); } |
335 | inline SPointer(const SPointer & /* other */) { count.ref(); } |
336 | }; |
337 | QBasicAtomicInt SPointer::count = Q_BASIC_ATOMIC_INITIALIZER(0); |
338 | |
339 | Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers1) |
340 | Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers2) |
341 | |
342 | class ThreadStorageLocalDataTester |
343 | { |
344 | public: |
345 | SPointer member; |
346 | inline ~ThreadStorageLocalDataTester() { |
347 | QVERIFY(!threadStoragePointers1()->hasLocalData()); |
348 | QVERIFY(!threadStoragePointers2()->hasLocalData()); |
349 | threadStoragePointers2()->setLocalData(new SPointer); |
350 | threadStoragePointers1()->setLocalData(new SPointer); |
351 | QVERIFY(threadStoragePointers1()->hasLocalData()); |
352 | QVERIFY(threadStoragePointers2()->hasLocalData()); |
353 | } |
354 | }; |
355 | |
356 | |
357 | void tst_QThreadStorage::leakInDestructor() |
358 | { |
359 | class Thread : public QThread |
360 | { |
361 | public: |
362 | QThreadStorage<ThreadStorageLocalDataTester *> &tls; |
363 | |
364 | Thread(QThreadStorage<ThreadStorageLocalDataTester *> &t) : tls(t) { } |
365 | |
366 | void run() |
367 | { |
368 | QVERIFY(!tls.hasLocalData()); |
369 | tls.setLocalData(new ThreadStorageLocalDataTester); |
370 | QVERIFY(tls.hasLocalData()); |
371 | } |
372 | }; |
373 | int c = SPointer::count.loadRelaxed(); |
374 | |
375 | QThreadStorage<ThreadStorageLocalDataTester *> tls; |
376 | |
377 | QVERIFY(!threadStoragePointers1()->hasLocalData()); |
378 | QThreadStorage<int *> tls2; //add some more tls to make sure ids are not following each other too much |
379 | QThreadStorage<int *> tls3; |
380 | QVERIFY(!tls2.hasLocalData()); |
381 | QVERIFY(!tls3.hasLocalData()); |
382 | QVERIFY(!tls.hasLocalData()); |
383 | |
384 | Thread t1(tls); |
385 | Thread t2(tls); |
386 | Thread t3(tls); |
387 | |
388 | t1.start(); |
389 | t2.start(); |
390 | t3.start(); |
391 | |
392 | QVERIFY(t1.wait()); |
393 | QVERIFY(t2.wait()); |
394 | QVERIFY(t3.wait()); |
395 | |
396 | //check all the constructed things have been destructed |
397 | QCOMPARE(int(SPointer::count.loadRelaxed()), c); |
398 | } |
399 | |
400 | class ThreadStorageResetLocalDataTester { |
401 | public: |
402 | SPointer member; |
403 | ~ThreadStorageResetLocalDataTester(); |
404 | }; |
405 | |
406 | Q_GLOBAL_STATIC(QThreadStorage<ThreadStorageResetLocalDataTester *>, ThreadStorageResetLocalDataTesterTls) |
407 | |
408 | ThreadStorageResetLocalDataTester::~ThreadStorageResetLocalDataTester() { |
409 | //Quite stupid, but WTF::ThreadSpecific<T>::destroy does it. |
410 | ThreadStorageResetLocalDataTesterTls()->setLocalData(this); |
411 | } |
412 | |
413 | void tst_QThreadStorage::resetInDestructor() |
414 | { |
415 | class Thread : public QThread |
416 | { |
417 | public: |
418 | void run() |
419 | { |
420 | QVERIFY(!ThreadStorageResetLocalDataTesterTls()->hasLocalData()); |
421 | ThreadStorageResetLocalDataTesterTls()->setLocalData(new ThreadStorageResetLocalDataTester); |
422 | QVERIFY(ThreadStorageResetLocalDataTesterTls()->hasLocalData()); |
423 | } |
424 | }; |
425 | int c = SPointer::count.loadRelaxed(); |
426 | |
427 | Thread t1; |
428 | Thread t2; |
429 | Thread t3; |
430 | t1.start(); |
431 | t2.start(); |
432 | t3.start(); |
433 | QVERIFY(t1.wait()); |
434 | QVERIFY(t2.wait()); |
435 | QVERIFY(t3.wait()); |
436 | |
437 | //check all the constructed things have been destructed |
438 | QCOMPARE(int(SPointer::count.loadRelaxed()), c); |
439 | } |
440 | |
441 | |
442 | void tst_QThreadStorage::valueBased() |
443 | { |
444 | struct Thread : QThread { |
445 | QThreadStorage<SPointer> &tlsSPointer; |
446 | QThreadStorage<QString> &tlsString; |
447 | QThreadStorage<int> &tlsInt; |
448 | |
449 | int ; |
450 | QString someString; |
451 | Thread(QThreadStorage<SPointer> &t1, QThreadStorage<QString> &t2, QThreadStorage<int> &t3) |
452 | : tlsSPointer(t1), tlsString(t2), tlsInt(t3) { } |
453 | |
454 | void run() { |
455 | /*QVERIFY(!tlsSPointer.hasLocalData()); |
456 | QVERIFY(!tlsString.hasLocalData()); |
457 | QVERIFY(!tlsInt.hasLocalData());*/ |
458 | SPointer pointercopy = tlsSPointer.localData(); |
459 | |
460 | //Default constructed values |
461 | QVERIFY(tlsString.localData().isNull()); |
462 | QCOMPARE(tlsInt.localData(), 0); |
463 | |
464 | //setting |
465 | tlsString.setLocalData(someString); |
466 | tlsInt.setLocalData(someNumber); |
467 | |
468 | QCOMPARE(tlsString.localData(), someString); |
469 | QCOMPARE(tlsInt.localData(), someNumber); |
470 | |
471 | //changing |
472 | tlsSPointer.setLocalData(SPointer()); |
473 | tlsInt.localData() += 42; |
474 | tlsString.localData().append(s: QLatin1String(" world" )); |
475 | |
476 | QCOMPARE(tlsString.localData(), (someString + QLatin1String(" world" ))); |
477 | QCOMPARE(tlsInt.localData(), (someNumber + 42)); |
478 | |
479 | // operator= |
480 | tlsString.localData() = QString::number(someNumber); |
481 | QCOMPARE(tlsString.localData().toInt(), someNumber); |
482 | } |
483 | }; |
484 | |
485 | QThreadStorage<SPointer> tlsSPointer; |
486 | QThreadStorage<QString> tlsString; |
487 | QThreadStorage<int> tlsInt; |
488 | |
489 | int c = SPointer::count.loadRelaxed(); |
490 | |
491 | Thread t1(tlsSPointer, tlsString, tlsInt); |
492 | Thread t2(tlsSPointer, tlsString, tlsInt); |
493 | Thread t3(tlsSPointer, tlsString, tlsInt); |
494 | t1.someNumber = 42; |
495 | t2.someNumber = -128; |
496 | t3.someNumber = 78; |
497 | t1.someString = "hello" ; |
498 | t2.someString = "australia" ; |
499 | t3.someString = "nokia" ; |
500 | |
501 | t1.start(); |
502 | t2.start(); |
503 | t3.start(); |
504 | |
505 | QVERIFY(t1.wait()); |
506 | QVERIFY(t2.wait()); |
507 | QVERIFY(t3.wait()); |
508 | |
509 | QCOMPARE(c, int(SPointer::count.loadRelaxed())); |
510 | |
511 | } |
512 | |
513 | |
514 | QTEST_MAIN(tst_QThreadStorage) |
515 | #include "tst_qthreadstorage.moc" |
516 | |