diff --git a/base/runtime/thread_management.odin b/base/runtime/thread_management.odin new file mode 100644 index 000000000..cabd4691c --- /dev/null +++ b/base/runtime/thread_management.odin @@ -0,0 +1,34 @@ +package runtime + +Thread_Local_Cleaner :: #type proc "odin" () + +@(private="file") +thread_local_cleaners: [8]Thread_Local_Cleaner + +// Add a procedure that will be run at the end of a thread for the purpose of +// deallocating state marked as `thread_local`. +// +// Intended to be called in an `init` procedure of a package with +// dynamically-allocated memory that is stored in `thread_local` variables. +add_thread_local_cleaner :: proc "contextless" (p: Thread_Local_Cleaner) { + for &v in thread_local_cleaners { + if v == nil { + v = p + return + } + } + panic_contextless("There are no more thread-local cleaner slots available.") +} + +// Run all of the thread-local cleaner procedures. +// +// Intended to be called by the internals of a threading API at the end of a +// thread's lifetime. +run_thread_local_cleaners :: proc "odin" () { + for p in thread_local_cleaners { + if p == nil { + break + } + p() + } +} diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index 0ab3adfb2..ddfe230be 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -61,3 +61,8 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime. global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT return tmp, loc } + +@(init, private) +init_thread_local_cleaner :: proc() { + runtime.add_thread_local_cleaner(temp_allocator_fini) +} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index f56454bfc..3d25b1909 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -2,6 +2,7 @@ // +private package thread +import "base:runtime" import "core:sync" import "core:sys/unix" import "core:time" @@ -55,7 +56,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Unix, we start the OS thread in a running state, and so we manually have it wait on a condition // variable above. We must perform that waiting BEFORE we select the context! context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 8da75a2d9..50a4e5fbc 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -3,6 +3,7 @@ package thread import "base:intrinsics" +import "base:runtime" import "core:sync" import win32 "core:sys/windows" @@ -39,7 +40,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Windows, the thread is created in a suspended state, and so we can select the context anywhere before the call // to t.procedure(). context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) }