1 // ©2015 Cameron Desrochers
2
3 // Tests various parts of the queue using the actual
4 // full implementation itself, instead of isolated
5 // components. This is much slower, but provides much
6 // better coverage too.
7
8 #define MCDBGQ_USE_RELACY
9 #include "../../concurrentqueue.h"
10
11 #include <string>
12
13 using namespace moodycamel;
14
15 struct SmallConstantTraits : public ConcurrentQueueDefaultTraits
16 {
17 static const size_t BLOCK_SIZE = 2;
18 static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 2;
19 static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 2;
20 static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 1;
21 static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 2;
22 };
23
24 struct MediumConstantTraits : public ConcurrentQueueDefaultTraits
25 {
26 static const size_t BLOCK_SIZE = 4;
27 static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 2;
28 static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 4;
29 static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 2;
30 static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 4;
31 };
32
33 struct Foo {
ctorCountFoo34 static int& ctorCount() { static int c; return c; }
dtorCountFoo35 static int& dtorCount() { static int c; return c; }
resetFoo36 static void reset() { ctorCount() = 0; dtorCount() = 0; }
37
FooFoo38 Foo()
39 : id(-2)
40 {
41 ++ctorCount();
42 }
43
FooFoo44 Foo(int id)
45 : id(id)
46 {
47 ++ctorCount();
48 }
49
FooFoo50 Foo(Foo const& o)
51 : id(o.id)
52 {
53 ++ctorCount();
54 }
55
~FooFoo56 ~Foo()
57 {
58 RL_ASSERT(id != -1);
59 ++dtorCount();
60 id = -1;
61 }
62
63 public:
64 int id;
65 };
66
67
68
69 struct enqueue_explicit_one : rl::test_suite<enqueue_explicit_one, 2>
70 {
71 ConcurrentQueue<int, SmallConstantTraits> q;
72
beforeenqueue_explicit_one73 void before()
74 {
75 }
76
threadenqueue_explicit_one77 void thread(unsigned int tid)
78 {
79 RelacyThreadExitNotifier::notify_relacy_thread_start();
80
81 ProducerToken t(q);
82 q.enqueue(t, tid);
83
84 RelacyThreadExitNotifier::notify_relacy_thread_exit();
85 }
86
afterenqueue_explicit_one87 void after()
88 {
89 int tid0, tid1;
90 RL_ASSERT(q.try_dequeue(tid0));
91 RL_ASSERT(tid0 == 0 || tid0 == 1);
92 RL_ASSERT(q.try_dequeue(tid1));
93 RL_ASSERT(tid1 == 0 || tid1 == 1);
94 RL_ASSERT(tid0 != tid1);
95 RL_ASSERT(!q.try_dequeue(tid0));
96 }
97
invariantenqueue_explicit_one98 void invariant()
99 {
100 }
101 };
102
103
104 struct enqueue_explicit_many : rl::test_suite<enqueue_explicit_many, 3>
105 {
106 ConcurrentQueue<int, SmallConstantTraits> q;
107
beforeenqueue_explicit_many108 void before()
109 {
110 }
111
threadenqueue_explicit_many112 void thread(unsigned int tid)
113 {
114 RelacyThreadExitNotifier::notify_relacy_thread_start();
115
116 ProducerToken t(q);
117 for (int i = 0; i != 5; ++i) {
118 q.enqueue(t, tid * 10 + i);
119 }
120
121 RelacyThreadExitNotifier::notify_relacy_thread_exit();
122 }
123
afterenqueue_explicit_many124 void after()
125 {
126 int item;
127 for (int i = 0; i != 15; ++i) {
128 RL_ASSERT(q.try_dequeue(item));
129 }
130 RL_ASSERT(!q.try_dequeue(item));
131 }
132
invariantenqueue_explicit_many133 void invariant()
134 {
135 }
136 };
137
138
139 // This one caught a bug with the memory ordering in the core dequeue algorithm
140 struct dequeue_some_explicit : rl::test_suite<dequeue_some_explicit, 3>
141 {
142 ConcurrentQueue<int, SmallConstantTraits> q;
143
beforedequeue_some_explicit144 void before()
145 {
146 }
147
threaddequeue_some_explicit148 void thread(unsigned int tid)
149 {
150 RelacyThreadExitNotifier::notify_relacy_thread_start();
151
152 if (tid <= 1) {
153 int item;
154 ConsumerToken t(q);
155 for (int i = 0; i != 5; ++i) {
156 q.try_dequeue(t, item);
157 }
158 }
159 else {
160 ProducerToken t(q);
161 for (int i = 0; i != 3; ++i) {
162 q.enqueue(t, tid * 10 + i);
163 }
164 }
165
166 RelacyThreadExitNotifier::notify_relacy_thread_exit();
167 }
168
afterdequeue_some_explicit169 void after()
170 {
171 }
172
invariantdequeue_some_explicit173 void invariant()
174 {
175 }
176 };
177
178
179 // Causes blocks to be reused
180 struct recycle_blocks_explicit : rl::test_suite<recycle_blocks_explicit, 3>
181 {
182 ConcurrentQueue<int, SmallConstantTraits> q;
183 std::vector<bool> seen;
184
beforerecycle_blocks_explicit185 void before()
186 {
187 seen.resize(8, false);
188 }
189
threadrecycle_blocks_explicit190 void thread(unsigned int tid)
191 {
192 RelacyThreadExitNotifier::notify_relacy_thread_start();
193
194 if (tid == 0) {
195 ProducerToken t(q);
196 for (int i = 0; i != 8; ++i) {
197 q.enqueue(t, i);
198 }
199 }
200 else {
201 int item;
202 ConsumerToken t(q);
203 for (int i = 0; i != 6; ++i) {
204 if (q.try_dequeue(t, item)) {
205 RL_ASSERT(!seen[item]);
206 seen[item] = true;
207 }
208 }
209 }
210
211 RelacyThreadExitNotifier::notify_relacy_thread_exit();
212 }
213
afterrecycle_blocks_explicit214 void after()
215 {
216 int item;
217 while (q.try_dequeue(item)) {
218 RL_ASSERT(!seen[item]);
219 seen[item] = true;
220 }
221 for (auto s : seen) {
222 RL_ASSERT(s);
223 }
224 }
225
invariantrecycle_blocks_explicit226 void invariant()
227 {
228 }
229 };
230
231 // Causes the explicit producer's block index to expand
232 struct expand_block_index_explicit : rl::test_suite<expand_block_index_explicit, 4>
233 {
234 ConcurrentQueue<int, SmallConstantTraits> q;
235 std::vector<bool> seen;
236
beforeexpand_block_index_explicit237 void before()
238 {
239 seen.resize(12, false);
240 }
241
threadexpand_block_index_explicit242 void thread(unsigned int tid)
243 {
244 RelacyThreadExitNotifier::notify_relacy_thread_start();
245
246 if (tid == 0) {
247 ProducerToken t(q);
248 for (int i = 0; i != 12; ++i) {
249 q.enqueue(t, i);
250 }
251 }
252 else {
253 int item;
254 ConsumerToken t(q);
255 for (int i = 0; i != 3; ++i) {
256 if (q.try_dequeue(t, item)) {
257 RL_ASSERT(!seen[item]);
258 seen[item] = true;
259 }
260 }
261 }
262
263 RelacyThreadExitNotifier::notify_relacy_thread_exit();
264 }
265
afterexpand_block_index_explicit266 void after()
267 {
268 int item;
269 while (q.try_dequeue(item)) {
270 RL_ASSERT(!seen[item]);
271 seen[item] = true;
272 }
273 for (auto s : seen) {
274 RL_ASSERT(s);
275 }
276 }
277
invariantexpand_block_index_explicit278 void invariant()
279 {
280 }
281 };
282
283
284 // Tests that implicit producers work at a very basic level
285 struct enqueue_implicit_one : rl::test_suite<enqueue_implicit_one, 2>
286 {
287 ConcurrentQueue<int, SmallConstantTraits> q;
288
beforeenqueue_implicit_one289 void before()
290 {
291 }
292
threadenqueue_implicit_one293 void thread(unsigned int tid)
294 {
295 RelacyThreadExitNotifier::notify_relacy_thread_start();
296
297 q.enqueue(tid);
298
299 RelacyThreadExitNotifier::notify_relacy_thread_exit();
300 }
301
afterenqueue_implicit_one302 void after()
303 {
304 int tid0, tid1;
305 RL_ASSERT(q.try_dequeue(tid0));
306 RL_ASSERT(tid0 == 0 || tid0 == 1);
307 RL_ASSERT(q.try_dequeue(tid1));
308 RL_ASSERT(tid1 == 0 || tid1 == 1);
309 RL_ASSERT(tid0 != tid1);
310 RL_ASSERT(!q.try_dequeue(tid0));
311 }
312
invariantenqueue_implicit_one313 void invariant()
314 {
315 }
316 };
317
318 // Tests implicit producer at a simple level
319 struct implicit_simple : rl::test_suite<implicit_simple, 3>
320 {
321 ConcurrentQueue<int, SmallConstantTraits> q;
322 std::vector<bool> seen;
323
beforeimplicit_simple324 void before()
325 {
326 seen.resize(5, false);
327 }
328
threadimplicit_simple329 void thread(unsigned int tid)
330 {
331 RelacyThreadExitNotifier::notify_relacy_thread_start();
332
333 if (tid == 0) {
334 for (int i = 0; i != 5; ++i) {
335 q.enqueue(i);
336 }
337 }
338 else {
339 int item;
340 for (int i = 0; i != 3; ++i) {
341 if (q.try_dequeue(item)) {
342 RL_ASSERT(!seen[item]);
343 seen[item] = true;
344 }
345 }
346 }
347
348 RelacyThreadExitNotifier::notify_relacy_thread_exit();
349 }
350
afterimplicit_simple351 void after()
352 {
353 int item;
354 while (q.try_dequeue(item)) {
355 RL_ASSERT(!seen[item]);
356 seen[item] = true;
357 }
358 for (auto s : seen) {
359 RL_ASSERT(s);
360 }
361 }
362
invariantimplicit_simple363 void invariant()
364 {
365 }
366 };
367
368
369 // Tests multiple implicit producers being created (stresses the implicit producer hash map)
370 struct many_implicit_producers : rl::test_suite<many_implicit_producers, 6>
371 {
372 ConcurrentQueue<int, SmallConstantTraits> q;
373 std::vector<bool> seen;
374
beforemany_implicit_producers375 void before()
376 {
377 seen.resize(18, false);
378 }
379
threadmany_implicit_producers380 void thread(unsigned int tid)
381 {
382 RelacyThreadExitNotifier::notify_relacy_thread_start();
383
384 q.enqueue(tid * 3 + 0);
385 q.enqueue(tid * 3 + 1);
386 q.enqueue(tid * 3 + 2);
387
388 int item;
389 for (int i = 0; i != 2; ++i) {
390 if (q.try_dequeue(item)) {
391 RL_ASSERT(!seen[item]);
392 seen[item] = true;
393 }
394 }
395
396 RelacyThreadExitNotifier::notify_relacy_thread_exit();
397 }
398
aftermany_implicit_producers399 void after()
400 {
401 int item;
402 while (q.try_dequeue(item)) {
403 RL_ASSERT(!seen[item]);
404 seen[item] = true;
405 }
406 for (auto s : seen) {
407 RL_ASSERT(s);
408 }
409 }
410
invariantmany_implicit_producers411 void invariant()
412 {
413 }
414 };
415
416 // Tests multiple implicit producers being created (stresses the implicit producer hash map)
417 struct implicit_producer_reuse : rl::test_suite<implicit_producer_reuse, 9>
418 {
419 ConcurrentQueue<int, SmallConstantTraits> q;
420 std::vector<bool> seen;
421
beforeimplicit_producer_reuse422 void before()
423 {
424 seen.resize(9, false);
425 }
426
threadimplicit_producer_reuse427 void thread(unsigned int tid)
428 {
429 RelacyThreadExitNotifier::notify_relacy_thread_start();
430
431 q.enqueue(tid);
432
433 RelacyThreadExitNotifier::notify_relacy_thread_exit();
434 }
435
afterimplicit_producer_reuse436 void after()
437 {
438 int item;
439 while (q.try_dequeue(item)) {
440 RL_ASSERT(!seen[item]);
441 seen[item] = true;
442 }
443 for (auto s : seen) {
444 RL_ASSERT(s);
445 }
446 }
447
invariantimplicit_producer_reuse448 void invariant()
449 {
450 }
451 };
452
453 // Tests implicit producer block recycling
454 struct implicit_block_reuse : rl::test_suite<implicit_block_reuse, 4>
455 {
456 ConcurrentQueue<int, SmallConstantTraits> q;
457 std::vector<bool> seen;
458
beforeimplicit_block_reuse459 void before()
460 {
461 seen.resize(28, false);
462 }
463
threadimplicit_block_reuse464 void thread(unsigned int tid)
465 {
466 RelacyThreadExitNotifier::notify_relacy_thread_start();
467
468 for (int i = 0; i != 7; ++i) {
469 q.enqueue(tid * 7 + i);
470 }
471
472 int item;
473 ConsumerToken t(q);
474 for (int i = 0; i != 7; ++i) {
475 if (q.try_dequeue(t, item)) {
476 RL_ASSERT(!seen[item]);
477 seen[item] = true;
478 }
479 }
480
481 RelacyThreadExitNotifier::notify_relacy_thread_exit();
482 }
483
afterimplicit_block_reuse484 void after()
485 {
486 int item;
487 while (q.try_dequeue(item)) {
488 RL_ASSERT(!seen[item]);
489 seen[item] = true;
490 }
491 for (auto s : seen) {
492 RL_ASSERT(s);
493 }
494 }
495
invariantimplicit_block_reuse496 void invariant()
497 {
498 }
499 };
500
501 // Tests consumption from mixed producers
502 struct mixed : rl::test_suite<mixed, 4>
503 {
504 ConcurrentQueue<int, SmallConstantTraits> q;
505 std::vector<bool> seen;
506
beforemixed507 void before()
508 {
509 seen.resize(28, false);
510 }
511
threadmixed512 void thread(unsigned int tid)
513 {
514 RelacyThreadExitNotifier::notify_relacy_thread_start();
515
516 if (tid <= 1) {
517 for (int i = 0; i != 7; ++i) {
518 q.enqueue(tid * 7 + i);
519 }
520 }
521 else {
522 ProducerToken t(q);
523 for (int i = 0; i != 7; ++i) {
524 q.enqueue(t, tid * 7 + i);
525 }
526 }
527
528 int item;
529 if (tid & 1) {
530 for (int i = 0; i != 4; ++i) {
531 if (q.try_dequeue(item)) {
532 RL_ASSERT(!seen[item]);
533 seen[item] = true;
534 }
535 }
536 }
537 else {
538 ConsumerToken t(q);
539 for (int i = 0; i != 4; ++i) {
540 if (q.try_dequeue(t, item)) {
541 RL_ASSERT(!seen[item]);
542 seen[item] = true;
543 }
544 }
545 }
546
547 RelacyThreadExitNotifier::notify_relacy_thread_exit();
548 }
549
aftermixed550 void after()
551 {
552 int item;
553 while (q.try_dequeue(item)) {
554 RL_ASSERT(!seen[item]);
555 seen[item] = true;
556 }
557 for (auto s : seen) {
558 RL_ASSERT(s);
559 }
560 }
561
invariantmixed562 void invariant()
563 {
564 }
565 };
566
567 // Test leftovers are being properly destroyed
568 struct leftovers_destroyed_explicit : rl::test_suite<leftovers_destroyed_explicit, 3>
569 {
570 ConcurrentQueue<Foo, MediumConstantTraits>* q;
571 std::vector<bool> seen;
572
beforeleftovers_destroyed_explicit573 void before()
574 {
575 seen.resize(rl::rand(32), false);
576
577 q = new ConcurrentQueue<Foo, MediumConstantTraits>();
578 Foo::reset();
579 }
580
threadleftovers_destroyed_explicit581 void thread(unsigned int tid)
582 {
583 RelacyThreadExitNotifier::notify_relacy_thread_start();
584
585 if (tid == 0) {
586 ProducerToken t(*q);
587 for (int i = 0; i != (int)seen.size(); ++i) {
588 q->enqueue(t, Foo(i));
589 }
590 }
591 else {
592 Foo item;
593 ConsumerToken t(*q);
594 for (int i = rl::rand(17); i > 0; --i) {
595 if (q->try_dequeue(t, item)) {
596 RL_ASSERT(!seen[item.id]);
597 seen[item.id] = true;
598 }
599 }
600 }
601
602 RelacyThreadExitNotifier::notify_relacy_thread_exit();
603 }
604
afterleftovers_destroyed_explicit605 void after()
606 {
607 int seenCount = 0;
608 {
609 for (auto s : seen) {
610 if (s) {
611 ++seenCount;
612 }
613 }
614 }
615
616 RL_ASSERT(Foo::ctorCount() == seen.size() * 2 + 2);
617 RL_ASSERT(Foo::dtorCount() == seen.size() + seenCount + 2);
618 delete q;
619
620 RL_ASSERT(Foo::ctorCount() == seen.size() * 2 + 2);
621 RL_ASSERT(Foo::ctorCount() == Foo::dtorCount());
622 }
623
invariantleftovers_destroyed_explicit624 void invariant()
625 {
626 }
627 };
628
629 // implicit
630 struct leftovers_destroyed_implicit : rl::test_suite<leftovers_destroyed_implicit, 3>
631 {
632 ConcurrentQueue<Foo, MediumConstantTraits>* q;
633 std::vector<bool> seen;
634
beforeleftovers_destroyed_implicit635 void before()
636 {
637 seen.resize(rl::rand(32), false);
638
639 q = new ConcurrentQueue<Foo, MediumConstantTraits>();
640 Foo::reset();
641 }
642
threadleftovers_destroyed_implicit643 void thread(unsigned int tid)
644 {
645 RelacyThreadExitNotifier::notify_relacy_thread_start();
646
647 if (tid == 0) {
648 for (int i = 0; i != (int)seen.size(); ++i) {
649 q->enqueue(Foo(i));
650 }
651 }
652 else {
653 Foo item;
654 for (int i = rl::rand(17); i > 0; --i) {
655 if (q->try_dequeue(item)) {
656 RL_ASSERT(!seen[item.id]);
657 seen[item.id] = true;
658 }
659 }
660 }
661
662 RelacyThreadExitNotifier::notify_relacy_thread_exit();
663 }
664
afterleftovers_destroyed_implicit665 void after()
666 {
667 int seenCount = 0;
668 {
669 for (auto s : seen) {
670 if (s) {
671 ++seenCount;
672 }
673 }
674 }
675
676 RL_ASSERT(Foo::ctorCount() == seen.size() * 2 + 2);
677 RL_ASSERT(Foo::dtorCount() == seen.size() + seenCount + 2);
678 delete q;
679
680 RL_ASSERT(Foo::ctorCount() == seen.size() * 2 + 2);
681 RL_ASSERT(Foo::ctorCount() == Foo::dtorCount());
682 }
683
invariantleftovers_destroyed_implicit684 void invariant()
685 {
686 }
687 };
688
689
690 template<typename TTest>
simulate(int iterations)691 void simulate(int iterations)
692 {
693 // Note: There's no point using the full search params
694 // Even with the simple enqueue_explicit_one test, it
695 // would take a few millenia to complete(!)
696 //rl::test_params fullParams;
697 //fullParams.search_type = rl::sched_full;
698
699 rl::test_params randomParams;
700 randomParams.search_type = rl::sched_random;
701 randomParams.iteration_count = iterations;
702 rl::simulate<TTest>(randomParams);
703 }
704
main()705 int main()
706 {
707 simulate<enqueue_explicit_one>(1000000);
708 simulate<enqueue_explicit_many>(1000000);
709 simulate<dequeue_some_explicit>(1000000);
710 simulate<recycle_blocks_explicit>(1000000);
711 simulate<expand_block_index_explicit>(1000000);
712 simulate<enqueue_implicit_one>(1000000);
713 simulate<implicit_simple>(1000000);
714 simulate<many_implicit_producers>(500000);
715 simulate<implicit_producer_reuse>(1000000);
716 simulate<implicit_block_reuse>(1000000);
717 simulate<mixed>(1000000);
718 simulate<leftovers_destroyed_explicit>(1000000);
719 simulate<leftovers_destroyed_implicit>(1000000);
720
721 return 0;
722 }
723