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 | |
35 | class tst_QSemaphore : public QObject |
36 | { |
37 | Q_OBJECT |
38 | private 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 | |
52 | static QSemaphore *semaphore = 0; |
53 | |
54 | class ThreadOne : public QThread |
55 | { |
56 | public: |
57 | ThreadOne() {} |
58 | |
59 | protected: |
60 | void run() |
61 | { |
62 | int i = 0; |
63 | while ( i < 100 ) { |
64 | semaphore->acquire(); |
65 | i++; |
66 | semaphore->release(); |
67 | } |
68 | } |
69 | }; |
70 | |
71 | class ThreadN : public QThread |
72 | { |
73 | int N; |
74 | |
75 | public: |
76 | ThreadN(int n) :N(n) { } |
77 | |
78 | protected: |
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 | |
90 | void 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 | |
154 | void 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 | |
187 | void 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 | |
221 | void 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 | |
292 | void 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 | |
300 | void 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 | |
394 | void 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 | |
438 | void 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 | |
447 | void 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 | |
480 | const char alphabet[] = "ACGTH" ; |
481 | const int AlphabetSize = sizeof(alphabet) - 1; |
482 | |
483 | const int BufferSize = 4096; // GCD of BufferSize and alphabet size must be 1 |
484 | char buffer[BufferSize]; |
485 | |
486 | const int ProducerChunkSize = 3; |
487 | const int ConsumerChunkSize = 7; |
488 | const int Multiplier = 10; |
489 | |
490 | // note: the code depends on the fact that DataSize is a multiple of |
491 | // ProducerChunkSize, ConsumerChunkSize, and BufferSize |
492 | const int DataSize = ProducerChunkSize * ConsumerChunkSize * BufferSize * Multiplier; |
493 | |
494 | QSemaphore freeSpace(BufferSize); |
495 | QSemaphore usedSpace; |
496 | |
497 | class Producer : public QThread |
498 | { |
499 | public: |
500 | void run(); |
501 | }; |
502 | |
503 | static const int Timeout = 60 * 1000; // 1min |
504 | |
505 | void 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 | |
521 | class Consumer : public QThread |
522 | { |
523 | public: |
524 | void run(); |
525 | }; |
526 | |
527 | void 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 | |
543 | void tst_QSemaphore::producerConsumer() |
544 | { |
545 | Producer producer; |
546 | Consumer consumer; |
547 | producer.start(); |
548 | consumer.start(); |
549 | producer.wait(); |
550 | consumer.wait(); |
551 | } |
552 | |
553 | void 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 | |
602 | QTEST_MAIN(tst_QSemaphore) |
603 | #include "tst_qsemaphore.moc" |
604 | |