diff --git a/include/SDL_thread.h b/include/SDL_thread.h index f248c3d69..42df3d6c3 100644 --- a/include/SDL_thread.h +++ b/include/SDL_thread.h @@ -165,13 +165,53 @@ extern DECLSPEC SDL_threadID SDLCALL SDL_GetThreadID(SDL_Thread * thread); extern DECLSPEC int SDLCALL SDL_SetThreadPriority(SDL_ThreadPriority priority); /** - * Wait for a thread to finish. + * Wait for a thread to finish. Threads that haven't been detached will + * remain (as a "zombie") until this function cleans them up. Not doing so + * is a resource leak. + * + * Once a thread has been cleaned up through this function, the SDL_Thread + * that references it becomes invalid and should not be referenced again. + * As such, only one thread may call SDL_WaitThread() on another. * * The return code for the thread function is placed in the area * pointed to by \c status, if \c status is not NULL. + * + * You may not wait on a thread that has been used in a call to + * SDL_DetachThread(). Use either that function or this one, but not + * both, or behavior is undefined. + * + * It is safe to pass NULL to this function; it is a no-op. */ extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread * thread, int *status); +/** + * A thread may be "detached" to signify that it should not remain until + * another thread has called SDL_WaitThread() on it. Detaching a thread + * is useful for long-running threads that nothing needs to synchronize + * with or further manage. When a detached thread is done, it simply + * goes away. + * + * There is no way to recover the return code of a detached thread. If you + * need this, don't detach the thread and instead use SDL_WaitThread(). + * + * Once a thread is detached, you should usually assume the SDL_Thread isn't + * safe to reference again, as it will become invalid immediately upon + * the detached thread's exit, instead of remaining until someone has called + * SDL_WaitThread() to finally clean it up. As such, don't detach the same + * thread more than once. + * + * If a thread has already exited when passed to SDL_DetachThread(), it will + * stop waiting for a call to SDL_WaitThread() and clean up immediately. + * It is not safe to detach a thread that might be used with SDL_WaitThread(). + * + * You may not call SDL_WaitThread() on a thread that has been detached. + * Use either that function or this one, but not both, or behavior is + * undefined. + * + * It is safe to pass NULL to this function; it is a no-op. + */ +extern DECLSPEC void SDLCALL SDL_DetachThread(SDL_Thread * thread); + /** * \brief Create an identifier that is globally visible to all threads but refers to data that is thread-specific. * diff --git a/src/thread/SDL_systhread.h b/src/thread/SDL_systhread.h index 36898f389..3c8ba899b 100644 --- a/src/thread/SDL_systhread.h +++ b/src/thread/SDL_systhread.h @@ -51,6 +51,9 @@ extern int SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority); */ extern void SDL_SYS_WaitThread(SDL_Thread * thread); +/* Mark thread as cleaned up as soon as it exits, without joining. */ +extern void SDL_SYS_DetachThread(SDL_Thread * thread); + /* Get the thread local storage for this thread */ extern SDL_TLSData *SDL_SYS_GetTLSData(); diff --git a/src/thread/SDL_thread.c b/src/thread/SDL_thread.c index 4b070dadc..362e75a42 100644 --- a/src/thread/SDL_thread.c +++ b/src/thread/SDL_thread.c @@ -22,6 +22,7 @@ /* System independent thread management routines for SDL */ +#include "SDL_assert.h" #include "SDL_thread.h" #include "SDL_thread_c.h" #include "SDL_systhread.h" @@ -265,13 +266,14 @@ SDL_RunThread(void *data) thread_args *args = (thread_args *) data; int (SDLCALL * userfunc) (void *) = args->func; void *userdata = args->data; - int *statusloc = &args->info->status; + SDL_Thread *thread = args->info; + int *statusloc = &thread->status; /* Perform any system-dependent setup - this function may not fail */ - SDL_SYS_SetupThread(args->info->name); + SDL_SYS_SetupThread(thread->name); /* Get the thread id */ - args->info->threadid = SDL_ThreadID(); + thread->threadid = SDL_ThreadID(); /* Wake up the parent thread */ SDL_SemPost(args->wait); @@ -281,6 +283,17 @@ SDL_RunThread(void *data) /* Clean up thread-local storage */ SDL_TLSCleanup(); + + /* Mark us as ready to be joined (or detached) */ + if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) { + /* Clean up if something already detached us. */ + if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) { + if (thread->name) { + SDL_free(thread->name); + } + SDL_free(thread); + } + } } #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD @@ -306,8 +319,9 @@ SDL_CreateThread(int (SDLCALL * fn) (void *), SDL_OutOfMemory(); return (NULL); } - SDL_memset(thread, 0, (sizeof *thread)); + SDL_zerop(thread); thread->status = -1; + SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE); /* Set up the arguments for the thread */ if (name != NULL) { @@ -410,4 +424,27 @@ SDL_WaitThread(SDL_Thread * thread, int *status) } } +void +SDL_DetachThread(SDL_Thread * thread) +{ + if (!thread) { + return; + } + + /* Grab dibs if the state is alive+joinable. */ + if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) { + SDL_SYS_DetachThread(thread); + } else { + /* all other states are pretty final, see where we landed. */ + const int state = SDL_AtomicGet(&thread->state); + if ((state == SDL_THREAD_STATE_DETACHED) || (state == SDL_THREAD_STATE_CLEANED)) { + return; /* already detached (you shouldn't call this twice!) */ + } else if (state == SDL_THREAD_STATE_ZOMBIE) { + SDL_WaitThread(thread, NULL); /* already done, clean it up. */ + } else { + SDL_assert(0 && "Unexpected thread state"); + } + } +} + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/SDL_thread_c.h b/src/thread/SDL_thread_c.h index 4fe925494..8d76a5f39 100644 --- a/src/thread/SDL_thread_c.h +++ b/src/thread/SDL_thread_c.h @@ -40,12 +40,21 @@ #endif #include "../SDL_error_c.h" +typedef enum SDL_ThreadState +{ + SDL_THREAD_STATE_ALIVE, + SDL_THREAD_STATE_DETACHED, + SDL_THREAD_STATE_ZOMBIE, + SDL_THREAD_STATE_CLEANED, +} SDL_ThreadState; + /* This is the system-independent thread info structure */ struct SDL_Thread { SDL_threadID threadid; SYS_ThreadHandle handle; int status; + SDL_atomic_t state; /* SDL_THREAD_STATE_* */ SDL_error errbuf; char *name; void *data; diff --git a/src/thread/generic/SDL_systhread.c b/src/thread/generic/SDL_systhread.c index 139f8ac03..ab7f4bad9 100644 --- a/src/thread/generic/SDL_systhread.c +++ b/src/thread/generic/SDL_systhread.c @@ -62,4 +62,10 @@ SDL_SYS_WaitThread(SDL_Thread * thread) return; } +void +SDL_SYS_DetachThread(SDL_Thread * thread) +{ + return; +} + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/psp/SDL_systhread.c b/src/thread/psp/SDL_systhread.c index 05a2341fe..269defe4d 100644 --- a/src/thread/psp/SDL_systhread.c +++ b/src/thread/psp/SDL_systhread.c @@ -77,6 +77,12 @@ void SDL_SYS_WaitThread(SDL_Thread *thread) sceKernelDeleteThread(thread->handle); } +void SDL_SYS_DetachThread(SDL_Thread *thread) +{ + /* !!! FIXME: is this correct? */ + sceKernelDeleteThread(thread->handle); +} + void SDL_SYS_KillThread(SDL_Thread *thread) { sceKernelTerminateDeleteThread(thread->handle); diff --git a/src/thread/pthread/SDL_systhread.c b/src/thread/pthread/SDL_systhread.c index f9eaef273..4c23478e5 100644 --- a/src/thread/pthread/SDL_systhread.c +++ b/src/thread/pthread/SDL_systhread.c @@ -213,4 +213,10 @@ SDL_SYS_WaitThread(SDL_Thread * thread) pthread_join(thread->handle, 0); } +void +SDL_SYS_DetachThread(SDL_Thread * thread) +{ + pthread_detach(thread->handle); +} + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/windows/SDL_systhread.c b/src/thread/windows/SDL_systhread.c index 06e23abd0..aa2db2e23 100644 --- a/src/thread/windows/SDL_systhread.c +++ b/src/thread/windows/SDL_systhread.c @@ -234,6 +234,12 @@ SDL_SYS_WaitThread(SDL_Thread * thread) CloseHandle(thread->handle); } +void +SDL_SYS_DetachThread(SDL_Thread * thread) +{ + CloseHandle(thread->handle); +} + #endif /* SDL_THREAD_WINDOWS */ /* vi: set ts=4 sw=4 expandtab: */