diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index 84073c26f..1ee3875d8 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -5,7 +5,7 @@ import "core:c" when ODIN_OS == .Darwin { foreign import lib "system:System" -} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux { +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux || ODIN_OS == .OpenBSD { foreign import lib "system:pthread" } else { foreign import lib "system:c" diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 9b08d7ba6..728bed485 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -180,7 +180,7 @@ foreign kernel32 { ResumeThread :: proc(thread: HANDLE) -> DWORD --- GetThreadPriority :: proc(thread: HANDLE) -> c_int --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- - GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT --- + GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PWSTR) -> HRESULT --- SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- diff --git a/core/thread/thread.odin b/core/thread/thread.odin index a07801b98..14e266b20 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -52,6 +52,10 @@ Thread :: struct { // started. Should be set after the thread has been created, but before // it is started. data: rawptr, + // Thread's Name/Description that will get set during thread creation + // it will be set using init_context's allocator to allocate and free a cstring buffer + // for thread's creation only : do not refer to it, use thread.get_name instead + name: Maybe(string), // User-supplied integer, that will be available to the thread once it is // started. Should be set after the thread has been created, but before // it is started. @@ -105,9 +109,13 @@ thread will be in a suspended state, until `start()` procedure is called. To start the thread, call `start()`. Also the `create_and_start()` procedure can be called to create and start the thread immediately. + +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. */ -create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { - return _create(procedure, priority) + +create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { + return _create(procedure, priority, name) } /* @@ -159,6 +167,20 @@ yield :: proc() { _yield() } +/* +Get thread's name/description. + +This procedure returns the name of the given thread. If `thread` is `nil`, this procedure returns the name of the calling thread. +OS level errors are silently ignored. + +**Note(linux, bsd)**: Because the thread name is stored in as the `cmdline`, if the thread name was not set, the command that has been used to create the process will be used as the name of the thread. + +allocates memory for the returned string using provided allocator. +*/ +get_name :: proc(thread: ^Thread = nil, allocator := context.temp_allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { + return _get_name(thread, allocator, loc) +} + /* Run a procedure on a different thread. @@ -166,12 +188,15 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - create_and_start(fn, init_context, priority, true) +run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) { + create_and_start(fn, init_context, priority, true, name) } /* @@ -181,12 +206,15 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - create_and_start_with_data(data, fn, init_context, priority, true) +run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) { + create_and_start_with_data(data, fn, init_context, priority, true, name) } /* @@ -196,13 +224,16 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data(data, fn, init_context, priority, true) + create_and_start_with_poly_data(data, fn, init_context, priority, true, name) } /* @@ -212,13 +243,16 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) + create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true, name) } /* @@ -228,13 +262,16 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) + create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true, name) } /* @@ -244,13 +281,16 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) + create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true, name) } /* @@ -264,6 +304,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -271,12 +314,12 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { +create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data fn() } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -299,6 +342,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -306,14 +352,14 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { +create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data assert(t.user_index >= 1) data := t.user_args[0] fn(data) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -338,6 +384,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -345,7 +394,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data @@ -355,7 +404,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex fn(data) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -383,6 +432,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -390,7 +442,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2))t.data @@ -403,7 +455,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), fn(arg1, arg2) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -434,6 +486,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -441,7 +496,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3))t.data @@ -455,7 +510,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr fn(arg1, arg2, arg3) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -487,6 +542,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to fit the OS's limit. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -494,7 +552,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3, T4))t.data @@ -508,7 +566,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: fn(arg1, arg2, arg3, arg4) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -560,4 +618,4 @@ _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Contex if context.temp_allocator.procedure == runtime.default_temp_allocator_proc { runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) } -} \ No newline at end of file +} diff --git a/core/thread/thread_name_darwin.odin b/core/thread/thread_name_darwin.odin new file mode 100644 index 000000000..7a01e0371 --- /dev/null +++ b/core/thread/thread_name_darwin.odin @@ -0,0 +1,15 @@ +#+build darwin +#+private +package thread + +import "core:sys/posix" +import "core:c" + +_MAX_PTHREAD_NAME_LENGTH :: 64 + +foreign import pthread "system:System.framework" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_freebsd.odin b/core/thread/thread_name_freebsd.odin new file mode 100644 index 000000000..5b79728a2 --- /dev/null +++ b/core/thread/thread_name_freebsd.odin @@ -0,0 +1,15 @@ +#+build freebsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +_MAX_PTHREAD_NAME_LENGTH :: 20 + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_linux.odin b/core/thread/thread_name_linux.odin new file mode 100644 index 000000000..b84bf999c --- /dev/null +++ b/core/thread/thread_name_linux.odin @@ -0,0 +1,15 @@ +#+build linux +#+private +package thread + +import "core:sys/posix" +import "core:c" + +_MAX_PTHREAD_NAME_LENGTH :: 16 + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_netbsd.odin b/core/thread/thread_name_netbsd.odin new file mode 100644 index 000000000..c62c8c8f4 --- /dev/null +++ b/core/thread/thread_name_netbsd.odin @@ -0,0 +1,15 @@ +#+build netbsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +_MAX_PTHREAD_NAME_LENGTH :: 32 + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: cstring, arg: rawptr) -> posix.Errno --- +} diff --git a/core/thread/thread_name_openbsd.odin b/core/thread/thread_name_openbsd.odin new file mode 100644 index 000000000..b349c2566 --- /dev/null +++ b/core/thread/thread_name_openbsd.odin @@ -0,0 +1,15 @@ +#+build openbsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:pthread" + +_MAX_PTHREAD_NAME_LENGTH :: 20 + +foreign pthread { + pthread_get_name_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) --- + pthread_set_name_np :: proc(thread: posix.pthread_t, name: [^]u8) --- +} diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index 878119cbd..aa310d41e 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -2,6 +2,7 @@ package thread import "base:intrinsics" +import "base:runtime" _IS_SUPPORTED :: false @@ -13,7 +14,7 @@ _thread_priority_map := [Thread_Priority]i32{ .High = +2, } -_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { unimplemented("core:thread procedure not supported on target") } @@ -45,3 +46,10 @@ _yield :: proc() { unimplemented("core:thread procedure not supported on target") } +_get_name :: proc(thread: ^Thread, allocator : runtime.Allocator, loc : runtime.Source_Code_Location) -> (string, runtime.Allocator_Error) { + unimplemented("core:thread procedure not supported on this target") +} + +_set_name :: proc(thread: ^Thread, name:string) { + unimplemented("core:thread procedure not supported on this target") +} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 02e628df5..fef0db9f8 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -5,9 +5,9 @@ package thread import "base:runtime" import "core:sync" import "core:sys/posix" +import "core:strings" _IS_SUPPORTED :: true - // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { @@ -18,7 +18,7 @@ Thread_Os_Specific :: struct #align(16) { // Creates a thread which will run the given procedure. // It then waits for `start` to be called. // -_create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(string)) -> ^Thread { __unix_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) @@ -50,6 +50,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { runtime.run_thread_local_cleaners() } + _set_name(t) + t.procedure(t) } @@ -122,6 +124,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { free(thread, thread.creation_allocator) return nil } + + thread.name = name + return thread } @@ -184,3 +189,50 @@ _terminate :: proc(t: ^Thread, exit_code: int) { _yield :: proc() { posix.sched_yield() } + +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name: string, err: runtime.Allocator_Error) { + tid: posix.pthread_t + if thread == nil { + tid = posix.pthread_self() + } else { + tid = thread.unix_thread + } + + buf : [_MAX_PTHREAD_NAME_LENGTH]u8 + + when ODIN_OS == .OpenBSD { + pthread_get_name_np(tid, raw_data(buf[:]), len(buf)) + } else { + pthread_getname_np(tid, raw_data(buf[:]), len(buf)) + } + + name, err = strings.clone_from_cstring(cstring(raw_data(buf[:])), allocator, loc) + return +} + +_set_name :: proc(thread: ^Thread) { + name, ok := thread.name.? + if !ok { + return + } + + when ODIN_OS != .Darwin { + tid := thread.unix_thread + } + + // _MAX_PTHREAD_NAME_LENGTH includes terminating null + buflen := len(name) + 1 < _MAX_PTHREAD_NAME_LENGTH ? len(name) + 1 : _MAX_PTHREAD_NAME_LENGTH + buf := make([]u8, buflen) + defer delete(buf) + copy(buf[:len(buf) - 1], name) + + when ODIN_OS == .Darwin { + pthread_setname_np(raw_data(buf[:])) + } else when ODIN_OS == .OpenBSD { + pthread_set_name_np(tid, raw_data(buf[:])) + } else when ODIN_OS == .NetBSD { + pthread_setname_np(tid, "%s", raw_data(buf[:])) + } else { + pthread_setname_np(tid, raw_data(buf[:])) + } +} diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 358e3e7f1..f13d550f7 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -6,9 +6,13 @@ import "base:intrinsics" import "base:runtime" import "core:sync" import win32 "core:sys/windows" +import "core:unicode/utf16" _IS_SUPPORTED :: true +//NOTE(peperronii): this is the system limit for windows api call, not specific to thread description +_THREAD_DESCRIPTION_LENGTH :: 32_767 + Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, @@ -22,7 +26,7 @@ _thread_priority_map := [Thread_Priority]i32{ .High = +2, } -_create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(string)) -> ^Thread { win32_thread_id: win32.DWORD __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { @@ -44,6 +48,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { runtime.run_thread_local_cleaners() } + _set_name(t) + t.procedure(t) } @@ -76,6 +82,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { thread.win32_thread = win32_thread thread.win32_thread_id = win32_thread_id thread.id = int(win32_thread_id) + thread.name = name ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]) assert(ok == true) @@ -152,3 +159,38 @@ _yield :: proc() { win32.SwitchToThread() } +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name: string, err: runtime.Allocator_Error) { + t_handle: win32.HANDLE + if thread == nil { + t_handle = win32.GetCurrentThread() + } else { + t_handle = thread.win32_thread + } + + buf: win32.PWSTR + + hr := win32.GetThreadDescription(t_handle, &buf) + + if win32.SUCCEEDED(hr) { + defer win32.LocalFree(rawptr(buf)) + name, err = win32.wstring_to_utf8(buf, -1, allocator) + } + return +} + +_set_name :: proc(thread: ^Thread) { + name, ok := thread.name.? + if !ok { + return + } + + t_handle := thread.win32_thread + + // _THREAD_DESCRIPTION_LENGTH includes terminating null + buflen := len(name) + 1 < _THREAD_DESCRIPTION_LENGTH ? len(name) + 1 : _THREAD_DESCRIPTION_LENGTH + buf := make([]u16, buflen) + defer delete(buf) + utf16.encode_string(buf[:len(buf) - 1], name) + win32.SetThreadDescription(t_handle, cstring16(raw_data(buf[:]))) +} + diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index 0b77ad511..e06fcc3cf 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -49,4 +49,40 @@ poly_data_test :: proc(_t: ^testing.T) { defer free(t4) thread.join_multiple(t1, t2, t3, t4) -} \ No newline at end of file +} + +@(test) +name_test :: proc(_t: ^testing.T) { + @static name_test_t: ^testing.T + @static main_wait := true + @static child_wait := true + name_test_t = _t + + t := thread.create_and_start(name = "test_name", fn = proc() { + main_wait = false + + for (child_wait) {} + + n, err := thread.get_name() + defer if err != nil { + delete(n) + } + testing.expect(name_test_t, err == nil, "thread name allocation failed") + testing.expectf(name_test_t, n == "test_name","thread name on self did not match : got %v", n) + + }) + defer free(t) + + for (main_wait) {} + + n, err := thread.get_name(t) + defer if err != nil { + delete(n) + } + testing.expect(name_test_t, err == nil, "thread name allocation failed") + testing.expectf(name_test_t, n == "test_name","thread name on main did not match : got %v", n) + + child_wait = false + + thread.join(t) +}