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 <qthread.h>
33#include <qsemaphore.h>
34
35class tst_QSemaphore : public QObject
36{
37 Q_OBJECT
38private slots:
39 void acquire();
40 void multiRelease();
41 void multiAcquireRelease();
42 void tryAcquire();
43 void tryAcquireWithTimeout_data();
44 void tryAcquireWithTimeout();
45 void tryAcquireWithTimeoutStarvation();
46 void tryAcquireWithTimeoutForever_data();
47 void tryAcquireWithTimeoutForever();
48 void producerConsumer();
49 void raii();
50};
51
52static QSemaphore *semaphore = 0;
53
54class ThreadOne : public QThread
55{
56public:
57 ThreadOne() {}
58
59protected:
60 void run()
61 {
62 int i = 0;
63 while ( i < 100 ) {
64 semaphore->acquire();
65 i++;
66 semaphore->release();
67 }
68 }
69};
70
71class ThreadN : public QThread
72{
73 int N;
74
75public:
76 ThreadN(int n) :N(n) { }
77
78protected:
79 void run()
80 {
81 int i = 0;
82 while ( i < 100 ) {
83 semaphore->acquire(n: N);
84 i++;
85 semaphore->release(n: N);
86 }
87 }
88};
89
90void tst_QSemaphore::acquire()
91{
92 {
93 // old incrementOne() test
94 QVERIFY(!semaphore);
95 semaphore = new QSemaphore;
96 // make some "thing" available
97 semaphore->release();
98
99 ThreadOne t1;
100 ThreadOne t2;
101
102 t1.start();
103 t2.start();
104
105 QVERIFY(t1.wait(4000));
106 QVERIFY(t2.wait(4000));
107
108 delete semaphore;
109 semaphore = 0;
110 }
111
112 // old incrementN() test
113 {
114 QVERIFY(!semaphore);
115 semaphore = new QSemaphore;
116 // make 4 "things" available
117 semaphore->release(n: 4);
118
119 ThreadN t1(2);
120 ThreadN t2(3);
121
122 t1.start();
123 t2.start();
124
125 QVERIFY(t1.wait(4000));
126 QVERIFY(t2.wait(4000));
127
128 delete semaphore;
129 semaphore = 0;
130 }
131
132 QSemaphore semaphore;
133
134 QCOMPARE(semaphore.available(), 0);
135 semaphore.release();
136 QCOMPARE(semaphore.available(), 1);
137 semaphore.release();
138 QCOMPARE(semaphore.available(), 2);
139 semaphore.release(n: 10);
140 QCOMPARE(semaphore.available(), 12);
141 semaphore.release(n: 10);
142 QCOMPARE(semaphore.available(), 22);
143
144 semaphore.acquire();
145 QCOMPARE(semaphore.available(), 21);
146 semaphore.acquire();
147 QCOMPARE(semaphore.available(), 20);
148 semaphore.acquire(n: 10);
149 QCOMPARE(semaphore.available(), 10);
150 semaphore.acquire(n: 10);
151 QCOMPARE(semaphore.available(), 0);
152}
153
154void tst_QSemaphore::multiRelease()
155{
156 class Thread : public QThread
157 {
158 public:
159 QSemaphore &sem;
160 Thread(QSemaphore &sem) : sem(sem) {}
161
162 void run() override
163 {
164 sem.acquire();
165 }
166 };
167
168 QSemaphore sem;
169 QVector<Thread *> threads;
170 threads.resize(size: 4);
171
172 for (Thread *&t : threads)
173 t = new Thread(sem);
174 for (Thread *&t : threads)
175 t->start();
176
177 // wait for all threads to reach the sem.acquire() and then
178 // release them all
179 QTest::qSleep(ms: 1);
180 sem.release(n: threads.size());
181
182 for (Thread *&t : threads)
183 t->wait();
184 qDeleteAll(c: threads);
185}
186
187void tst_QSemaphore::multiAcquireRelease()
188{
189 class Thread : public QThread
190 {
191 public:
192 QSemaphore &sem;
193 Thread(QSemaphore &sem) : sem(sem) {}
194
195 void run() override
196 {
197 sem.acquire();
198 sem.release();
199 }
200 };
201
202 QSemaphore sem;
203 QVector<Thread *> threads;
204 threads.resize(size: 4);
205
206 for (Thread *&t : threads)
207 t = new Thread(sem);
208 for (Thread *&t : threads)
209 t->start();
210
211 // wait for all threads to reach the sem.acquire() and then
212 // release them all
213 QTest::qSleep(ms: 1);
214 sem.release();
215
216 for (Thread *&t : threads)
217 t->wait();
218 qDeleteAll(c: threads);
219}
220
221void tst_QSemaphore::tryAcquire()
222{
223 QSemaphore semaphore;
224
225 QCOMPARE(semaphore.available(), 0);
226
227 semaphore.release();
228 QCOMPARE(semaphore.available(), 1);
229 QVERIFY(!semaphore.tryAcquire(2));
230 QVERIFY(!semaphore.tryAcquire(2, 0));
231 QCOMPARE(semaphore.available(), 1);
232
233 semaphore.release();
234 QCOMPARE(semaphore.available(), 2);
235 QVERIFY(!semaphore.tryAcquire(3));
236 QVERIFY(!semaphore.tryAcquire(3, 0));
237 QCOMPARE(semaphore.available(), 2);
238
239 semaphore.release(n: 10);
240 QCOMPARE(semaphore.available(), 12);
241 QVERIFY(!semaphore.tryAcquire(100));
242 QVERIFY(!semaphore.tryAcquire(100, 0));
243 QCOMPARE(semaphore.available(), 12);
244
245 semaphore.release(n: 10);
246 QCOMPARE(semaphore.available(), 22);
247 QVERIFY(!semaphore.tryAcquire(100));
248 QVERIFY(!semaphore.tryAcquire(100, 0));
249 QCOMPARE(semaphore.available(), 22);
250
251 QVERIFY(semaphore.tryAcquire());
252 QCOMPARE(semaphore.available(), 21);
253
254 QVERIFY(semaphore.tryAcquire());
255 QCOMPARE(semaphore.available(), 20);
256
257 semaphore.release(n: 2);
258 QVERIFY(semaphore.tryAcquire(1, 0));
259 QCOMPARE(semaphore.available(), 21);
260
261 QVERIFY(semaphore.tryAcquire(1, 0));
262 QCOMPARE(semaphore.available(), 20);
263
264 QVERIFY(semaphore.tryAcquire(10));
265 QCOMPARE(semaphore.available(), 10);
266
267 semaphore.release(n: 10);
268 QVERIFY(semaphore.tryAcquire(10, 0));
269 QCOMPARE(semaphore.available(), 10);
270
271 QVERIFY(semaphore.tryAcquire(10));
272 QCOMPARE(semaphore.available(), 0);
273
274 // should not be able to acquire more
275 QVERIFY(!semaphore.tryAcquire());
276 QVERIFY(!semaphore.tryAcquire(1, 0));
277 QCOMPARE(semaphore.available(), 0);
278
279 QVERIFY(!semaphore.tryAcquire());
280 QVERIFY(!semaphore.tryAcquire(1, 0));
281 QCOMPARE(semaphore.available(), 0);
282
283 QVERIFY(!semaphore.tryAcquire(10));
284 QVERIFY(!semaphore.tryAcquire(10, 0));
285 QCOMPARE(semaphore.available(), 0);
286
287 QVERIFY(!semaphore.tryAcquire(10));
288 QVERIFY(!semaphore.tryAcquire(10, 0));
289 QCOMPARE(semaphore.available(), 0);
290}
291
292void tst_QSemaphore::tryAcquireWithTimeout_data()
293{
294 QTest::addColumn<int>(name: "timeout");
295
296 QTest::newRow(dataTag: "0.2s") << 200;
297 QTest::newRow(dataTag: "2s") << 2000;
298}
299
300void tst_QSemaphore::tryAcquireWithTimeout()
301{
302 QFETCH(int, timeout);
303
304 // timers are not guaranteed to be accurate down to the last millisecond,
305 // so we permit the elapsed times to be up to this far from the expected value.
306 int fuzz = 50 + (timeout / 20);
307
308 QSemaphore semaphore;
309 QElapsedTimer time;
310
311#define FUZZYCOMPARE(a,e) \
312 do { \
313 int a1 = a; \
314 int e1 = e; \
315 QVERIFY2(qAbs(a1-e1) < fuzz, \
316 qPrintable(QString("(%1=%2) is more than %3 milliseconds different from (%4=%5)") \
317 .arg(#a).arg(a1).arg(fuzz).arg(#e).arg(e1))); \
318 } while (0)
319
320 QCOMPARE(semaphore.available(), 0);
321
322 semaphore.release();
323 QCOMPARE(semaphore.available(), 1);
324 time.start();
325 QVERIFY(!semaphore.tryAcquire(2, timeout));
326 FUZZYCOMPARE(time.elapsed(), timeout);
327 QCOMPARE(semaphore.available(), 1);
328
329 semaphore.release();
330 QCOMPARE(semaphore.available(), 2);
331 time.start();
332 QVERIFY(!semaphore.tryAcquire(3, timeout));
333 FUZZYCOMPARE(time.elapsed(), timeout);
334 QCOMPARE(semaphore.available(), 2);
335
336 semaphore.release(n: 10);
337 QCOMPARE(semaphore.available(), 12);
338 time.start();
339 QVERIFY(!semaphore.tryAcquire(100, timeout));
340 FUZZYCOMPARE(time.elapsed(), timeout);
341 QCOMPARE(semaphore.available(), 12);
342
343 semaphore.release(n: 10);
344 QCOMPARE(semaphore.available(), 22);
345 time.start();
346 QVERIFY(!semaphore.tryAcquire(100, timeout));
347 FUZZYCOMPARE(time.elapsed(), timeout);
348 QCOMPARE(semaphore.available(), 22);
349
350 time.start();
351 QVERIFY(semaphore.tryAcquire(1, timeout));
352 FUZZYCOMPARE(time.elapsed(), 0);
353 QCOMPARE(semaphore.available(), 21);
354
355 time.start();
356 QVERIFY(semaphore.tryAcquire(1, timeout));
357 FUZZYCOMPARE(time.elapsed(), 0);
358 QCOMPARE(semaphore.available(), 20);
359
360 time.start();
361 QVERIFY(semaphore.tryAcquire(10, timeout));
362 FUZZYCOMPARE(time.elapsed(), 0);
363 QCOMPARE(semaphore.available(), 10);
364
365 time.start();
366 QVERIFY(semaphore.tryAcquire(10, timeout));
367 FUZZYCOMPARE(time.elapsed(), 0);
368 QCOMPARE(semaphore.available(), 0);
369
370 // should not be able to acquire more
371 time.start();
372 QVERIFY(!semaphore.tryAcquire(1, timeout));
373 FUZZYCOMPARE(time.elapsed(), timeout);
374 QCOMPARE(semaphore.available(), 0);
375
376 time.start();
377 QVERIFY(!semaphore.tryAcquire(1, timeout));
378 FUZZYCOMPARE(time.elapsed(), timeout);
379 QCOMPARE(semaphore.available(), 0);
380
381 time.start();
382 QVERIFY(!semaphore.tryAcquire(10, timeout));
383 FUZZYCOMPARE(time.elapsed(), timeout);
384 QCOMPARE(semaphore.available(), 0);
385
386 time.start();
387 QVERIFY(!semaphore.tryAcquire(10, timeout));
388 FUZZYCOMPARE(time.elapsed(), timeout);
389 QCOMPARE(semaphore.available(), 0);
390
391#undef FUZZYCOMPARE
392}
393
394void tst_QSemaphore::tryAcquireWithTimeoutStarvation()
395{
396 class Thread : public QThread
397 {
398 public:
399 QSemaphore startup;
400 QSemaphore *semaphore;
401 int amountToConsume, timeout;
402
403 void run()
404 {
405 startup.release();
406 forever {
407 if (!semaphore->tryAcquire(n: amountToConsume, timeout))
408 break;
409 semaphore->release(n: amountToConsume);
410 }
411 }
412 };
413
414 QSemaphore semaphore;
415 semaphore.release(n: 1);
416
417 Thread consumer;
418 consumer.semaphore = &semaphore;
419 consumer.amountToConsume = 1;
420 consumer.timeout = 1000;
421
422 // start the thread and wait for it to start consuming
423 consumer.start();
424 consumer.startup.acquire();
425
426 // try to consume more than the thread we started is, and provide a longer
427 // timeout... we should timeout, not wait indefinitely
428 QVERIFY(!semaphore.tryAcquire(consumer.amountToConsume * 2, consumer.timeout * 2));
429
430 // the consumer should still be running
431 QVERIFY(consumer.isRunning() && !consumer.isFinished());
432
433 // acquire, and wait for smallConsumer to timeout
434 semaphore.acquire();
435 QVERIFY(consumer.wait());
436}
437
438void tst_QSemaphore::tryAcquireWithTimeoutForever_data()
439{
440 QTest::addColumn<int>(name: "timeout");
441 QTest::newRow(dataTag: "-1") << -1;
442
443 // tryAcquire is documented to take any negative value as "forever"
444 QTest::newRow(dataTag: "INT_MIN") << INT_MIN;
445}
446
447void tst_QSemaphore::tryAcquireWithTimeoutForever()
448{
449 enum { WaitTime = 1000 };
450 struct Thread : public QThread {
451 QSemaphore sem;
452
453 void run() override
454 {
455 QTest::qWait(ms: WaitTime);
456 sem.release(n: 2);
457 }
458 };
459
460 QFETCH(int, timeout);
461 Thread t;
462
463 // sanity check it works if we can immediately acquire
464 t.sem.release(n: 11);
465 QVERIFY(t.sem.tryAcquire(1, timeout));
466 QVERIFY(t.sem.tryAcquire(10, timeout));
467
468 // verify that we do wait for at least WaitTime if we can't acquire immediately
469 QElapsedTimer timer;
470 timer.start();
471 t.start();
472 QVERIFY(t.sem.tryAcquire(1, timeout));
473 QVERIFY(timer.elapsed() >= WaitTime);
474
475 QVERIFY(t.wait());
476
477 QCOMPARE(t.sem.available(), 1);
478}
479
480const char alphabet[] = "ACGTH";
481const int AlphabetSize = sizeof(alphabet) - 1;
482
483const int BufferSize = 4096; // GCD of BufferSize and alphabet size must be 1
484char buffer[BufferSize];
485
486const int ProducerChunkSize = 3;
487const int ConsumerChunkSize = 7;
488const int Multiplier = 10;
489
490// note: the code depends on the fact that DataSize is a multiple of
491// ProducerChunkSize, ConsumerChunkSize, and BufferSize
492const int DataSize = ProducerChunkSize * ConsumerChunkSize * BufferSize * Multiplier;
493
494QSemaphore freeSpace(BufferSize);
495QSemaphore usedSpace;
496
497class Producer : public QThread
498{
499public:
500 void run();
501};
502
503static const int Timeout = 60 * 1000; // 1min
504
505void Producer::run()
506{
507 for (int i = 0; i < DataSize; ++i) {
508 QVERIFY(freeSpace.tryAcquire(1, Timeout));
509 buffer[i % BufferSize] = alphabet[i % AlphabetSize];
510 usedSpace.release();
511 }
512 for (int i = 0; i < DataSize; ++i) {
513 if ((i % ProducerChunkSize) == 0)
514 QVERIFY(freeSpace.tryAcquire(ProducerChunkSize, Timeout));
515 buffer[i % BufferSize] = alphabet[i % AlphabetSize];
516 if ((i % ProducerChunkSize) == (ProducerChunkSize - 1))
517 usedSpace.release(n: ProducerChunkSize);
518 }
519}
520
521class Consumer : public QThread
522{
523public:
524 void run();
525};
526
527void Consumer::run()
528{
529 for (int i = 0; i < DataSize; ++i) {
530 usedSpace.acquire();
531 QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
532 freeSpace.release();
533 }
534 for (int i = 0; i < DataSize; ++i) {
535 if ((i % ConsumerChunkSize) == 0)
536 usedSpace.acquire(n: ConsumerChunkSize);
537 QCOMPARE(buffer[i % BufferSize], alphabet[i % AlphabetSize]);
538 if ((i % ConsumerChunkSize) == (ConsumerChunkSize - 1))
539 freeSpace.release(n: ConsumerChunkSize);
540 }
541}
542
543void tst_QSemaphore::producerConsumer()
544{
545 Producer producer;
546 Consumer consumer;
547 producer.start();
548 consumer.start();
549 producer.wait();
550 consumer.wait();
551}
552
553void tst_QSemaphore::raii()
554{
555 QSemaphore sem;
556
557 QCOMPARE(sem.available(), 0);
558
559 // basic operation:
560 {
561 QSemaphoreReleaser r0;
562 const QSemaphoreReleaser r1(sem);
563 const QSemaphoreReleaser r2(sem, 2);
564
565 QCOMPARE(r0.semaphore(), nullptr);
566 QCOMPARE(r1.semaphore(), &sem);
567 QCOMPARE(r2.semaphore(), &sem);
568 }
569
570 QCOMPARE(sem.available(), 3);
571
572 // cancel:
573 {
574 const QSemaphoreReleaser r1(sem);
575 QSemaphoreReleaser r2(sem, 2);
576
577 QCOMPARE(r2.cancel(), &sem);
578 QCOMPARE(r2.semaphore(), nullptr);
579 }
580
581 QCOMPARE(sem.available(), 4);
582
583 // move-assignment:
584 {
585 const QSemaphoreReleaser r1(sem);
586 QSemaphoreReleaser r2(sem, 2);
587
588 QCOMPARE(sem.available(), 4);
589
590 r2 = QSemaphoreReleaser();
591
592 QCOMPARE(sem.available(), 6);
593
594 r2 = QSemaphoreReleaser(sem, 42);
595
596 QCOMPARE(sem.available(), 6);
597 }
598
599 QCOMPARE(sem.available(), 49);
600}
601
602QTEST_MAIN(tst_QSemaphore)
603#include "tst_qsemaphore.moc"
604

source code of qtbase/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp