Add detached threads impl, make init order more robust, print to console with UTF16

This commit is contained in:
2026-07-15 21:48:41 +10:00
parent ec5f5e927b
commit a718c600b9
7 changed files with 228 additions and 88 deletions
+101 -41
View File
@@ -1,4 +1,4 @@
// Generated by the DN single header generator 2026-07-15 17:58:16
// Generated by the DN single header generator 2026-07-15 21:41:33
// DN: Single header generator commented out => #if defined(_CLANGD)
// #define DN_WITH_TESTS 1
@@ -2189,13 +2189,12 @@ DN_API void DN_TCInitFromHeap(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs arg
DN_API void DN_TCDeinit(DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas)
{
// NOTE: That we deallocate the main memory last as TC might be allocated in that arena.
if (deinit_arenas == DN_TCDeinitArenas_Yes) {
DN_MemListDeinit(tc->main_arena->mem);
for (DN_ForIndexU(index, tc->temp_arenas_count)) {
for (DN_ForIndexU(index, tc->temp_arenas_count))
DN_MemListDeinit(tc->temp_arenas[index]->mem);
DN_MemListDeinit(tc->temp_arenas[index]->mem);
}
DN_MemListDeinit(tc->err_sink.arena->mem);
DN_MemListDeinit(tc->main_arena->mem);
}
}
@@ -12369,11 +12368,32 @@ DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *a
return result;
}
// NOTE: DN_OSThread
static void DN_OS_ThreadExecute_(void *user_context)
{
DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context;
// NOTE: Wait on the semaphore, the thread that initiated the thread is going to retrieve the
// thread ID, then set it the value on `thread->thread_id` pointer then increment this semaphore
// to unblock this thread.
//
// This semaphore is deleted in the caller's thread when we unblock them by signalling the
// semaphore below this.
DN_OS_SemaphoreWait(&thread->thread_exec_wait_for_thread_id_sem, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
// NOTE: Setup thread context (TLS) _after_ thread ID is setup.
DN_TCInitFromHeap(&thread->context, thread->thread_id, thread->tc_init_args, DN_OS_HeapInitDefault());
// NOTE: Once all initialisation is done, if the thread is to be detached, make a copy of the
// thread pointer because the caller is not guaranteeing to keep the thread pointer alive (they
// can throw away the DN_OSThread since they want to detach the thread)
//
// Note that the caller _cannot_ throw away the thread pointer until we increment the semaphore
// below as the OS implementation _should_ be sleeping on that semaphore.
if (thread->flags & DN_OSThreadFlags_Detached)
thread = DN_ArenaNewCopy(thread->context.main_arena, DN_OSThread, thread);
// NOTE: Equip the pointers into TLS only _after_ the thread context is copied (if it was
// detached) to avoid potential dangling ref in the TLS.
DN_TCEquip(&thread->context);
if (thread->is_lane_set) {
DN_OS_TCThreadLaneEquip(thread->lane);
@@ -12381,8 +12401,55 @@ static void DN_OS_ThreadExecute_(void *user_context)
} else {
DN_OS_ThreadSetNameFmt("T%zu", thread->lane.index, thread->lane.count, thread->thread_id);
}
DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
// NOTE: Now we can increment the semaphore that the caller's thread is waiting on now that we've
// safely initialised the thread's contents and made a copy if necessary.
DN_OS_SemaphoreIncrement(&thread->caller_wait_for_thread_init_to_finish_sem, 1);
// NOTE: Run the user's code
thread->func(thread);
// NOTE: If we're detached, it's this thread's responsibility to cleanup itself. In the platform
// layer it should have closed any references to the thread so we should just need to cleanup the
// TLS.
if (thread->flags & DN_OSThreadFlags_Detached)
DN_TCDeinit(&thread->context, DN_TCDeinitArenas_Yes);
}
static void DN_OS_ThreadPreInit_(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context)
{
if (thread) {
thread->func = func;
thread->user_context = user_context;
thread->thread_exec_wait_for_thread_id_sem = DN_OS_SemaphoreInit(0 /*initial_count*/);
thread->caller_wait_for_thread_init_to_finish_sem = DN_OS_SemaphoreInit(0 /*initial_count*/);
thread->tc_init_args = init_args.tc_args;
thread->flags = init_args.flags;
if (lane) {
thread->is_lane_set = true;
thread->lane = *lane;
}
}
}
static void DN_OS_ThreadPostInit_(DN_OSThread *thread, bool result)
{
// NOTE: Ensure that thread_id is set before 'thread->func' is called.
if (result) {
// NOTE: Unblock the thread executor now that thread_id and flags have been set
DN_OS_SemaphoreIncrement(&thread->thread_exec_wait_for_thread_id_sem, 1);
// NOTE: Wait for the thread to finish initialising before we leave
DN_OS_SemaphoreWait(&thread->caller_wait_for_thread_init_to_finish_sem,
DN_OS_SEMAPHORE_INFINITE_TIMEOUT);
}
// NOTE: Clean up the semaphores
DN_OS_SemaphoreDeinit(&thread->thread_exec_wait_for_thread_id_sem);
DN_OS_SemaphoreDeinit(&thread->caller_wait_for_thread_init_to_finish_sem);
if (!result)
*thread = {};
}
DN_API DN_OSThreadInitArgs DN_OS_ThreadInitArgsDefault()
@@ -12706,10 +12773,14 @@ DN_API void DN_OS_Print(DN_OSPrintDest dest, DN_Str8 string)
DN_Assert(string.count < DN_Cast(unsigned long) - 1);
unsigned long bytes_written = 0;
(void)bytes_written;
if (print_to_console)
WriteConsoleA(print_handle, string.data, DN_Cast(unsigned long) string.count, &bytes_written, nullptr);
else
if (print_to_console) {
DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0);
DN_Str16 string16 = DN_OS_W32Str8ToStr16(&scratch.arena, string);
WriteConsoleW(print_handle, string16.data, DN_Cast(unsigned long) string16.count, &bytes_written, nullptr);
DN_TCScratchEnd(&scratch);
} else {
WriteFile(print_handle, string.data, DN_Cast(unsigned long) string.count, &bytes_written, nullptr);
}
#else
fprintf(dest == DN_OSPrintDest_Out ? stdout : stderr, "%.*s", DN_Str8PrintFmt(string));
#endif
@@ -14351,11 +14422,7 @@ DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_
if (!thread)
return result;
thread->func = func;
thread->user_context = user_context;
thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/);
thread->lane = *lane;
thread->tc_init_args = init_args.tc_args;
DN_OS_ThreadPreInit_(thread, func, lane, init_args, user_context);
// TODO(doyle): Check if semaphore is valid
// NOTE: pthread_t is essentially the thread ID. In Windows, the handle and
@@ -14380,15 +14447,13 @@ DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_
if (result) {
DN_Memcpy(&thread->handle, &p_thread, sizeof(p_thread));
DN_Memcpy(&thread->thread_id, &p_thread, sizeof(p_thread));
if (thread->flags & DN_OSThreadFlags_Detached) {
pthread_detach(p_thread);
thread->handle = {};
}
}
if (result) {
DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1);
} else {
DN_OS_SemaphoreDeinit(&thread->init_semaphore);
*thread = {};
}
DN_OS_ThreadPostInit_(thread, result);
return result;
}
@@ -14396,6 +14461,8 @@ DN_API bool DN_OS_ThreadJoin(DN_OSThread *thread, DN_TCDeinitArenas deinit_arena
{
bool result = false;
if (thread && thread->handle) {
DN_AssertF(DN_BitIsNotSet(thread->flags, DN_OSThreadFlags_Detached),
"Detached threads should have their handle immediately closed and invalidated so this branch should never hit.");
pthread_t thread_id = {};
DN_Memcpy(&thread_id, &thread->thread_id, sizeof(thread_id));
@@ -15193,7 +15260,7 @@ DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle,
char *stderr_buffer,
DN_USize *stderr_size,
DN_U32 timeout_ms,
DN_ErrSink *err)
DN_ErrSink *err)
{
DN_OSExecResult result = {};
size_t stdout_buffer_size = 0;
@@ -15750,14 +15817,7 @@ DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_
if (!thread)
return result;
thread->func = func;
thread->user_context = user_context;
thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/);
thread->tc_init_args = init_args.tc_args;
if (lane) {
thread->is_lane_set = true;
thread->lane = *lane;
}
DN_OS_ThreadPreInit_(thread, func, lane, init_args, user_context);
// TODO(doyle): Check if semaphore is valid
DWORD thread_id = 0;
@@ -15769,25 +15829,25 @@ DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_
0 /*creation_flags*/,
&thread_id);
result = thread->handle != INVALID_HANDLE_VALUE;
if (result)
thread->thread_id = thread_id;
// NOTE: Ensure that thread_id is set before 'thread->func' is called.
result = thread->handle != NULL;
if (result) {
DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1);
} else {
DN_OS_SemaphoreDeinit(&thread->init_semaphore);
*thread = {};
thread->thread_id = thread_id;
if (thread->flags & DN_OSThreadFlags_Detached) {
CloseHandle(thread->handle);
thread->handle = NULL;
}
}
DN_OS_ThreadPostInit_(thread, result);
return result;
}
DN_API bool DN_OS_ThreadJoin(DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas)
{
bool result = false;
if (thread && thread->handle) {
if (thread && thread->handle && thread->handle != INVALID_HANDLE_VALUE) {
DN_AssertF(DN_BitIsNotSet(thread->flags, DN_OSThreadFlags_Detached),
"Detached threads should have their handle immediately closed and invalidated so this branch should never hit.");
DWORD wait_result = WaitForSingleObject(thread->handle, INFINITE);
result = wait_result == WAIT_OBJECT_0;
CloseHandle(thread->handle);