| /* |
| Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely. |
| */ |
| |
| /* Test the thread and mutex locking functions |
| Also exercises the system's signal/thread interaction |
| */ |
| |
| #include <signal.h> |
| #include <stdlib.h> /* for atexit() */ |
| |
| #include <SDL3/SDL.h> |
| #include <SDL3/SDL_main.h> |
| #include <SDL3/SDL_test.h> |
| |
| static SDL_Mutex *mutex = NULL; |
| static SDL_ThreadID mainthread; |
| static SDL_AtomicInt doterminate; |
| static int nb_threads = 6; |
| static SDL_Thread **threads; |
| static int worktime = 1000; |
| static SDLTest_CommonState *state; |
| |
| /** |
| * SDL_Quit() shouldn't be used with atexit() directly because |
| * calling conventions may differ... |
| */ |
| static void |
| SDL_Quit_Wrapper(void) |
| { |
| SDL_Quit(); |
| SDLTest_CommonDestroyState(state); |
| } |
| |
| static void printid(void) |
| { |
| SDL_Log("Thread %" SDL_PRIu64 ": exiting\n", SDL_GetCurrentThreadID()); |
| } |
| |
| static void terminate(int sig) |
| { |
| (void)signal(SIGINT, terminate); |
| SDL_AtomicSet(&doterminate, 1); |
| } |
| |
| static void closemutex(int sig) |
| { |
| SDL_ThreadID id = SDL_GetCurrentThreadID(); |
| int i; |
| SDL_Log("Thread %" SDL_PRIu64 ": Cleaning up...\n", id == mainthread ? 0 : id); |
| SDL_AtomicSet(&doterminate, 1); |
| if (threads) { |
| for (i = 0; i < nb_threads; ++i) { |
| SDL_WaitThread(threads[i], NULL); |
| } |
| SDL_free(threads); |
| threads = NULL; |
| } |
| SDL_DestroyMutex(mutex); |
| /* Let 'main()' return normally */ |
| if (sig != 0) { |
| exit(sig); |
| } |
| } |
| |
| static int SDLCALL |
| Run(void *data) |
| { |
| SDL_ThreadID current_thread = SDL_GetCurrentThreadID(); |
| |
| if (current_thread == mainthread) { |
| (void)signal(SIGTERM, closemutex); |
| } |
| SDL_Log("Thread %" SDL_PRIu64 ": starting up", current_thread); |
| while (!SDL_AtomicGet(&doterminate)) { |
| SDL_Log("Thread %" SDL_PRIu64 ": ready to work\n", current_thread); |
| SDL_LockMutex(mutex); |
| SDL_Log("Thread %" SDL_PRIu64 ": start work!\n", current_thread); |
| SDL_Delay(1 * worktime); |
| SDL_Log("Thread %" SDL_PRIu64 ": work done!\n", current_thread); |
| SDL_UnlockMutex(mutex); |
| |
| /* If this sleep isn't done, then threads may starve */ |
| SDL_Delay(10); |
| } |
| if (current_thread == mainthread && SDL_AtomicGet(&doterminate)) { |
| SDL_Log("Thread %" SDL_PRIu64 ": raising SIGTERM\n", current_thread); |
| (void)raise(SIGTERM); |
| } |
| SDL_Log("Thread %" SDL_PRIu64 ": exiting!\n", current_thread); |
| return 0; |
| } |
| |
| #ifndef _WIN32 |
| static Uint32 hit_timeout(Uint32 interval, void *param) { |
| SDL_Log("Hit timeout! Sending SIGINT!"); |
| (void)raise(SIGINT); |
| return 0; |
| } |
| #endif |
| |
| int main(int argc, char *argv[]) |
| { |
| int i; |
| #ifndef _WIN32 |
| int timeout = 0; |
| #endif |
| |
| /* Initialize test framework */ |
| state = SDLTest_CommonCreateState(argv, 0); |
| if (!state) { |
| return 1; |
| } |
| |
| /* Enable standard application logging */ |
| SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); |
| /* Parse commandline */ |
| for (i = 1; i < argc;) { |
| int consumed; |
| |
| consumed = SDLTest_CommonArg(state, i); |
| if (!consumed) { |
| if (SDL_strcmp(argv[i], "--nbthreads") == 0) { |
| if (argv[i + 1]) { |
| char *endptr; |
| nb_threads = SDL_strtol(argv[i + 1], &endptr, 0); |
| if (endptr != argv[i + 1] && *endptr == '\0' && nb_threads > 0) { |
| consumed = 2; |
| } |
| } |
| } else if (SDL_strcmp(argv[i], "--worktime") == 0) { |
| if (argv[i + 1]) { |
| char *endptr; |
| nb_threads = SDL_strtol(argv[i + 1], &endptr, 0); |
| if (endptr != argv[i + 1] && *endptr == '\0' && nb_threads > 0) { |
| consumed = 2; |
| } |
| } |
| #ifndef _WIN32 |
| } else if (SDL_strcmp(argv[i], "--timeout") == 0) { |
| if (argv[i + 1]) { |
| char *endptr; |
| timeout = SDL_strtol(argv[i + 1], &endptr, 0); |
| if (endptr != argv[i + 1] && *endptr == '\0' && timeout > 0) { |
| consumed = 2; |
| } |
| } |
| #endif |
| } |
| } |
| if (consumed <= 0) { |
| static const char *options[] = { |
| "[--nbthreads NB]", |
| "[--worktime ms]", |
| #ifndef _WIN32 |
| "[--timeout ms]", |
| #endif |
| NULL, |
| }; |
| SDLTest_CommonLogUsage(state, argv[0], options); |
| exit(1); |
| } |
| |
| i += consumed; |
| } |
| |
| threads = SDL_malloc(nb_threads * sizeof(SDL_Thread*)); |
| |
| /* Load the SDL library */ |
| if (SDL_Init(0) < 0) { |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); |
| exit(1); |
| } |
| (void)atexit(SDL_Quit_Wrapper); |
| |
| SDL_AtomicSet(&doterminate, 0); |
| |
| mutex = SDL_CreateMutex(); |
| if (!mutex) { |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create mutex: %s\n", SDL_GetError()); |
| exit(1); |
| } |
| |
| mainthread = SDL_GetCurrentThreadID(); |
| SDL_Log("Main thread: %" SDL_PRIu64 "\n", mainthread); |
| (void)atexit(printid); |
| for (i = 0; i < nb_threads; ++i) { |
| char name[64]; |
| (void)SDL_snprintf(name, sizeof(name), "Worker%d", i); |
| threads[i] = SDL_CreateThread(Run, name, NULL); |
| if (threads[i] == NULL) { |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create thread!\n"); |
| } |
| } |
| |
| #ifndef _WIN32 |
| if (timeout) { |
| SDL_AddTimer(timeout, hit_timeout, NULL); |
| } |
| #endif |
| |
| (void)signal(SIGINT, terminate); |
| Run(NULL); |
| |
| return 0; /* Never reached */ |
| } |