Browse Source

Add API for freeing `thread_local` state

Feoramund 11 months ago
parent
commit
d338642dc4

+ 34 - 0
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()
+	}
+}

+ 5 - 1
core/thread/thread_unix.odin

@@ -2,6 +2,7 @@
 // +private
 // +private
 package thread
 package thread
 
 
+import "base:runtime"
 import "core:sync"
 import "core:sync"
 import "core:sys/unix"
 import "core:sys/unix"
 import "core:time"
 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
 			// 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!
 			// variable above. We must perform that waiting BEFORE we select the context!
 			context = _select_context_for_thread(init_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)
 			t.procedure(t)
 		}
 		}

+ 5 - 1
core/thread/thread_windows.odin

@@ -3,6 +3,7 @@
 package thread
 package thread
 
 
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:runtime"
 import "core:sync"
 import "core:sync"
 import win32 "core:sys/windows"
 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
 			// 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().
 			// to t.procedure().
 			context = _select_context_for_thread(init_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)
 			t.procedure(t)
 		}
 		}