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