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
+8 -12
View File
@@ -1287,11 +1287,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
@@ -1316,15 +1312,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;
}
@@ -1332,6 +1326,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));
+12 -19
View File
@@ -683,7 +683,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;
@@ -1240,14 +1240,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;
@@ -1259,25 +1252,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);
+1 -1
View File
@@ -383,7 +383,7 @@
// NOTE: consoleapi.h ///////////////////////////////////////////////////////////////////////////
extern "C"
{
__declspec(dllimport) BOOL __stdcall WriteConsoleA(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved);
__declspec(dllimport) BOOL __stdcall WriteConsoleW(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved);
__declspec(dllimport) BOOL __stdcall AllocConsole(VOID);
__declspec(dllimport) BOOL __stdcall FreeConsole(VOID);
__declspec(dllimport) BOOL __stdcall AttachConsole(DWORD dwProcessId);
+80 -9
View File
@@ -2187,13 +2187,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);
}
}
@@ -12367,11 +12366,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);
@@ -12379,8 +12399,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()
@@ -12704,10 +12771,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
+12 -2
View File
@@ -2457,6 +2457,13 @@ struct DN_OSConditionVariable
typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *);
typedef DN_U32 DN_OSThreadFlags;
enum DN_OSThreadFlags_
{
DN_OSThreadFlags_Nil = 0,
DN_OSThreadFlags_Detached = 1 << 0,
};
typedef struct DN_OSThreadInitArgs DN_OSThreadInitArgs;
struct DN_OSThreadInitArgs
{
@@ -2466,7 +2473,8 @@ struct DN_OSThreadInitArgs
// NOTE: Set how much stack space in bytes the thread will have. Leave this value to 0 to defer
// to the OS's default stack size.
DN_USize stack_size;
DN_USize stack_size;
DN_OSThreadFlags flags;
};
typedef struct DN_OSThreadLane DN_OSThreadLane;
@@ -2490,6 +2498,7 @@ struct DN_OSThreadLaneway
typedef struct DN_OSThread DN_OSThread;
struct DN_OSThread
{
DN_OSThreadFlags flags;
DN_Str8x64 name;
DN_TCCore context;
DN_OSThreadLane lane;
@@ -2498,7 +2507,8 @@ struct DN_OSThread
DN_U64 thread_id;
void *user_context;
DN_OSThreadFunc *func;
DN_OSSemaphore init_semaphore;
DN_OSSemaphore thread_exec_wait_for_thread_id_sem;
DN_OSSemaphore caller_wait_for_thread_init_to_finish_sem;
DN_TCInitArgs tc_init_args;
};