6af9d4bf62830033495e5dd781c25a96a42da040
[platform/upstream/SDL.git] / test / testatomic.c
1 /*
2   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
3
4   This software is provided 'as-is', without any express or implied
5   warranty.  In no event will the authors be held liable for any damages
6   arising from the use of this software.
7
8   Permission is granted to anyone to use this software for any purpose,
9   including commercial applications, and to alter it and redistribute it
10   freely.
11 */
12 #include <stdio.h>
13
14 #include "SDL.h"
15
16 /*
17   Absolutely basic tests just to see if we get the expected value
18   after calling each function.
19 */
20
21 static
22 char *
23 tf(SDL_bool tf)
24 {
25     static char *t = "TRUE";
26     static char *f = "FALSE";
27
28     if (tf)
29     {
30        return t;
31     }
32
33     return f;
34 }
35
36 static
37 void RunBasicTest()
38 {
39     int value;
40     SDL_SpinLock lock = 0;
41
42     SDL_atomic_t v;
43     SDL_bool tfret = SDL_FALSE;
44
45     SDL_Log("\nspin lock---------------------------------------\n\n");
46
47     SDL_AtomicLock(&lock);
48     SDL_Log("AtomicLock                   lock=%d\n", lock);
49     SDL_AtomicUnlock(&lock);
50     SDL_Log("AtomicUnlock                 lock=%d\n", lock);
51
52     SDL_Log("\natomic -----------------------------------------\n\n");
53
54     SDL_AtomicSet(&v, 0);
55     tfret = SDL_AtomicSet(&v, 10) == 0 ? SDL_TRUE : SDL_FALSE;
56     SDL_Log("AtomicSet(10)        tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
57     tfret = SDL_AtomicAdd(&v, 10) == 10 ? SDL_TRUE : SDL_FALSE;
58     SDL_Log("AtomicAdd(10)        tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
59
60     SDL_AtomicSet(&v, 0);
61     SDL_AtomicIncRef(&v);
62     tfret = (SDL_AtomicGet(&v) == 1) ? SDL_TRUE : SDL_FALSE;
63     SDL_Log("AtomicIncRef()       tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
64     SDL_AtomicIncRef(&v);
65     tfret = (SDL_AtomicGet(&v) == 2) ? SDL_TRUE : SDL_FALSE;
66     SDL_Log("AtomicIncRef()       tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
67     tfret = (SDL_AtomicDecRef(&v) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE;
68     SDL_Log("AtomicDecRef()       tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
69     tfret = (SDL_AtomicDecRef(&v) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE;
70     SDL_Log("AtomicDecRef()       tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
71
72     SDL_AtomicSet(&v, 10);
73     tfret = (SDL_AtomicCAS(&v, 0, 20) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE;
74     SDL_Log("AtomicCAS()          tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
75     value = SDL_AtomicGet(&v);
76     tfret = (SDL_AtomicCAS(&v, value, 20) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE;
77     SDL_Log("AtomicCAS()          tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
78 }
79
80 /**************************************************************************/
81 /* Atomic operation test
82  * Adapted with permission from code by Michael Davidsaver at:
83  *  http://bazaar.launchpad.net/~mdavidsaver/epics-base/atomic/revision/12105#src/libCom/test/epicsAtomicTest.c
84  * Original copyright 2010 Brookhaven Science Associates as operator of Brookhaven National Lab
85  * http://www.aps.anl.gov/epics/license/open.php
86  */
87
88 /* Tests semantics of atomic operations.  Also a stress test
89  * to see if they are really atomic.
90  *
91  * Several threads adding to the same variable.
92  * at the end the value is compared with the expected
93  * and with a non-atomic counter.
94  */
95
96 /* Number of concurrent incrementers */
97 #define NThreads 2
98 #define CountInc 100
99 #define VALBITS (sizeof(atomicValue)*8)
100
101 #define atomicValue int
102 #define CountTo ((atomicValue)((unsigned int)(1<<(VALBITS-1))-1))
103 #define NInter (CountTo/CountInc/NThreads)
104 #define Expect (CountTo-NInter*CountInc*NThreads)
105
106 enum {
107    CountTo_GreaterThanZero = CountTo > 0,
108 };
109 SDL_COMPILE_TIME_ASSERT(size, CountTo_GreaterThanZero); /* check for rollover */
110
111 static SDL_atomic_t good = { 42 };
112
113 static atomicValue bad = 42;
114
115 static SDL_atomic_t threadsRunning;
116
117 static SDL_sem *threadDone;
118
119 static
120 int SDLCALL adder(void* junk)
121 {
122     unsigned long N=NInter;
123     SDL_Log("Thread subtracting %d %lu times\n",CountInc,N);
124     while (N--) {
125         SDL_AtomicAdd(&good, -CountInc);
126         bad-=CountInc;
127     }
128     SDL_AtomicAdd(&threadsRunning, -1);
129     SDL_SemPost(threadDone);
130     return 0;
131 }
132
133 static
134 void runAdder(void)
135 {
136     Uint32 start, end;
137     int T=NThreads;
138
139     start = SDL_GetTicks();
140
141     threadDone = SDL_CreateSemaphore(0);
142
143     SDL_AtomicSet(&threadsRunning, NThreads);
144
145     while (T--)
146         SDL_CreateThread(adder, "Adder", NULL);
147
148     while (SDL_AtomicGet(&threadsRunning) > 0)
149         SDL_SemWait(threadDone);
150
151     SDL_DestroySemaphore(threadDone);
152
153     end = SDL_GetTicks();
154
155     SDL_Log("Finished in %f sec\n", (end - start) / 1000.f);
156 }
157
158 static
159 void RunEpicTest()
160 {
161     int b;
162     atomicValue v;
163
164     SDL_Log("\nepic test---------------------------------------\n\n");
165
166     SDL_Log("Size asserted to be >= 32-bit\n");
167     SDL_assert(sizeof(atomicValue)>=4);
168
169     SDL_Log("Check static initializer\n");
170     v=SDL_AtomicGet(&good);
171     SDL_assert(v==42);
172
173     SDL_assert(bad==42);
174
175     SDL_Log("Test negative values\n");
176     SDL_AtomicSet(&good, -5);
177     v=SDL_AtomicGet(&good);
178     SDL_assert(v==-5);
179
180     SDL_Log("Verify maximum value\n");
181     SDL_AtomicSet(&good, CountTo);
182     v=SDL_AtomicGet(&good);
183     SDL_assert(v==CountTo);
184
185     SDL_Log("Test compare and exchange\n");
186
187     b=SDL_AtomicCAS(&good, 500, 43);
188     SDL_assert(!b); /* no swap since CountTo!=500 */
189     v=SDL_AtomicGet(&good);
190     SDL_assert(v==CountTo); /* ensure no swap */
191
192     b=SDL_AtomicCAS(&good, CountTo, 44);
193     SDL_assert(!!b); /* will swap */
194     v=SDL_AtomicGet(&good);
195     SDL_assert(v==44);
196
197     SDL_Log("Test Add\n");
198
199     v=SDL_AtomicAdd(&good, 1);
200     SDL_assert(v==44);
201     v=SDL_AtomicGet(&good);
202     SDL_assert(v==45);
203
204     v=SDL_AtomicAdd(&good, 10);
205     SDL_assert(v==45);
206     v=SDL_AtomicGet(&good);
207     SDL_assert(v==55);
208
209     SDL_Log("Test Add (Negative values)\n");
210
211     v=SDL_AtomicAdd(&good, -20);
212     SDL_assert(v==55);
213     v=SDL_AtomicGet(&good);
214     SDL_assert(v==35);
215
216     v=SDL_AtomicAdd(&good, -50); /* crossing zero down */
217     SDL_assert(v==35);
218     v=SDL_AtomicGet(&good);
219     SDL_assert(v==-15);
220
221     v=SDL_AtomicAdd(&good, 30); /* crossing zero up */
222     SDL_assert(v==-15);
223     v=SDL_AtomicGet(&good);
224     SDL_assert(v==15);
225
226     SDL_Log("Reset before count down test\n");
227     SDL_AtomicSet(&good, CountTo);
228     v=SDL_AtomicGet(&good);
229     SDL_assert(v==CountTo);
230
231     bad=CountTo;
232     SDL_assert(bad==CountTo);
233
234     SDL_Log("Counting down from %d, Expect %d remaining\n",CountTo,Expect);
235     runAdder();
236
237     v=SDL_AtomicGet(&good);
238     SDL_Log("Atomic %d Non-Atomic %d\n",v,bad);
239     SDL_assert(v==Expect);
240     SDL_assert(bad!=Expect);
241 }
242
243 /* End atomic operation test */
244 /**************************************************************************/
245
246 /**************************************************************************/
247 /* Lock-free FIFO test */
248
249 /* This is useful to test the impact of another thread locking the queue
250    entirely for heavy-weight manipulation.
251  */
252 #define TEST_SPINLOCK_FIFO
253
254 #define NUM_READERS 4
255 #define NUM_WRITERS 4
256 #define EVENTS_PER_WRITER   1000000
257
258 /* The number of entries must be a power of 2 */
259 #define MAX_ENTRIES 256
260 #define WRAP_MASK   (MAX_ENTRIES-1)
261
262 typedef struct
263 {
264     SDL_atomic_t sequence;
265     SDL_Event event;
266 } SDL_EventQueueEntry;
267
268 typedef struct
269 {
270     SDL_EventQueueEntry entries[MAX_ENTRIES];
271
272     char cache_pad1[SDL_CACHELINE_SIZE-((sizeof(SDL_EventQueueEntry)*MAX_ENTRIES)%SDL_CACHELINE_SIZE)];
273
274     SDL_atomic_t enqueue_pos;
275
276     char cache_pad2[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)];
277
278     SDL_atomic_t dequeue_pos;
279
280     char cache_pad3[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)];
281
282 #ifdef TEST_SPINLOCK_FIFO
283     SDL_SpinLock lock;
284     SDL_atomic_t rwcount;
285     SDL_atomic_t watcher;
286
287     char cache_pad4[SDL_CACHELINE_SIZE-sizeof(SDL_SpinLock)-2*sizeof(SDL_atomic_t)];
288 #endif
289
290     SDL_atomic_t active;
291
292     /* Only needed for the mutex test */
293     SDL_mutex *mutex;
294
295 } SDL_EventQueue;
296
297 static void InitEventQueue(SDL_EventQueue *queue)
298 {
299     int i;
300
301     for (i = 0; i < MAX_ENTRIES; ++i) {
302         SDL_AtomicSet(&queue->entries[i].sequence, i);
303     }
304     SDL_AtomicSet(&queue->enqueue_pos, 0);
305     SDL_AtomicSet(&queue->dequeue_pos, 0);
306 #ifdef TEST_SPINLOCK_FIFO
307     queue->lock = 0;
308     SDL_AtomicSet(&queue->rwcount, 0);
309     SDL_AtomicSet(&queue->watcher, 0);
310 #endif
311     SDL_AtomicSet(&queue->active, 1);
312 }
313
314 static SDL_bool EnqueueEvent_LockFree(SDL_EventQueue *queue, const SDL_Event *event)
315 {
316     SDL_EventQueueEntry *entry;
317     unsigned queue_pos;
318     unsigned entry_seq;
319     int delta;
320     SDL_bool status;
321
322 #ifdef TEST_SPINLOCK_FIFO
323     /* This is a gate so an external thread can lock the queue */
324     SDL_AtomicLock(&queue->lock);
325     SDL_assert(SDL_AtomicGet(&queue->watcher) == 0);
326     SDL_AtomicIncRef(&queue->rwcount);
327     SDL_AtomicUnlock(&queue->lock);
328 #endif
329
330     queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos);
331     for ( ; ; ) {
332         entry = &queue->entries[queue_pos & WRAP_MASK];
333         entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence);
334
335         delta = (int)(entry_seq - queue_pos);
336         if (delta == 0) {
337             /* The entry and the queue position match, try to increment the queue position */
338             if (SDL_AtomicCAS(&queue->enqueue_pos, (int)queue_pos, (int)(queue_pos+1))) {
339                 /* We own the object, fill it! */
340                 entry->event = *event;
341                 SDL_AtomicSet(&entry->sequence, (int)(queue_pos + 1));
342                 status = SDL_TRUE;
343                 break;
344             }
345         } else if (delta < 0) {
346             /* We ran into an old queue entry, which means it still needs to be dequeued */
347             status = SDL_FALSE;
348             break;
349         } else {
350             /* We ran into a new queue entry, get the new queue position */
351             queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos);
352         }
353     }
354
355 #ifdef TEST_SPINLOCK_FIFO
356     SDL_AtomicDecRef(&queue->rwcount);
357 #endif
358     return status;
359 }
360
361 static SDL_bool DequeueEvent_LockFree(SDL_EventQueue *queue, SDL_Event *event)
362 {
363     SDL_EventQueueEntry *entry;
364     unsigned queue_pos;
365     unsigned entry_seq;
366     int delta;
367     SDL_bool status;
368
369 #ifdef TEST_SPINLOCK_FIFO
370     /* This is a gate so an external thread can lock the queue */
371     SDL_AtomicLock(&queue->lock);
372     SDL_assert(SDL_AtomicGet(&queue->watcher) == 0);
373     SDL_AtomicIncRef(&queue->rwcount);
374     SDL_AtomicUnlock(&queue->lock);
375 #endif
376
377     queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos);
378     for ( ; ; ) {
379         entry = &queue->entries[queue_pos & WRAP_MASK];
380         entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence);
381
382         delta = (int)(entry_seq - (queue_pos + 1));
383         if (delta == 0) {
384             /* The entry and the queue position match, try to increment the queue position */
385             if (SDL_AtomicCAS(&queue->dequeue_pos, (int)queue_pos, (int)(queue_pos+1))) {
386                 /* We own the object, fill it! */
387                 *event = entry->event;
388                 SDL_AtomicSet(&entry->sequence, (int)(queue_pos+MAX_ENTRIES));
389                 status = SDL_TRUE;
390                 break;
391             }
392         } else if (delta < 0) {
393             /* We ran into an old queue entry, which means we've hit empty */
394             status = SDL_FALSE;
395             break;
396         } else {
397             /* We ran into a new queue entry, get the new queue position */
398             queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos);
399         }
400     }
401
402 #ifdef TEST_SPINLOCK_FIFO
403     SDL_AtomicDecRef(&queue->rwcount);
404 #endif
405     return status;
406 }
407
408 static SDL_bool EnqueueEvent_Mutex(SDL_EventQueue *queue, const SDL_Event *event)
409 {
410     SDL_EventQueueEntry *entry;
411     unsigned queue_pos;
412     unsigned entry_seq;
413     int delta;
414     SDL_bool status = SDL_FALSE;
415
416     SDL_LockMutex(queue->mutex);
417
418     queue_pos = (unsigned)queue->enqueue_pos.value;
419     entry = &queue->entries[queue_pos & WRAP_MASK];
420     entry_seq = (unsigned)entry->sequence.value;
421
422     delta = (int)(entry_seq - queue_pos);
423     if (delta == 0) {
424         ++queue->enqueue_pos.value;
425
426         /* We own the object, fill it! */
427         entry->event = *event;
428         entry->sequence.value = (int)(queue_pos + 1);
429         status = SDL_TRUE;
430     } else if (delta < 0) {
431         /* We ran into an old queue entry, which means it still needs to be dequeued */
432     } else {
433         SDL_Log("ERROR: mutex failed!\n");
434     }
435
436     SDL_UnlockMutex(queue->mutex);
437
438     return status;
439 }
440
441 static SDL_bool DequeueEvent_Mutex(SDL_EventQueue *queue, SDL_Event *event)
442 {
443     SDL_EventQueueEntry *entry;
444     unsigned queue_pos;
445     unsigned entry_seq;
446     int delta;
447     SDL_bool status = SDL_FALSE;
448
449     SDL_LockMutex(queue->mutex);
450
451     queue_pos = (unsigned)queue->dequeue_pos.value;
452     entry = &queue->entries[queue_pos & WRAP_MASK];
453     entry_seq = (unsigned)entry->sequence.value;
454
455     delta = (int)(entry_seq - (queue_pos + 1));
456     if (delta == 0) {
457         ++queue->dequeue_pos.value;
458
459         /* We own the object, fill it! */
460         *event = entry->event;
461         entry->sequence.value = (int)(queue_pos + MAX_ENTRIES);
462         status = SDL_TRUE;
463     } else if (delta < 0) {
464         /* We ran into an old queue entry, which means we've hit empty */
465     } else {
466         SDL_Log("ERROR: mutex failed!\n");
467     }
468
469     SDL_UnlockMutex(queue->mutex);
470
471     return status;
472 }
473
474 static SDL_sem *writersDone;
475 static SDL_sem *readersDone;
476 static SDL_atomic_t writersRunning;
477 static SDL_atomic_t readersRunning;
478
479 typedef struct
480 {
481     SDL_EventQueue *queue;
482     int index;
483     char padding1[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int))%SDL_CACHELINE_SIZE];
484     int waits;
485     SDL_bool lock_free;
486     char padding2[SDL_CACHELINE_SIZE-sizeof(int)-sizeof(SDL_bool)];
487 } WriterData;
488
489 typedef struct
490 {
491     SDL_EventQueue *queue;
492     int counters[NUM_WRITERS];
493     int waits;
494     SDL_bool lock_free;
495     char padding[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int)*NUM_WRITERS+sizeof(int)+sizeof(SDL_bool))%SDL_CACHELINE_SIZE];
496 } ReaderData;
497
498 static int SDLCALL FIFO_Writer(void* _data)
499 {
500     WriterData *data = (WriterData *)_data;
501     SDL_EventQueue *queue = data->queue;
502     int i;
503     SDL_Event event;
504
505     event.type = SDL_USEREVENT;
506     event.user.windowID = 0;
507     event.user.code = 0;
508     event.user.data1 = data;
509     event.user.data2 = NULL;
510
511     if (data->lock_free) {
512         for (i = 0; i < EVENTS_PER_WRITER; ++i) {
513             event.user.code = i;
514             while (!EnqueueEvent_LockFree(queue, &event)) {
515                 ++data->waits;
516                 SDL_Delay(0);
517             }
518         }
519     } else {
520         for (i = 0; i < EVENTS_PER_WRITER; ++i) {
521             event.user.code = i;
522             while (!EnqueueEvent_Mutex(queue, &event)) {
523                 ++data->waits;
524                 SDL_Delay(0);
525             }
526         }
527     }
528     SDL_AtomicAdd(&writersRunning, -1);
529     SDL_SemPost(writersDone);
530     return 0;
531 }
532
533 static int SDLCALL FIFO_Reader(void* _data)
534 {
535     ReaderData *data = (ReaderData *)_data;
536     SDL_EventQueue *queue = data->queue;
537     SDL_Event event;
538
539     if (data->lock_free) {
540         for ( ; ; ) {
541             if (DequeueEvent_LockFree(queue, &event)) {
542                 WriterData *writer = (WriterData*)event.user.data1;
543                 ++data->counters[writer->index];
544             } else if (SDL_AtomicGet(&queue->active)) {
545                 ++data->waits;
546                 SDL_Delay(0);
547             } else {
548                 /* We drained the queue, we're done! */
549                 break;
550             }
551         }
552     } else {
553         for ( ; ; ) {
554             if (DequeueEvent_Mutex(queue, &event)) {
555                 WriterData *writer = (WriterData*)event.user.data1;
556                 ++data->counters[writer->index];
557             } else if (SDL_AtomicGet(&queue->active)) {
558                 ++data->waits;
559                 SDL_Delay(0);
560             } else {
561                 /* We drained the queue, we're done! */
562                 break;
563             }
564         }
565     }
566     SDL_AtomicAdd(&readersRunning, -1);
567     SDL_SemPost(readersDone);
568     return 0;
569 }
570
571 #ifdef TEST_SPINLOCK_FIFO
572 /* This thread periodically locks the queue for no particular reason */
573 static int SDLCALL FIFO_Watcher(void* _data)
574 {
575     SDL_EventQueue *queue = (SDL_EventQueue *)_data;
576
577     while (SDL_AtomicGet(&queue->active)) {
578         SDL_AtomicLock(&queue->lock);
579         SDL_AtomicIncRef(&queue->watcher);
580         while (SDL_AtomicGet(&queue->rwcount) > 0) {
581             SDL_Delay(0);
582         }
583         /* Do queue manipulation here... */
584         SDL_AtomicDecRef(&queue->watcher);
585         SDL_AtomicUnlock(&queue->lock);
586
587         /* Wait a bit... */
588         SDL_Delay(1);
589     }
590     return 0;
591 }
592 #endif /* TEST_SPINLOCK_FIFO */
593
594 static void RunFIFOTest(SDL_bool lock_free)
595 {
596     SDL_EventQueue queue;
597     WriterData writerData[NUM_WRITERS];
598     ReaderData readerData[NUM_READERS];
599     Uint32 start, end;
600     int i, j;
601     int grand_total;
602     char textBuffer[1024];
603     size_t len;
604
605     SDL_Log("\nFIFO test---------------------------------------\n\n");
606     SDL_Log("Mode: %s\n", lock_free ? "LockFree" : "Mutex");
607
608     readersDone = SDL_CreateSemaphore(0);
609     writersDone = SDL_CreateSemaphore(0);
610
611     SDL_memset(&queue, 0xff, sizeof(queue));
612
613     InitEventQueue(&queue);
614     if (!lock_free) {
615         queue.mutex = SDL_CreateMutex();
616     }
617
618     start = SDL_GetTicks();
619
620 #ifdef TEST_SPINLOCK_FIFO
621     /* Start a monitoring thread */
622     if (lock_free) {
623         SDL_CreateThread(FIFO_Watcher, "FIFOWatcher", &queue);
624     }
625 #endif
626
627     /* Start the readers first */
628     SDL_Log("Starting %d readers\n", NUM_READERS);
629     SDL_zero(readerData);
630     SDL_AtomicSet(&readersRunning, NUM_READERS);
631     for (i = 0; i < NUM_READERS; ++i) {
632         char name[64];
633         SDL_snprintf(name, sizeof (name), "FIFOReader%d", i);
634         readerData[i].queue = &queue;
635         readerData[i].lock_free = lock_free;
636         SDL_CreateThread(FIFO_Reader, name, &readerData[i]);
637     }
638
639     /* Start up the writers */
640     SDL_Log("Starting %d writers\n", NUM_WRITERS);
641     SDL_zero(writerData);
642     SDL_AtomicSet(&writersRunning, NUM_WRITERS);
643     for (i = 0; i < NUM_WRITERS; ++i) {
644         char name[64];
645         SDL_snprintf(name, sizeof (name), "FIFOWriter%d", i);
646         writerData[i].queue = &queue;
647         writerData[i].index = i;
648         writerData[i].lock_free = lock_free;
649         SDL_CreateThread(FIFO_Writer, name, &writerData[i]);
650     }
651
652     /* Wait for the writers */
653     while (SDL_AtomicGet(&writersRunning) > 0) {
654         SDL_SemWait(writersDone);
655     }
656
657     /* Shut down the queue so readers exit */
658     SDL_AtomicSet(&queue.active, 0);
659
660     /* Wait for the readers */
661     while (SDL_AtomicGet(&readersRunning) > 0) {
662         SDL_SemWait(readersDone);
663     }
664
665     end = SDL_GetTicks();
666
667     SDL_DestroySemaphore(readersDone);
668     SDL_DestroySemaphore(writersDone);
669
670     if (!lock_free) {
671         SDL_DestroyMutex(queue.mutex);
672     }
673
674     SDL_Log("Finished in %f sec\n", (end - start) / 1000.f);
675
676     SDL_Log("\n");
677     for (i = 0; i < NUM_WRITERS; ++i) {
678         SDL_Log("Writer %d wrote %d events, had %d waits\n", i, EVENTS_PER_WRITER, writerData[i].waits);
679     }
680     SDL_Log("Writers wrote %d total events\n", NUM_WRITERS*EVENTS_PER_WRITER);
681
682     /* Print a breakdown of which readers read messages from which writer */
683     SDL_Log("\n");
684     grand_total = 0;
685     for (i = 0; i < NUM_READERS; ++i) {
686         int total = 0;
687         for (j = 0; j < NUM_WRITERS; ++j) {
688             total += readerData[i].counters[j];
689         }
690         grand_total += total;
691         SDL_Log("Reader %d read %d events, had %d waits\n", i, total, readerData[i].waits);
692         SDL_snprintf(textBuffer, sizeof(textBuffer), "  { ");
693         for (j = 0; j < NUM_WRITERS; ++j) {
694             if (j > 0) {
695                 len = SDL_strlen(textBuffer);
696                 SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", ");
697             }
698             len = SDL_strlen(textBuffer);
699             SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d", readerData[i].counters[j]);
700         }
701         len = SDL_strlen(textBuffer);
702         SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n");
703         SDL_Log("%s", textBuffer);
704     }
705     SDL_Log("Readers read %d total events\n", grand_total);
706 }
707
708 /* End FIFO test */
709 /**************************************************************************/
710
711 int
712 main(int argc, char *argv[])
713 {
714     /* Enable standard application logging */
715     SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
716
717     RunBasicTest();
718     RunEpicTest();
719 /* This test is really slow, so don't run it by default */
720 #if 0
721     RunFIFOTest(SDL_FALSE);
722 #endif
723     RunFIFOTest(SDL_TRUE);
724     return 0;
725 }
726
727 /* vi: set ts=4 sw=4 expandtab: */