Merge pull request #5464 from peperronii/master

core:thread get_name/set_name
This commit is contained in:
Jeroen van Rijn
2026-07-30 12:52:16 +02:00
committed by GitHub
12 changed files with 305 additions and 34 deletions

View File

@@ -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"

View File

@@ -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 ---

View File

@@ -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)
}
}
}

View File

@@ -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 ---
}

View File

@@ -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 ---
}

View File

@@ -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 ---
}

View File

@@ -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 ---
}

View File

@@ -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) ---
}

View File

@@ -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")
}

View File

@@ -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[:]))
}
}

View File

@@ -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[:])))
}

View File

@@ -49,4 +49,40 @@ poly_data_test :: proc(_t: ^testing.T) {
defer free(t4)
thread.join_multiple(t1, t2, t3, t4)
}
}
@(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)
}