From e474047ff8bf076c86699cc9bf33e9320f2eb05a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 24 Apr 2023 01:07:59 -0400 Subject: [PATCH] rwlock: Added SDL_rwlock API for shared locks. --- CMakeLists.txt | 10 +- VisualC-GDK/SDL/SDL.vcxproj.filters | 6 + VisualC-WinRT/SDL-UWP.vcxproj | 18 ++ VisualC-WinRT/SDL-UWP.vcxproj.filters | 3 + VisualC/SDL/SDL.vcxproj | 3 + VisualC/SDL/SDL.vcxproj.filters | 3 + Xcode/SDL/SDL.xcodeproj/project.pbxproj | 20 ++ cmake/sdlchecks.cmake | 1 + include/SDL3/SDL_mutex.h | 226 ++++++++++++++- include/build_config/SDL_build_config.h.cmake | 1 + .../build_config/SDL_build_config_windows.h | 1 + .../build_config/SDL_build_config_wingdk.h | 1 + include/build_config/SDL_build_config_winrt.h | 1 + include/build_config/SDL_build_config_xbox.h | 1 + src/dynapi/SDL_dynapi.sym | 7 + src/dynapi/SDL_dynapi_overrides.h | 7 + src/dynapi/SDL_dynapi_procs.h | 7 + src/thread/generic/SDL_sysmutex.c | 1 + src/thread/generic/SDL_sysrwlock.c | 185 +++++++++++++ src/thread/generic/SDL_sysrwlock_c.h | 38 +++ src/thread/ngage/SDL_sysmutex.cpp | 1 + src/thread/ngage/SDL_syssem.cpp | 1 + src/thread/pthread/SDL_sysmutex.c | 16 +- src/thread/pthread/SDL_sysmutex_c.h | 9 + src/thread/pthread/SDL_sysrwlock.c | 127 +++++++++ src/thread/stdcpp/SDL_sysmutex.cpp | 1 + src/thread/stdcpp/SDL_sysmutex_c.h | 1 + src/thread/stdcpp/SDL_sysrwlock.cpp | 130 +++++++++ src/thread/windows/SDL_sysrwlock_srw.c | 258 ++++++++++++++++++ test/CMakeLists.txt | 1 + test/testrwlock.c | 179 ++++++++++++ 31 files changed, 1244 insertions(+), 20 deletions(-) create mode 100644 src/thread/generic/SDL_sysrwlock.c create mode 100644 src/thread/generic/SDL_sysrwlock_c.h create mode 100644 src/thread/pthread/SDL_sysrwlock.c create mode 100644 src/thread/stdcpp/SDL_sysrwlock.cpp create mode 100644 src/thread/windows/SDL_sysrwlock_srw.c create mode 100644 test/testrwlock.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 407f7e46a..003b7bce6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2008,11 +2008,14 @@ elseif(WINDOWS) if(SDL_THREADS) set(SDL_THREAD_GENERIC_COND_SUFFIX 1) + set(SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1) set(SDL_THREAD_WINDOWS 1) list(APPEND SOURCE_FILES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c + ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syscond_cv.c ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysmutex.c + ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysrwlock_srw.c ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syssem.c ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systhread.c ${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systls.c) @@ -2597,6 +2600,7 @@ elseif(VITA) ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c ${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syscond.c + ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c) set(HAVE_SDL_THREADS TRUE) endif() @@ -2732,7 +2736,7 @@ elseif(PSP) endif() if(SDL_THREADS) set(SDL_THREAD_PSP 1) - file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c) + file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c) list(APPEND SOURCE_FILES ${PSP_THREAD_SOURCES}) set(HAVE_SDL_THREADS TRUE) endif() @@ -2791,7 +2795,7 @@ elseif(PS2) endif() if(SDL_THREADS) set(SDL_THREAD_PS2 1) - file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c) + file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c) list(APPEND SOURCE_FILES ${PS2_THREAD_SOURCES}) set(HAVE_SDL_THREADS TRUE) endif() @@ -2852,7 +2856,7 @@ elseif(N3DS) if(SDL_THREADS) set(SDL_THREAD_N3DS 1) file(GLOB N3DS_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c) - list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c) + list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c) set(HAVE_SDL_THREADS TRUE) endif() diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 9e7e0b72f..71ab29e44 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -1225,6 +1225,9 @@ thread\windows + + thread\windows + thread\windows @@ -1237,6 +1240,9 @@ thread\generic + + thread\generic + stdlib diff --git a/VisualC-WinRT/SDL-UWP.vcxproj b/VisualC-WinRT/SDL-UWP.vcxproj index 8fde0dadf..3a503634f 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj +++ b/VisualC-WinRT/SDL-UWP.vcxproj @@ -444,6 +444,24 @@ true true + + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + $(IntDir)$(TargetName)_cpp.pch + true + true + true + true + true + true + true + true + $(IntDir)$(TargetName)_cpp.pch $(IntDir)$(TargetName)_cpp.pch diff --git a/VisualC-WinRT/SDL-UWP.vcxproj.filters b/VisualC-WinRT/SDL-UWP.vcxproj.filters index e51068b41..26bb708b7 100644 --- a/VisualC-WinRT/SDL-UWP.vcxproj.filters +++ b/VisualC-WinRT/SDL-UWP.vcxproj.filters @@ -678,6 +678,9 @@ Source Files + + Source Files + Source Files diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 55c4c72cd..a111aaeb3 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -402,6 +402,7 @@ + @@ -599,9 +600,11 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 3a2919fcc..cb03064bf 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1213,6 +1213,9 @@ thread\windows + + thread\windows + thread\windows diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index f587aa6d7..862ee3394 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -83,6 +83,15 @@ 566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; }; 566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; }; 566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; }; + 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; + 56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; }; 56C5237F1D8F4985001F2F30 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; }; 56C523811D8F498C001F2F30 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; }; 75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; }; @@ -3482,6 +3491,7 @@ 566E26CC246274CB00718109 /* SDL_syslocale.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDL_syslocale.m; path = locale/macos/SDL_syslocale.m; sourceTree = ""; }; 566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = ""; }; 566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = ""; }; + 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = ""; }; 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = ""; }; 75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = ""; }; 9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = ""; }; @@ -4691,6 +4701,7 @@ A7D8A78123E2513E00DCD162 /* pthread */ = { isa = PBXGroup; children = ( + 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */, A7D8A78523E2513E00DCD162 /* SDL_syscond.c */, A7D8A78823E2513E00DCD162 /* SDL_sysmutex_c.h */, A7D8A78723E2513E00DCD162 /* SDL_sysmutex.c */, @@ -7328,6 +7339,7 @@ A75FCE7023E25AB700529352 /* SDL_hidapi_xboxone.c in Sources */, A75FCE7123E25AB700529352 /* SDL_blit_auto.c in Sources */, A75FCE7323E25AB700529352 /* SDL_keyboard.c in Sources */, + 56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A75FCE7523E25AB700529352 /* SDL_rect.c in Sources */, A75FCE7623E25AB700529352 /* SDL_cocoaopengles.m in Sources */, A75FCE7723E25AB700529352 /* SDL_qsort.c in Sources */, @@ -7523,6 +7535,7 @@ A75FD02923E25AC700529352 /* SDL_hidapi_xboxone.c in Sources */, A75FD02A23E25AC700529352 /* SDL_blit_auto.c in Sources */, A75FD02C23E25AC700529352 /* SDL_keyboard.c in Sources */, + 56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A75FD02E23E25AC700529352 /* SDL_rect.c in Sources */, A75FD02F23E25AC700529352 /* SDL_cocoaopengles.m in Sources */, A75FD03023E25AC700529352 /* SDL_qsort.c in Sources */, @@ -7718,6 +7731,7 @@ A769B20423E259AE00872273 /* SDL_hidapi_switch.c in Sources */, F3984CD525BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */, A769B20523E259AE00872273 /* SDL_strtokr.c in Sources */, + 56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, 5605720B2473687A00B46B66 /* SDL_syslocale.m in Sources */, F3820718284F3609004DD584 /* controller_type.c in Sources */, A769B20623E259AE00872273 /* SDL_clipboardevents.c in Sources */, @@ -7913,6 +7927,7 @@ A7D8BB6A23E2514500DCD162 /* SDL_keyboard.c in Sources */, A7D8ACE823E2514100DCD162 /* SDL_rect.c in Sources */, A7D8AE9B23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */, + 56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A7D8B96923E2514400DCD162 /* SDL_qsort.c in Sources */, A7D8B55223E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, A7D8B96323E2514400DCD162 /* SDL_strtokr.c in Sources */, @@ -8108,6 +8123,7 @@ A7D8BB6B23E2514500DCD162 /* SDL_keyboard.c in Sources */, A7D8ACE923E2514100DCD162 /* SDL_rect.c in Sources */, A7D8AE9C23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */, + 56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A7D8B96A23E2514400DCD162 /* SDL_qsort.c in Sources */, A7D8B55323E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, A7D8B96423E2514400DCD162 /* SDL_strtokr.c in Sources */, @@ -8303,6 +8319,7 @@ A7D8B55523E2514300DCD162 /* SDL_hidapi_switch.c in Sources */, F3984CD425BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */, A7D8B96623E2514400DCD162 /* SDL_strtokr.c in Sources */, + 56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, 560572092473687900B46B66 /* SDL_syslocale.m in Sources */, F3820717284F3609004DD584 /* controller_type.c in Sources */, A7D8BB7923E2514500DCD162 /* SDL_clipboardevents.c in Sources */, @@ -8469,6 +8486,7 @@ A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */, A7D8BB0923E2514500DCD162 /* k_tan.c in Sources */, A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */, + 56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, 566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */, A7D8AFC023E2514200DCD162 /* SDL_egl.c in Sources */, A7D8AC3323E2514100DCD162 /* SDL_RLEaccel.c in Sources */, @@ -8663,6 +8681,7 @@ A7D8BBF223E2574800DCD162 /* SDL_uikitevents.m in Sources */, A7D8BBB923E2560500DCD162 /* SDL_steamcontroller.c in Sources */, A7D8B8AB23E2514400DCD162 /* SDL_diskaudio.c in Sources */, + 56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A7D8AFC323E2514200DCD162 /* SDL_egl.c in Sources */, A7D8AC3623E2514100DCD162 /* SDL_RLEaccel.c in Sources */, A7D8BBB423E2514500DCD162 /* SDL_assert.c in Sources */, @@ -8857,6 +8876,7 @@ A7D8ADEB23E2514100DCD162 /* SDL_blit_0.c in Sources */, A7D8BB0E23E2514500DCD162 /* k_tan.c in Sources */, A7D8B8AD23E2514400DCD162 /* SDL_diskaudio.c in Sources */, + 56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */, A7D8AFC523E2514200DCD162 /* SDL_egl.c in Sources */, A7D8AC3823E2514100DCD162 /* SDL_RLEaccel.c in Sources */, A7D8BBB623E2514500DCD162 /* SDL_assert.c in Sources */, diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index bab638658..0aec8607a 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -860,6 +860,7 @@ macro(CheckPTHREAD) ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systhread.c ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysmutex.c # Can be faked, if necessary ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_syscond.c # Can be faked, if necessary + ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysrwlock.c # Can be faked, if necessary ${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systls.c ) if(HAVE_PTHREADS_SEM) diff --git a/include/SDL3/SDL_mutex.h b/include/SDL3/SDL_mutex.h index b242dd74e..8f6131020 100644 --- a/include/SDL3/SDL_mutex.h +++ b/include/SDL3/SDL_mutex.h @@ -203,11 +203,9 @@ extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex) SDL_TRY_ACQUIRE( * unlock it the same number of times before it is actually made available for * other threads in the system (this is known as a "recursive mutex"). * - * It is an error to unlock a mutex that has not been locked by the current + * It is illegal to unlock a mutex that has not been locked by the current * thread, and doing so results in undefined behavior. * - * It is also an error to unlock a mutex that isn't locked at all. - * * \param mutex the mutex to unlock. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. @@ -240,6 +238,228 @@ extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex); /* @} *//* Mutex functions */ +/** + * \name Read/write lock functions + */ +/* @{ */ + +/* The SDL read/write lock structure, defined in SDL_sysrwlock.c */ +struct SDL_rwlock; +typedef struct SDL_rwlock SDL_rwlock; + +/* + * Synchronization functions which can time out return this value + * if they time out. + */ +#define SDL_RWLOCK_TIMEDOUT SDL_MUTEX_TIMEDOUT + + +/** + * Create a new read/write lock. + * + * A read/write lock is useful for situations where you have multiple + * threads trying to access a resource that is rarely updated. All threads + * requesting a read-only lock will be allowed to run in parallel; if a + * thread requests a write lock, it will be provided exclusive access. + * This makes it safe for multiple threads to use a resource at the same + * time if they promise not to change it, and when it has to be changed, + * the rwlock will serve as a gateway to make sure those changes can be + * made safely. + * + * In the right situation, a rwlock can be more efficient than a mutex, + * which only lets a single thread proceed at a time, even if it won't be + * modifying the data. + * + * All newly-created read/write locks begin in the _unlocked_ state. + * + * Calls to SDL_LockRWLockForReading() and SDL_LockRWLockForWriting will + * not return while the rwlock is locked _for writing_ by another thread. + * See SDL_TryLockRWLockForReading() and SDL_TryLockRWLockForWriting() to + * attempt to lock without blocking. + * + * SDL read/write locks are only recursive for read-only locks! They + * are not guaranteed to be fair, or provide access in a FIFO manner! They + * are not guaranteed to favor writers. You may not lock a rwlock for + * both read-only and write access at the same time from the same thread + * (so you can't promote your read-only lock to a write lock without + * unlocking first). + * + * \returns the initialized and unlocked read/write lock or NULL on + * failure; call SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_DestroyRWLock + * \sa SDL_LockRWLockForReading + * \sa SDL_TryLockRWLockForReading + * \sa SDL_LockRWLockForWriting + * \sa SDL_TryLockRWLockForWriting + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC SDL_rwlock *SDLCALL SDL_CreateRWLock(void); + +/** + * Lock the read/write lock for _read only_ operations. + * + * This will block until the rwlock is available, which is to say it is + * not locked for writing by any other thread. Of all threads waiting to + * lock the rwlock, all may do so at the same time as long as they are + * requesting read-only access; if a thread wants to lock for writing, + * only one may do so at a time, and no other threads, read-only or not, + * may hold the lock at the same time. + * + * It is legal for the owning thread to lock an already-locked rwlock + * for reading. It must unlock it the same number of times before it is + * actually made available for other threads in the system (this is known + * as a "recursive rwlock"). + * + * Note that locking for writing is not recursive (this is only available + * to read-only locks). + * + * It is illegal to request a read-only lock from a thread that already + * holds the write lock. Doing so results in undefined behavior. Unlock the + * write lock before requesting a read-only lock. (But, of course, if you + * have the write lock, you don't need further locks to read in any case.) + * + * \param rwlock the read/write lock to lock + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC int SDLCALL SDL_LockRWLockForReading(SDL_rwlock * rwlock) SDL_ACQUIRE_SHARED(rwlock); + +/** + * Lock the read/write lock for _write_ operations. + * + * This will block until the rwlock is available, which is to say it is + * not locked for reading or writing by any other thread. Only one thread + * may hold the lock when it requests write access; all other threads, + * whether they also want to write or only want read-only access, must wait + * until the writer thread has released the lock. + * + * It is illegal for the owning thread to lock an already-locked rwlock + * for writing (read-only may be locked recursively, writing can not). Doing + * so results in undefined behavior. + * + * It is illegal to request a write lock from a thread that already holds + * a read-only lock. Doing so results in undefined behavior. Unlock the + * read-only lock before requesting a write lock. + * + * \param rwlock the read/write lock to lock + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC int SDLCALL SDL_LockRWLockForWriting(SDL_rwlock * rwlock) SDL_ACQUIRE(rwlock); + +/** + * Try to lock a read/write lock _for reading_ without blocking. + * + * This works just like SDL_LockRWLockForReading(), but if the rwlock is not + * available, then this function returns `SDL_RWLOCK_TIMEDOUT` immediately. + * + * This technique is useful if you need access to a resource but + * don't want to wait for it, and will return to it to try again later. + * + * Trying to lock for read-only access can succeed if other threads are + * holding read-only locks, as this won't prevent access. + * + * \param rwlock the rwlock to try to lock + * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateRWLock + * \sa SDL_DestroyRWLock + * \sa SDL_TryLockRWLockForReading + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC int SDLCALL SDL_TryLockRWLockForReading(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE_SHARED(0, rwlock); + +/** + * Try to lock a read/write lock _for writing_ without blocking. + * + * This works just like SDL_LockRWLockForWriting(), but if the rwlock is not available, + * this function returns `SDL_RWLOCK_TIMEDOUT` immediately. + * + * This technique is useful if you need exclusive access to a resource but + * don't want to wait for it, and will return to it to try again later. + * + * It is illegal for the owning thread to lock an already-locked rwlock + * for writing (read-only may be locked recursively, writing can not). Doing + * so results in undefined behavior. + * + * It is illegal to request a write lock from a thread that already holds + * a read-only lock. Doing so results in undefined behavior. Unlock the + * read-only lock before requesting a write lock. + * + * \param rwlock the rwlock to try to lock + * \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for + * more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateRWLock + * \sa SDL_DestroyRWLock + * \sa SDL_TryLockRWLockForWriting + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC int SDLCALL SDL_TryLockRWLockForWriting(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE(0, rwlock); + +/** + * Unlock the read/write lock. + * + * Use this function to unlock the rwlock, whether it was locked for read-only + * or write operations. + * + * It is legal for the owning thread to lock an already-locked read-only lock. + * It must unlock it the same number of times before it is actually made + * available for other threads in the system (this is known as a "recursive + * rwlock"). + * + * It is illegal to unlock a rwlock that has not been locked by the current + * thread, and doing so results in undefined behavior. + * + * \param rwlock the rwlock to unlock. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_UnlockRWLock(SDL_rwlock * rwlock) SDL_RELEASE_SHARED(rwlock); + +/** + * Destroy a read/write lock created with SDL_CreateRWLock(). + * + * This function must be called on any read/write lock that is no longer needed. + * Failure to destroy a rwlock will result in a system memory or resource leak. While + * it is safe to destroy a rwlock that is _unlocked_, it is not safe to attempt + * to destroy a locked rwlock, and may result in undefined behavior depending + * on the platform. + * + * \param rwlock the rwlock to destroy + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateRWLock + * \sa SDL_LockRWLockForReading + * \sa SDL_LockRWLockForWriting + * \sa SDL_TryLockRWLockForReading + * \sa SDL_TryLockRWLockForWriting + * \sa SDL_UnlockRWLock + */ +extern DECLSPEC void SDLCALL SDL_DestroyRWLock(SDL_rwlock * rwlock); + +/* @} *//* Read/write lock functions */ + + /** * \name Semaphore functions */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 29fdae642..13005525c 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -348,6 +348,7 @@ /* Enable various threading systems */ #cmakedefine SDL_THREAD_GENERIC_COND_SUFFIX @SDL_THREAD_GENERIC_COND_SUFFIX@ +#cmakedefine SDL_THREAD_GENERIC_RWLOCK_SUFFIX @SDL_THREAD_GENERIC_RWLOCK_SUFFIX@ #cmakedefine SDL_THREAD_PTHREAD @SDL_THREAD_PTHREAD@ #cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX@ #cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP@ diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index 18cf96e91..8a29bab17 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -256,6 +256,7 @@ typedef unsigned int uintptr_t; /* Enable various threading systems */ #define SDL_THREAD_GENERIC_COND_SUFFIX 1 +#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 /* Enable various timer systems */ diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h index 512213b7e..54b0359c2 100644 --- a/include/build_config/SDL_build_config_wingdk.h +++ b/include/build_config/SDL_build_config_wingdk.h @@ -196,6 +196,7 @@ /* Enable various threading systems */ #define SDL_THREAD_GENERIC_COND_SUFFIX 1 +#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 /* Enable various timer systems */ diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h index bd628d199..8623b6e9b 100644 --- a/include/build_config/SDL_build_config_winrt.h +++ b/include/build_config/SDL_build_config_winrt.h @@ -184,6 +184,7 @@ /* Enable various threading systems */ #if (NTDDI_VERSION >= NTDDI_WINBLUE) #define SDL_THREAD_GENERIC_COND_SUFFIX 1 +#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 #else /* WinRT on Windows 8.0 and Windows Phone 8.0 don't support CreateThread() */ diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h index 0059ca7d8..4e441895f 100644 --- a/include/build_config/SDL_build_config_xbox.h +++ b/include/build_config/SDL_build_config_xbox.h @@ -196,6 +196,7 @@ /* Enable various threading systems */ #define SDL_THREAD_GENERIC_COND_SUFFIX 1 +#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1 #define SDL_THREAD_WINDOWS 1 /* Enable various timer systems */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 457afa5c9..2a1dd322d 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -844,6 +844,13 @@ SDL3_0.0.0 { SDL_CreateWindowWithPosition; SDL_GetAudioStreamFormat; SDL_SetAudioStreamFormat; + SDL_CreateRWLock; + SDL_LockRWLockForReading; + SDL_LockRWLockForWriting; + SDL_TryLockRWLockForReading; + SDL_TryLockRWLockForWriting; + SDL_UnlockRWLock; + SDL_DestroyRWLock; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index f62b70981..d9095bec7 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -870,3 +870,10 @@ #define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL #define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL +#define SDL_CreateRWLock SDL_CreateRWLock_REAL +#define SDL_LockRWLockForReading SDL_LockRWLockForReading_REAL +#define SDL_LockRWLockForWriting SDL_LockRWLockForWriting_REAL +#define SDL_TryLockRWLockForReading SDL_TryLockRWLockForReading_REAL +#define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL +#define SDL_UnlockRWLock SDL_UnlockRWLock_REAL +#define SDL_DestroyRWLock SDL_DestroyRWLock_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 856d8abd3..bd97581e1 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -915,3 +915,10 @@ SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(SDL_rwlock*,SDL_CreateRWLock,(void),(),return) +SDL_DYNAPI_PROC(int,SDL_LockRWLockForReading,(SDL_rwlock *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_LockRWLockForWriting,(SDL_rwlock *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_rwlock *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_rwlock *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_rwlock *a),(a),return) +SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_rwlock *a),(a),) diff --git a/src/thread/generic/SDL_sysmutex.c b/src/thread/generic/SDL_sysmutex.c index 5c2c245cd..a5c5ec962 100644 --- a/src/thread/generic/SDL_sysmutex.c +++ b/src/thread/generic/SDL_sysmutex.c @@ -159,3 +159,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe return 0; #endif /* SDL_THREADS_DISABLED */ } + diff --git a/src/thread/generic/SDL_sysrwlock.c b/src/thread/generic/SDL_sysrwlock.c new file mode 100644 index 000000000..01aa97652 --- /dev/null +++ b/src/thread/generic/SDL_sysrwlock.c @@ -0,0 +1,185 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +/* An implementation of rwlocks using mutexes, condition variables, and atomics. */ + +#include "SDL_systhread_c.h" + +#include "../generic/SDL_sysrwlock_c.h" + +/* If two implementations are to be compiled into SDL (the active one + * will be chosen at runtime), the function names need to be + * suffixed + */ +/* !!! FIXME: this is quite a tapdance with macros and the build system, maybe we can simplify how we do this. --ryan. */ +#ifndef SDL_THREAD_GENERIC_RWLOCK_SUFFIX +#define SDL_CreateRWLock_generic SDL_CreateRWLock +#define SDL_DestroyRWLock_generic SDL_DestroyRWLock +#define SDL_LockRWLockForReading_generic SDL_LockRWLockForReading +#define SDL_LockRWLockForWriting_generic SDL_LockRWLockForWriting +#define SDL_TryLockRWLockForReading_generic SDL_TryLockRWLockForReading +#define SDL_TryLockRWLockForWriting_generic SDL_TryLockRWLockForWriting +#define SDL_UnlockRWLock_generic SDL_UnlockRWLock +#endif + +struct SDL_rwlock +{ + SDL_mutex *lock; + SDL_cond *condition; + SDL_threadID writer_thread; + SDL_AtomicInt reader_count; + SDL_AtomicInt writer_count; +}; + +SDL_rwlock *SDL_CreateRWLock_generic(void) +{ + SDL_rwlock *rwlock = (SDL_rwlock *) SDL_malloc(sizeof (*rwlock)); + + if (!rwlock) { + SDL_OutOfMemory(); + return NULL; + } + + rwlock->lock = SDL_CreateMutex(); + if (!rwlock->lock) { + SDL_free(rwlock); + return NULL; + } + + rwlock->condition = SDL_CreateCond(); + if (!rwlock->condition) { + SDL_DestroyMutex(rwlock->lock); + SDL_free(rwlock); + return NULL; + } + + SDL_AtomicSet(&rwlock->reader_count, 0); + SDL_AtomicSet(&rwlock->writer_count, 0); + + return rwlock; +} + +void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock) +{ + if (rwlock) { + SDL_DestroyMutex(rwlock->lock); + SDL_DestroyCond(rwlock->condition); + SDL_free(rwlock); + } +} + +int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } else if (SDL_LockMutex(rwlock->lock) == -1) { + return -1; + } + + SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */ + + SDL_AtomicAdd(&rwlock->reader_count, 1); + SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */ + return 0; +} + +int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } else if (SDL_LockMutex(rwlock->lock) == -1) { + return -1; + } + + while (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* while something is holding the shared lock, keep waiting. */ + SDL_CondWait(rwlock->condition, rwlock->lock); /* release the lock and wait for readers holding the shared lock to release it, regrab the lock. */ + } + + /* we hold the lock! */ + SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */ + + return 0; +} + +int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock) +{ + int rc; + + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } + + rc = SDL_TryLockMutex(rwlock->lock); + if (rc != 0) { + /* !!! FIXME: there is a small window where a reader has to lock the mutex, and if we hit that, we will return SDL_RWLOCK_TIMEDOUT even though we could have shared the lock. */ + return rc; + } + + SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */ + + SDL_AtomicAdd(&rwlock->reader_count, 1); + SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */ + return 0; +} + +int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock) +{ + int rc; + + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } else if ((rc = SDL_TryLockMutex(rwlock->lock)) != 0) { + return rc; + } + + if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* a reader is using the shared lock, treat it as unavailable. */ + SDL_UnlockMutex(rwlock->lock); + return SDL_RWLOCK_TIMEDOUT; + } + + /* we hold the lock! */ + SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */ + + return 0; +} + +int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } + + SDL_LockMutex(rwlock->lock); /* recursive lock for writers, readers grab lock to make sure things are sane. */ + + if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* we're a reader */ + SDL_AtomicAdd(&rwlock->reader_count, -1); + SDL_CondBroadcast(rwlock->condition); /* alert any pending writers to attempt to try to grab the lock again. */ + } else if (SDL_AtomicGet(&rwlock->writer_count) > 0) { /* we're a writer */ + SDL_AtomicAdd(&rwlock->writer_count, -1); + SDL_UnlockMutex(rwlock->lock); /* recursive unlock. */ + } + + SDL_UnlockMutex(rwlock->lock); + + return 0; +} + diff --git a/src/thread/generic/SDL_sysrwlock_c.h b/src/thread/generic/SDL_sysrwlock_c.h new file mode 100644 index 000000000..847442123 --- /dev/null +++ b/src/thread/generic/SDL_sysrwlock_c.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_sysrwlock_c_h_ +#define SDL_sysrwlock_c_h_ + +#ifdef SDL_THREAD_GENERIC_RWLOCK_SUFFIX + +SDL_rwlock *SDL_CreateRWLock_generic(void); +void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock); +int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock); +int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock); +int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock); +int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock); +int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock); + +#endif /* SDL_THREAD_GENERIC_RWLOCK_SUFFIX */ + +#endif /* SDL_sysrwlock_c_h_ */ diff --git a/src/thread/ngage/SDL_sysmutex.cpp b/src/thread/ngage/SDL_sysmutex.cpp index 541cc41ea..a8bd25bf4 100644 --- a/src/thread/ngage/SDL_sysmutex.cpp +++ b/src/thread/ngage/SDL_sysmutex.cpp @@ -109,3 +109,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe return 0; } + diff --git a/src/thread/ngage/SDL_syssem.cpp b/src/thread/ngage/SDL_syssem.cpp index 30213ad80..01c352238 100644 --- a/src/thread/ngage/SDL_syssem.cpp +++ b/src/thread/ngage/SDL_syssem.cpp @@ -24,6 +24,7 @@ #include +/* !!! FIXME: Should this be SDL_MUTEX_TIMEDOUT? */ #define SDL_MUTEX_TIMEOUT -2 struct SDL_semaphore diff --git a/src/thread/pthread/SDL_sysmutex.c b/src/thread/pthread/SDL_sysmutex.c index 04cb89bca..4bf9dfa1e 100644 --- a/src/thread/pthread/SDL_sysmutex.c +++ b/src/thread/pthread/SDL_sysmutex.c @@ -23,20 +23,7 @@ #include #include - -#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \ - defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP)) -#define FAKE_RECURSIVE_MUTEX -#endif - -struct SDL_mutex -{ - pthread_mutex_t id; -#ifdef FAKE_RECURSIVE_MUTEX - int recursive; - pthread_t owner; -#endif -}; +#include "SDL_sysmutex_c.h" SDL_mutex * SDL_CreateMutex(void) @@ -186,3 +173,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe return 0; } + diff --git a/src/thread/pthread/SDL_sysmutex_c.h b/src/thread/pthread/SDL_sysmutex_c.h index c2b39caba..7ea1e9570 100644 --- a/src/thread/pthread/SDL_sysmutex_c.h +++ b/src/thread/pthread/SDL_sysmutex_c.h @@ -23,9 +23,18 @@ #ifndef SDL_mutex_c_h_ #define SDL_mutex_c_h_ +#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \ + defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP)) +#define FAKE_RECURSIVE_MUTEX +#endif + struct SDL_mutex { pthread_mutex_t id; +#ifdef FAKE_RECURSIVE_MUTEX + int recursive; + pthread_t owner; +#endif }; #endif /* SDL_mutex_c_h_ */ diff --git a/src/thread/pthread/SDL_sysrwlock.c b/src/thread/pthread/SDL_sysrwlock.c new file mode 100644 index 000000000..950dd4713 --- /dev/null +++ b/src/thread/pthread/SDL_sysrwlock.c @@ -0,0 +1,127 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include + +struct SDL_rwlock +{ + pthread_rwlock_t id; +}; + + +SDL_rwlock * +SDL_CreateRWLock(void) +{ + SDL_rwlock *rwlock; + + /* Allocate the structure */ + rwlock = (SDL_rwlock *)SDL_calloc(1, sizeof(*rwlock)); + if (rwlock) { + if (pthread_rwlock_init(&rwlock->id, NULL) != 0) { + SDL_SetError("pthread_rwlock_init() failed"); + SDL_free(rwlock); + rwlock = NULL; + } + } else { + SDL_OutOfMemory(); + } + return rwlock; +} + +void SDL_DestroyRWLock(SDL_rwlock *rwlock) +{ + if (rwlock) { + pthread_rwlock_destroy(&rwlock->id); + SDL_free(rwlock); + } +} + +int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } else if (pthread_rwlock_rdlock(&rwlock->id) != 0) { + return SDL_SetError("pthread_rwlock_rdlock() failed"); + } + return 0; +} + +int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } else if (pthread_rwlock_wrlock(&rwlock->id) != 0) { + return SDL_SetError("pthread_rwlock_wrlock() failed"); + } + return 0; +} + +int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock) +{ + int retval = 0; + + if (rwlock == NULL) { + retval = SDL_InvalidParamError("rwlock"); + } else { + const int result = pthread_rwlock_tryrdlock(&rwlock->id); + if (result != 0) { + if (result == EBUSY) { + retval = SDL_RWLOCK_TIMEDOUT; + } else { + retval = SDL_SetError("pthread_rwlock_tryrdlock() failed"); + } + } + } + return retval; +} + +int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock) +{ + int retval = 0; + + if (rwlock == NULL) { + retval = SDL_InvalidParamError("rwlock"); + } else { + const int result = pthread_rwlock_trywrlock(&rwlock->id); + if (result != 0) { + if (result == EBUSY) { + retval = SDL_RWLOCK_TIMEDOUT; + } else { + retval = SDL_SetError("pthread_rwlock_tryrdlock() failed"); + } + } + } + + return retval; +} + +int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } else if (pthread_rwlock_unlock(&rwlock->id) != 0) { + return SDL_SetError("pthread_rwlock_unlock() failed"); + } + return 0; +} + diff --git a/src/thread/stdcpp/SDL_sysmutex.cpp b/src/thread/stdcpp/SDL_sysmutex.cpp index 28923c5da..16a96510b 100644 --- a/src/thread/stdcpp/SDL_sysmutex.cpp +++ b/src/thread/stdcpp/SDL_sysmutex.cpp @@ -97,3 +97,4 @@ SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't mutex->cpp_mutex.unlock(); return 0; } + diff --git a/src/thread/stdcpp/SDL_sysmutex_c.h b/src/thread/stdcpp/SDL_sysmutex_c.h index d824001be..95c6502dd 100644 --- a/src/thread/stdcpp/SDL_sysmutex_c.h +++ b/src/thread/stdcpp/SDL_sysmutex_c.h @@ -26,3 +26,4 @@ struct SDL_mutex { std::recursive_mutex cpp_mutex; }; + diff --git a/src/thread/stdcpp/SDL_sysrwlock.cpp b/src/thread/stdcpp/SDL_sysrwlock.cpp new file mode 100644 index 000000000..870c8eedf --- /dev/null +++ b/src/thread/stdcpp/SDL_sysrwlock.cpp @@ -0,0 +1,130 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include +#include +#include + +struct SDL_rwlock +{ + std::shared_mutex cpp_mutex; + SDL_threadID write_owner; +}; + +/* Create a rwlock */ +extern "C" SDL_rwlock *SDL_CreateRWLock(void) +{ + /* Allocate and initialize the rwlock */ + try { + SDL_rwlock *rwlock = new SDL_rwlock; + return rwlock; + } catch (std::system_error &ex) { + SDL_SetError("unable to create a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + return NULL; + } catch (std::bad_alloc &) { + SDL_OutOfMemory(); + return NULL; + } +} + +/* Free the rwlock */ +extern "C" void SDL_DestroyRWLock(SDL_rwlock *rwlock) +{ + if (rwlock != NULL) { + delete rwlock; + } +} + +/* Lock the rwlock */ +extern "C" int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } + + try { + rwlock->cpp_mutex.lock_shared(); + return 0; + } catch (std::system_error &ex) { + return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + } +} + +/* Lock the rwlock for writing */ +extern "C" int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } + + try { + rwlock->cpp_mutex.lock(); + rwlock->write_owner = SDL_ThreadID(); + return 0; + } catch (std::system_error &ex) { + return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what()); + } +} + +/* TryLock the rwlock for reading */ +int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock) +{ + int retval = 0; + + if (!rwlock) { + retval = SDL_InvalidParamError("rwlock"); + } else if (rwlock->cpp_mutex.try_lock_shared() == false) { + retval = SDL_RWLOCK_TIMEDOUT; + } + return retval; +} + +/* TryLock the rwlock for writing */ +int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock) +{ + int retval = 0; + + if (!rwlock) { + retval = SDL_InvalidParamError("rwlock"); + } else if (rwlock->cpp_mutex.try_lock() == false) { + retval = SDL_RWLOCK_TIMEDOUT; + } else { + rwlock->write_owner = SDL_ThreadID(); + } + return retval; +} + +/* Unlock the rwlock */ +extern "C" int +SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + if (!rwlock) { + return SDL_InvalidParamError("rwlock"); + } else if (rwlock->write_owner == SDL_ThreadID()) { + rwlock->write_owner = 0; + rwlock->cpp_mutex.unlock(); + } else { + rwlock->cpp_mutex.unlock_shared(); + } + return 0; +} + diff --git a/src/thread/windows/SDL_sysrwlock_srw.c b/src/thread/windows/SDL_sysrwlock_srw.c new file mode 100644 index 000000000..e797573f7 --- /dev/null +++ b/src/thread/windows/SDL_sysrwlock_srw.c @@ -0,0 +1,258 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2023 Sam Lantinga + + 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, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +/** + * Implementation based on Slim Reader/Writer (SRW) Locks for Win 7 and newer. + */ + +/* This header makes sure SRWLOCK is actually declared, even on ancient WinSDKs. */ +#include "SDL_sysmutex_c.h" + +typedef VOID(WINAPI *pfnInitializeSRWLock)(PSRWLOCK); +typedef VOID(WINAPI *pfnReleaseSRWLockShared)(PSRWLOCK); +typedef VOID(WINAPI *pfnAcquireSRWLockShared)(PSRWLOCK); +typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockShared)(PSRWLOCK); +typedef VOID(WINAPI *pfnReleaseSRWLockExclusive)(PSRWLOCK); +typedef VOID(WINAPI *pfnAcquireSRWLockExclusive)(PSRWLOCK); +typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockExclusive)(PSRWLOCK); + +#ifdef __WINRT__ +/* Functions are guaranteed to be available */ +#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive +#define pInitializeSRWLock InitializeSRWLock +#define pReleaseSRWLockShared ReleaseSRWLockShared +#define pAcquireSRWLockShared AcquireSRWLockShared +#define pTryAcquireSRWLockShared TryAcquireSRWLockShared +#define pReleaseSRWLockExclusive ReleaseSRWLockExclusive +#define pAcquireSRWLockExclusive AcquireSRWLockExclusive +#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive +#else +static pfnInitializeSRWLock pInitializeSRWLock = NULL; +static pfnReleaseSRWLockShared pReleaseSRWLockShared = NULL; +static pfnAcquireSRWLockShared pAcquireSRWLockShared = NULL; +static pfnTryAcquireSRWLockShared pTryAcquireSRWLockShared = NULL; +static pfnReleaseSRWLockExclusive pReleaseSRWLockExclusive = NULL; +static pfnAcquireSRWLockExclusive pAcquireSRWLockExclusive = NULL; +static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL; +#endif + +typedef SDL_rwlock *(*pfnSDL_CreateRWLock)(void); +typedef void (*pfnSDL_DestroyRWLock)(SDL_rwlock *); +typedef int (*pfnSDL_LockRWLockForReading)(SDL_rwlock *); +typedef int (*pfnSDL_LockRWLockForWriting)(SDL_rwlock *); +typedef int (*pfnSDL_TryLockRWLockForReading)(SDL_rwlock *); +typedef int (*pfnSDL_TryLockRWLockForWriting)(SDL_rwlock *); +typedef int (*pfnSDL_UnlockRWLock)(SDL_rwlock *); + +typedef struct SDL_rwlock_impl_t +{ + pfnSDL_CreateRWLock Create; + pfnSDL_DestroyRWLock Destroy; + pfnSDL_LockRWLockForReading LockForReading; + pfnSDL_LockRWLockForWriting LockForWriting; + pfnSDL_TryLockRWLockForReading TryLockForReading; + pfnSDL_TryLockRWLockForWriting TryLockForWriting; + pfnSDL_UnlockRWLock Unlock; +} SDL_rwlock_impl_t; + +/* Implementation will be chosen at runtime based on available Kernel features */ +static SDL_rwlock_impl_t SDL_rwlock_impl_active = { 0 }; + +/* rwlock implementation using Win7+ slim read/write locks (SRWLOCK) */ + +typedef struct SDL_rwlock_srw +{ + SRWLOCK srw; + SDL_threadID write_owner; +} SDL_rwlock_srw; + +static SDL_rwlock *SDL_CreateRWLock_srw(void) +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *)SDL_calloc(1, sizeof(*rwlock)); + if (rwlock == NULL) { + SDL_OutOfMemory(); + } + pInitializeSRWLock(&rwlock->srw); + return (SDL_rwlock *)rwlock; +} + +static void SDL_DestroyRWLock_srw(SDL_rwlock *_rwlock) +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + if (rwlock) { + /* There are no kernel allocated resources */ + SDL_free(rwlock); + } +} + +static int SDL_LockRWLockForReading_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } + pAcquireSRWLockShared(&rwlock->srw); + return 0; +} + +static int SDL_LockRWLockForWriting_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } + pAcquireSRWLockExclusive(&rwlock->srw); + rwlock->write_owner = SDL_ThreadID(); + return 0; +} + +static int SDL_TryLockRWLockForReading_srw(SDL_rwlock *_rwlock) +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + int retval = 0; + + if (rwlock == NULL) { + retval = SDL_InvalidParamError("rwlock"); + } else { + retval = pTryAcquireSRWLockShared(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT; + } + return retval; +} + +static int SDL_TryLockRWLockForWriting_srw(SDL_rwlock *_rwlock) +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + int retval = 0; + + if (rwlock == NULL) { + retval = SDL_InvalidParamError("rwlock"); + } else { + retval = pTryAcquireSRWLockExclusive(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT; + } + return retval; +} + +static int SDL_UnlockRWLock_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock; + if (rwlock == NULL) { + return SDL_InvalidParamError("rwlock"); + } else if (rwlock->write_owner == SDL_ThreadID()) { + rwlock->write_owner = 0; + pReleaseSRWLockExclusive(&rwlock->srw); + } else { + pReleaseSRWLockShared(&rwlock->srw); + } + return 0; +} + +static const SDL_rwlock_impl_t SDL_rwlock_impl_srw = { + &SDL_CreateRWLock_srw, + &SDL_DestroyRWLock_srw, + &SDL_LockRWLockForReading_srw, + &SDL_LockRWLockForWriting_srw, + &SDL_TryLockRWLockForReading_srw, + &SDL_TryLockRWLockForWriting_srw, + &SDL_UnlockRWLock_srw +}; + +#ifndef __WINRT__ + +#include "../generic/SDL_sysrwlock_c.h" + +/* Generic rwlock implementation using SDL_mutex, SDL_cond, and SDL_AtomicInt */ +static const SDL_rwlock_impl_t SDL_rwlock_impl_generic = { + &SDL_CreateRWLock_generic, + &SDL_DestroyRWLock_generic, + &SDL_LockRWLockForReading_generic, + &SDL_LockRWLockForWriting_generic, + &SDL_TryLockRWLockForReading_generic, + &SDL_TryLockRWLockForWriting_generic, + &SDL_UnlockRWLock_generic +}; +#endif + +SDL_rwlock *SDL_CreateRWLock(void) +{ + if (SDL_rwlock_impl_active.Create == NULL) { + const SDL_rwlock_impl_t *impl; + +#ifdef __WINRT__ + /* Link statically on this platform */ + impl = &SDL_rwlock_impl_srw; +#else + /* Default to generic implementation, works with all mutex implementations */ + impl = &SDL_rwlock_impl_generic; + { + HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (kernel32) { + SDL_bool okay = SDL_TRUE; + #define LOOKUP_SRW_SYM(sym) if (okay) { if ((p##sym = (pfn##sym)GetProcAddress(kernel32, #sym)) == NULL) { okay = SDL_FALSE; } } + LOOKUP_SRW_SYM(InitializeSRWLock); + LOOKUP_SRW_SYM(ReleaseSRWLockShared); + LOOKUP_SRW_SYM(AcquireSRWLockShared); + LOOKUP_SRW_SYM(TryAcquireSRWLockShared); + LOOKUP_SRW_SYM(ReleaseSRWLockExclusive); + LOOKUP_SRW_SYM(AcquireSRWLockExclusive); + LOOKUP_SRW_SYM(TryAcquireSRWLockExclusive); + #undef LOOKUP_SRW_SYM + if (okay) { + impl = &SDL_rwlock_impl_srw; /* Use the Windows provided API instead of generic fallback */ + } + } + } +#endif + + SDL_copyp(&SDL_rwlock_impl_active, impl); + } + return SDL_rwlock_impl_active.Create(); +} + +void SDL_DestroyRWLock(SDL_rwlock *rwlock) +{ + SDL_rwlock_impl_active.Destroy(rwlock); +} + +int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + return SDL_rwlock_impl_active.LockForReading(rwlock); +} + +int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + return SDL_rwlock_impl_active.LockForWriting(rwlock); +} + +int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock) +{ + return SDL_rwlock_impl_active.TryLockForReading(rwlock); +} + +int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock) +{ + return SDL_rwlock_impl_active.TryLockForWriting(rwlock); +} + +int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */ +{ + return SDL_rwlock_impl_active.Unlock(rwlock); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 669646c5f..d6a7acc7b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -225,6 +225,7 @@ add_sdl_test_executable(testkeys SOURCES testkeys.c) add_sdl_test_executable(testloadso SOURCES testloadso.c) add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c) add_sdl_test_executable(testlock SOURCES testlock.c) +add_sdl_test_executable(testrwlock SOURCES testrwlock.c) add_sdl_test_executable(testmouse SOURCES testmouse.c) add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c) diff --git a/test/testrwlock.c b/test/testrwlock.c new file mode 100644 index 000000000..d7be73d8e --- /dev/null +++ b/test/testrwlock.c @@ -0,0 +1,179 @@ +/* + Copyright (C) 1997-2023 Sam Lantinga + + 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 rwlock locking functions + Also exercises the system's signal/thread interaction +*/ + +#include +#include +#include + +static SDL_rwlock *rwlock = NULL; +static SDL_threadID mainthread; +static SDL_AtomicInt doterminate; +static int nb_threads = 6; +static SDL_Thread **threads; +static int worktime = 1000; +static int writerworktime = 100; +static int timeout = 10000; +static SDLTest_CommonState *state; + +static void DoWork(const int workticks) /* "Work" */ +{ + const SDL_threadID tid = SDL_ThreadID(); + const SDL_bool is_reader = tid != mainthread; + const char *typestr = is_reader ? "Reader" : "Writer"; + int rc; + + SDL_Log("%s Thread %lu: ready to work\n", typestr, (unsigned long) tid); + rc = is_reader ? SDL_LockRWLockForReading(rwlock) : SDL_LockRWLockForWriting(rwlock); + if (rc < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't lock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError()); + } else { + SDL_Log("%s Thread %lu: start work!\n", typestr, (unsigned long) tid); + SDL_Delay(workticks); + SDL_Log("%s Thread %lu: work done!\n", typestr, (unsigned long) tid); + if (SDL_UnlockRWLock(rwlock) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't unlock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError()); + } + /* If this sleep isn't done, then threads may starve */ + SDL_Delay(10); + } +} + +static int SDLCALL +ReaderRun(void *data) +{ + SDL_Log("Reader Thread %lu: starting up", SDL_ThreadID()); + while (!SDL_AtomicGet(&doterminate)) { + DoWork(worktime); + } + SDL_Log("Reader Thread %lu: exiting!\n", SDL_ThreadID()); + return 0; +} + +int main(int argc, char *argv[]) +{ + int i; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (state == NULL) { + return 1; + } + + SDL_AtomicSet(&doterminate, 0); + + /* 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; + worktime = SDL_strtol(argv[i + 1], &endptr, 0); + if (endptr != argv[i + 1] && *endptr == '\0' && worktime > 0) { + consumed = 2; + } + } + } else if (SDL_strcmp(argv[i], "--writerworktime") == 0) { + if (argv[i + 1]) { + char *endptr; + writerworktime = SDL_strtol(argv[i + 1], &endptr, 0); + if (endptr != argv[i + 1] && *endptr == '\0' && writerworktime > 0) { + consumed = 2; + } + } + } else if (SDL_strcmp(argv[i], "--timeout") == 0) { + if (argv[i + 1]) { + char *endptr; + timeout = (Uint64) SDL_strtol(argv[i + 1], &endptr, 0); + if (endptr != argv[i + 1] && *endptr == '\0' && timeout > 0) { + consumed = 2; + } + } + } + } + if (consumed <= 0) { + static const char *options[] = { + "[--nbthreads NB]", + "[--worktime ms]", + "[--writerworktime ms]", + "[--timeout ms]", + NULL, + }; + SDLTest_CommonLogUsage(state, argv[0], options); + return 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()); + return 1; + } + + SDL_AtomicSet(&doterminate, 0); + + rwlock = SDL_CreateRWLock(); + if (rwlock == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create rwlock: %s\n", SDL_GetError()); + SDL_Quit(); + SDLTest_CommonDestroyState(state); + return 1; + } + + mainthread = SDL_ThreadID(); + SDL_Log("Writer thread: %lu\n", mainthread); + for (i = 0; i < nb_threads; ++i) { + char name[64]; + (void)SDL_snprintf(name, sizeof(name), "Reader%d", i); + threads[i] = SDL_CreateThread(ReaderRun, name, NULL); + if (threads[i] == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create reader thread! %s\n", SDL_GetError()); + } + } + + while (!SDL_AtomicGet(&doterminate) && (SDL_GetTicks() < ((Uint64) timeout))) { + DoWork(writerworktime); + } + + SDL_AtomicSet(&doterminate, 1); + SDL_Log("Waiting on reader threads to terminate..."); + for (i = 0; i < nb_threads; ++i) { + SDL_WaitThread(threads[i], NULL); + } + + SDL_Log("Reader threads have terminated, quitting!"); + SDL_DestroyRWLock(rwlock); + SDLTest_CommonDestroyState(state); + SDL_Quit(); + + return 0; +}