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
47class tst_QThreadStorage : public QObject
48{
49 Q_OBJECT
50private 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
64class Pointer
65{
66public:
67 static int count;
68 inline Pointer() { ++count; }
69 inline ~Pointer() { --count; }
70};
71int Pointer::count = 0;
72
73void 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
83void 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
96void 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
110void 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
120class Thread : public QThread
121{
122public:
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
142void 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
160bool threadStorageOk;
161void 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
184unsigned __stdcall testAdoptedThreadStorageWinRT(void *p)
185{
186 testAdoptedThreadStorageWin(p);
187 return 0;
188}
189#endif
190void *testAdoptedThreadStorageUnix(void *pointers)
191{
192 testAdoptedThreadStorageWin(p: pointers);
193 return 0;
194}
195void 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
227QBasicAtomicInt cleanupOrder = Q_BASIC_ATOMIC_INITIALIZER(0);
228
229class First
230{
231public:
232 ~First()
233 {
234 order = cleanupOrder.fetchAndAddRelaxed(valueToAdd: 1);
235 }
236 static int order;
237};
238int First::order = -1;
239
240class Second
241{
242public:
243 ~Second()
244 {
245 order = cleanupOrder.fetchAndAddRelaxed(valueToAdd: 1);
246 }
247 static int order;
248};
249int Second::order = -1;
250
251void 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)
284static 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
306void 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.
329class SPointer
330{
331public:
332 static QBasicAtomicInt count;
333 inline SPointer() { count.ref(); }
334 inline ~SPointer() { count.deref(); }
335 inline SPointer(const SPointer & /* other */) { count.ref(); }
336};
337QBasicAtomicInt SPointer::count = Q_BASIC_ATOMIC_INITIALIZER(0);
338
339Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers1)
340Q_GLOBAL_STATIC(QThreadStorage<SPointer *>, threadStoragePointers2)
341
342class ThreadStorageLocalDataTester
343{
344public:
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
357void 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
400class ThreadStorageResetLocalDataTester {
401public:
402 SPointer member;
403 ~ThreadStorageResetLocalDataTester();
404};
405
406Q_GLOBAL_STATIC(QThreadStorage<ThreadStorageResetLocalDataTester *>, ThreadStorageResetLocalDataTesterTls)
407
408ThreadStorageResetLocalDataTester::~ThreadStorageResetLocalDataTester() {
409 //Quite stupid, but WTF::ThreadSpecific<T>::destroy does it.
410 ThreadStorageResetLocalDataTesterTls()->setLocalData(this);
411}
412
413void 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
442void tst_QThreadStorage::valueBased()
443{
444 struct Thread : QThread {
445 QThreadStorage<SPointer> &tlsSPointer;
446 QThreadStorage<QString> &tlsString;
447 QThreadStorage<int> &tlsInt;
448
449 int someNumber;
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
514QTEST_MAIN(tst_QThreadStorage)
515#include "tst_qthreadstorage.moc"
516

source code of qtbase/tests/auto/corelib/thread/qthreadstorage/tst_qthreadstorage.cpp