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
27static SDL_sem *sem;
28int alive;
29
30typedef 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
38static void
39killed(int sig)
40{
41 alive = 0;
42}
43
44static int SDLCALL
45ThreadFuncRealWorld(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
63static void
64TestRealWorld(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
98static void
99TestWaitTimeout(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
126static void
127TestOverheadUncontended(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
154static int SDLCALL
155ThreadFuncOverheadContended(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
178static void
179TestOverheadContended(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
244int
245main(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 = 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