1 /*
2   Copyright (C) 1997-2021 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 
13 /* Simple test of the SDL semaphore code */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <signal.h>
18 
19 #include "SDL.h"
20 
21 #define NUM_THREADS 10
22 /* This value should be smaller than the maximum count of the */
23 /* semaphore implementation: */
24 #define NUM_OVERHEAD_OPS 10000
25 #define NUM_OVERHEAD_OPS_MULT 10
26 
27 static SDL_sem *sem;
28 int alive;
29 
30 typedef struct Thread_State {
31     SDL_Thread * thread;
32     int number;
33     SDL_bool flag;
34     int loop_count;
35     int content_count;
36 } Thread_State;
37 
38 static void
killed(int sig)39 killed(int sig)
40 {
41     alive = 0;
42 }
43 
44 static int SDLCALL
ThreadFuncRealWorld(void * data)45 ThreadFuncRealWorld(void *data)
46 {
47     Thread_State *state = (Thread_State *) data;
48     while (alive) {
49         SDL_SemWait(sem);
50         SDL_Log("Thread number %d has got the semaphore (value = %d)!\n",
51                 state->number, SDL_SemValue(sem));
52         SDL_Delay(200);
53         SDL_SemPost(sem);
54         SDL_Log("Thread number %d has released the semaphore (value = %d)!\n",
55                 state->number, SDL_SemValue(sem));
56         ++state->loop_count;
57         SDL_Delay(1);           /* For the scheduler */
58     }
59     SDL_Log("Thread number %d exiting.\n", state->number);
60     return 0;
61 }
62 
63 static void
TestRealWorld(int init_sem)64 TestRealWorld(int init_sem) {
65     Thread_State thread_states[NUM_THREADS] = { {0} };
66     int i;
67     int loop_count;
68 
69     sem = SDL_CreateSemaphore(init_sem);
70 
71     SDL_Log("Running %d threads, semaphore value = %d\n", NUM_THREADS,
72            init_sem);
73     alive = 1;
74     /* Create all the threads */
75     for (i = 0; i < NUM_THREADS; ++i) {
76         char name[64];
77         SDL_snprintf(name, sizeof (name), "Thread%u", (unsigned int) i);
78         thread_states[i].number = i;
79         thread_states[i].thread = SDL_CreateThread(ThreadFuncRealWorld, name, (void *) &thread_states[i]);
80     }
81 
82     /* Wait 10 seconds */
83     SDL_Delay(10 * 1000);
84 
85     /* Wait for all threads to finish */
86     SDL_Log("Waiting for threads to finish\n");
87     alive = 0;
88     loop_count = 0;
89     for (i = 0; i < NUM_THREADS; ++i) {
90         SDL_WaitThread(thread_states[i].thread, NULL);
91         loop_count += thread_states[i].loop_count;
92     }
93     SDL_Log("Finished waiting for threads, ran %d loops in total\n\n", loop_count);
94 
95     SDL_DestroySemaphore(sem);
96 }
97 
98 static void
TestWaitTimeout(void)99 TestWaitTimeout(void)
100 {
101     Uint32 start_ticks;
102     Uint32 end_ticks;
103     Uint32 duration;
104     int retval;
105 
106     sem = SDL_CreateSemaphore(0);
107     SDL_Log("Waiting 2 seconds on semaphore\n");
108 
109     start_ticks = SDL_GetTicks();
110     retval = SDL_SemWaitTimeout(sem, 2000);
111     end_ticks = SDL_GetTicks();
112 
113     duration = end_ticks - start_ticks;
114 
115     /* Accept a little offset in the effective wait */
116     SDL_assert(duration > 1900 && duration < 2050);
117     SDL_Log("Wait took %d milliseconds\n\n", duration);
118 
119     /* Check to make sure the return value indicates timed out */
120     if (retval != SDL_MUTEX_TIMEDOUT)
121         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_SemWaitTimeout returned: %d; expected: %d\n\n", retval, SDL_MUTEX_TIMEDOUT);
122 
123     SDL_DestroySemaphore(sem);
124 }
125 
126 static void
TestOverheadUncontended(void)127 TestOverheadUncontended(void)
128 {
129     Uint32 start_ticks;
130     Uint32 end_ticks;
131     Uint32 duration;
132     int i, j;
133 
134     sem = SDL_CreateSemaphore(0);
135     SDL_Log("Doing %d uncontended Post/Wait operations on semaphore\n", NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT);
136 
137     start_ticks = SDL_GetTicks();
138     for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++){
139         for (j = 0; j < NUM_OVERHEAD_OPS; j++) {
140             SDL_SemPost(sem);
141         }
142         for (j = 0; j < NUM_OVERHEAD_OPS; j++) {
143             SDL_SemWait(sem);
144         }
145     }
146     end_ticks = SDL_GetTicks();
147 
148     duration = end_ticks - start_ticks;
149     SDL_Log("Took %d milliseconds\n\n", duration);
150 
151     SDL_DestroySemaphore(sem);
152 }
153 
154 static int SDLCALL
ThreadFuncOverheadContended(void * data)155 ThreadFuncOverheadContended(void *data)
156 {
157     Thread_State *state = (Thread_State *) data;
158 
159     if (state->flag) {
160         while(alive) {
161             if (SDL_SemTryWait(sem) == SDL_MUTEX_TIMEDOUT) {
162                 ++state->content_count;
163             }
164             ++state->loop_count;
165         }
166     } else {
167         while(alive) {
168             /* Timeout needed to allow check on alive flag */
169             if (SDL_SemWaitTimeout(sem, 50) == SDL_MUTEX_TIMEDOUT) {
170                 ++state->content_count;
171             }
172             ++state->loop_count;
173         }
174     }
175     return 0;
176 }
177 
178 static void
TestOverheadContended(SDL_bool try_wait)179 TestOverheadContended(SDL_bool try_wait)
180 {
181     Uint32 start_ticks;
182     Uint32 end_ticks;
183     Uint32 duration;
184     Thread_State thread_states[NUM_THREADS] = { {0} };
185     char textBuffer[1024];
186     int loop_count;
187     int content_count;
188     int i, j;
189     size_t len;
190 
191     sem = SDL_CreateSemaphore(0);
192     SDL_Log("Doing %d contended %s operations on semaphore using %d threads\n",
193             NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT, try_wait ? "Post/TryWait" : "Post/WaitTimeout", NUM_THREADS);
194     alive = 1;
195     /* Create multiple threads to starve the semaphore and cause contention */
196     for (i = 0; i < NUM_THREADS; ++i) {
197         char name[64];
198         SDL_snprintf(name, sizeof (name), "Thread%u", (unsigned int) i);
199         thread_states[i].flag = try_wait;
200         thread_states[i].thread = SDL_CreateThread(ThreadFuncOverheadContended, name, (void *) &thread_states[i]);
201     }
202 
203     start_ticks = SDL_GetTicks();
204     for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++) {
205         for (j = 0; j < NUM_OVERHEAD_OPS; j++) {
206             SDL_SemPost(sem);
207         }
208         /* Make sure threads consumed everything */
209         while (SDL_SemValue(sem)) { }
210     }
211     end_ticks = SDL_GetTicks();
212 
213     alive = 0;
214     loop_count = 0;
215     content_count = 0;
216     for (i = 0; i < NUM_THREADS; ++i) {
217         SDL_WaitThread(thread_states[i].thread, NULL);
218         loop_count += thread_states[i].loop_count;
219         content_count += thread_states[i].content_count;
220     }
221     SDL_assert_release((loop_count - content_count) == NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT);
222 
223     duration = end_ticks - start_ticks;
224     SDL_Log("Took %d milliseconds, threads %s %d out of %d times in total (%.2f%%)\n",
225             duration, try_wait ? "where contended" : "timed out", content_count,
226             loop_count, ((float)content_count * 100) / loop_count);
227     /* Print how many semaphores where consumed per thread */
228     SDL_snprintf(textBuffer, sizeof(textBuffer), "{ ");
229     for (i = 0; i < NUM_THREADS; ++i) {
230         if (i > 0) {
231             len = SDL_strlen(textBuffer);
232             SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", ");
233         }
234         len = SDL_strlen(textBuffer);
235         SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d", thread_states[i].loop_count - thread_states[i].content_count);
236     }
237     len = SDL_strlen(textBuffer);
238     SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n");
239     SDL_Log("%s\n", textBuffer);
240 
241     SDL_DestroySemaphore(sem);
242 }
243 
244 int
main(int argc,char ** argv)245 main(int argc, char **argv)
246 {
247     int init_sem;
248 
249     /* Enable standard application logging */
250     SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
251 
252     if (argc < 2) {
253         SDL_Log("Usage: %s init_value\n", argv[0]);
254         return (1);
255     }
256 
257     /* Load the SDL library */
258     if (SDL_Init(0) < 0) {
259         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
260         return (1);
261     }
262     signal(SIGTERM, killed);
263     signal(SIGINT, killed);
264 
265     init_sem = SDL_atoi(argv[1]);
266     if (init_sem > 0) {
267         TestRealWorld(init_sem);
268     }
269 
270     TestWaitTimeout();
271 
272     TestOverheadUncontended();
273 
274     TestOverheadContended(SDL_FALSE);
275 
276     TestOverheadContended(SDL_TRUE);
277 
278     SDL_Quit();
279     return (0);
280 }
281