From fcbe2e6ce77207e4d35b31cade9d6f1ecef55b5f Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sun, 6 Jul 2025 22:42:33 +0800 Subject: [PATCH] sync: add thread local storage (TLS) support (#24849) --- vlib/sync/thread_windows.c.v | 2 + vlib/sync/tls.v | 111 ++++++++++++++ vlib/sync/tls_default.c.v | 62 ++++++++ vlib/sync/tls_test.v | 284 +++++++++++++++++++++++++++++++++++ vlib/sync/tls_windows.c.v | 62 ++++++++ 5 files changed, 521 insertions(+) create mode 100644 vlib/sync/tls.v create mode 100644 vlib/sync/tls_default.c.v create mode 100644 vlib/sync/tls_test.v create mode 100644 vlib/sync/tls_windows.c.v diff --git a/vlib/sync/thread_windows.c.v b/vlib/sync/thread_windows.c.v index 33241d5a0f..8e9135215e 100644 --- a/vlib/sync/thread_windows.c.v +++ b/vlib/sync/thread_windows.c.v @@ -1,7 +1,9 @@ module sync +// Get current thread ID from Windows API (via C interface) fn C.GetCurrentThreadId() u32 +// thread_id returns a unique identifier for the caller thread. pub fn thread_id() u64 { return u64(C.GetCurrentThreadId()) } diff --git a/vlib/sync/tls.v b/vlib/sync/tls.v new file mode 100644 index 0000000000..ab151cd4c7 --- /dev/null +++ b/vlib/sync/tls.v @@ -0,0 +1,111 @@ +module sync + +// ThreadLocalStorage provides thread-local storage for values of type T +@[noinit] +struct ThreadLocalStorage[T] { +mut: + key u64 // TLS key identifier. Note: While Linux uses unsigned int, Darwin requires unsigned long. u64 accommodates both. + in_use bool // Allocation status flag +} + +// DataConversion convert voidptr to/from type T +union DataConversion { +mut: + f_u8 u8 + f_u16 u16 + f_u32 u32 + f_u64 u64 + + f_i8 i8 + f_i16 i16 + f_i32 i32 + f_i64 i64 + + f_voidptr voidptr + f_isize isize + f_usize usize + f_int int + f_f32 f32 + f_f64 f64 + f_rune rune +} + +// convert_t_to_voidptr convert value from type T to voidptr +@[inline] +fn convert_t_to_voidptr[T](value T) !voidptr { + mut f := DataConversion{} + $if T is i8 { + f.f_i8 = value + } $else $if T is i16 { + f.f_i16 = value + } $else $if T is i32 { + f.f_i32 = value + } $else $if T is i64 { + f.f_i64 = value + } $else $if T is u8 { + f.f_u8 = value + } $else $if T is u16 { + f.f_u16 = value + } $else $if T is u32 { + f.f_u32 = value + } $else $if T is u64 { + f.f_u64 = value + } $else $if T is $pointer { + f.f_voidptr = voidptr(value) + } $else $if T is isize { + f.f_isize = value + } $else $if T is usize { + f.f_usize = value + } $else $if T is int { + f.f_int = value + } $else $if T is f32 { + f.f_f32 = value + } $else $if T is f64 { + f.f_f64 = value + } $else $if T is rune { + f.f_rune = value + } $else { + return error('Unsupported data type `${T.name}` to voidptr') + } + return unsafe { f.f_voidptr } +} + +// convert_voidptr_to_t convert value from voidptr to type T +@[inline] +fn convert_voidptr_to_t[T](value voidptr) !T { + f := DataConversion{ + f_voidptr: value + } + $if T is i8 { + return unsafe { f.f_i8 } + } $else $if T is i16 { + return unsafe { f.f_i16 } + } $else $if T is i32 { + return unsafe { f.f_i32 } + } $else $if T is i64 { + return unsafe { f.f_i64 } + } $else $if T is u8 { + return unsafe { f.f_u8 } + } $else $if T is u16 { + return unsafe { f.f_u16 } + } $else $if T is u32 { + return unsafe { f.f_u32 } + } $else $if T is u64 { + return unsafe { f.f_u64 } + } $else $if T is $pointer { + return unsafe { f.f_voidptr } + } $else $if T is isize { + return unsafe { f.f_isize } + } $else $if T is usize { + return unsafe { f.f_usize } + } $else $if T is int { + return unsafe { f.f_int } + } $else $if T is f32 { + return unsafe { f.f_f32 } + } $else $if T is f64 { + return unsafe { f.f_f64 } + } $else $if T is rune { + return unsafe { f.f_rune } + } + return error('Unsupported data type `${T.name}` from voidptr') +} diff --git a/vlib/sync/tls_default.c.v b/vlib/sync/tls_default.c.v new file mode 100644 index 0000000000..da1ea0bd44 --- /dev/null +++ b/vlib/sync/tls_default.c.v @@ -0,0 +1,62 @@ +module sync + +// Pthread TLS API functions (via C interface) +fn C.pthread_key_create(key voidptr, voidptr) int +fn C.pthread_key_delete(key u64) int +fn C.pthread_setspecific(key u64, const_ptr voidptr) int +fn C.pthread_getspecific(key u64) voidptr + +// new_tls creates new TLS storage initialized with the given `value` +pub fn new_tls[T](value T) !ThreadLocalStorage[T] { + $if T !in [i8, i16, i32, i64, u8, u16, u32, u64, isize, usize, f32, f64, rune, int, voidptr, + $pointer] { + return error('new_tls: invalid type ${T.name}') + } + mut key := u64(0) + // Validate key allocation + if C.pthread_key_create(voidptr(&key), 0) == 0 { + // Initialize storage and verify success + if C.pthread_setspecific(key, convert_t_to_voidptr(value)!) == 0 { + return ThreadLocalStorage[T]{ + key: key + in_use: true + } + } else { + return error('new_tls: Failed to initialize TLS value: ${value}') + } + } + + // Handle allocation failure + return error('new_tls: Failed to allocate TLS index') +} + +// set updates the `value` in TLS storage. +@[inline] +pub fn (mut t ThreadLocalStorage[T]) set(value T) ! { + if t.in_use { + if C.pthread_setspecific(t.key, convert_t_to_voidptr(value)!) != 0 { + return error('set: Failed to update TLS value: ${value}') + } + } else { + return error('set: TLS storage is already destroyed') + } +} + +// get retrieves the current value from TLS storage. +@[inline] +pub fn (mut t ThreadLocalStorage[T]) get() !T { + if t.in_use { + return convert_voidptr_to_t[T](C.pthread_getspecific(t.key)) + } + return error('get: TLS storage is already destroyed') +} + +// destroy releases TLS resources (must be called manually). +@[inline] +pub fn (mut t ThreadLocalStorage[T]) destroy() ! { + if C.pthread_key_delete(t.key) == 0 { + t.in_use = false + } else { + return error('destroy: Failed to release TLS resources') + } +} diff --git a/vlib/sync/tls_test.v b/vlib/sync/tls_test.v new file mode 100644 index 0000000000..d7aea89afd --- /dev/null +++ b/vlib/sync/tls_test.v @@ -0,0 +1,284 @@ +import sync +import time + +// Testing basic data types +fn test_basic_tls() { + println('--- Testing basic datatypes TLS ---') + + // invalid type + sync.new_tls[string]('hello world') or { assert err.msg().contains('invalid type') } + + mut tls_i8 := sync.new_tls[i8](-3)! + assert tls_i8.get()! == -3 + tls_i8.set(-4)! + assert tls_i8.get()! == -4 + tls_i8.destroy()! + if v := tls_i8.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_i8.set(-5) or { assert err.msg().contains('already destroyed') } + + mut tls_i16 := sync.new_tls[i16](-57)! + assert tls_i16.get()! == -57 + tls_i16.set(-58)! + assert tls_i16.get()! == -58 + tls_i16.destroy()! + if v := tls_i16.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_i16.set(-59) or { assert err.msg().contains('already destroyed') } + + mut tls_i32 := sync.new_tls[i32](-570)! + assert tls_i32.get()! == -570 + tls_i32.set(-580)! + assert tls_i32.get()! == -580 + tls_i32.destroy()! + if v := tls_i32.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_i32.set(-59) or { assert err.msg().contains('already destroyed') } + + mut tls_i64 := sync.new_tls[i64](-5700)! + assert tls_i64.get()! == -5700 + tls_i64.set(-5800)! + assert tls_i64.get()! == -5800 + tls_i64.destroy()! + if v := tls_i64.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_i64.set(-5900) or { assert err.msg().contains('already destroyed') } + + mut tls_u8 := sync.new_tls[u8](3)! + assert tls_u8.get()! == 3 + tls_u8.set(4)! + assert tls_u8.get()! == 4 + tls_u8.destroy()! + if v := tls_u8.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_u8.set(5) or { assert err.msg().contains('already destroyed') } + + mut tls_u16 := sync.new_tls[i16](57)! + assert tls_u16.get()! == 57 + tls_u16.set(58)! + assert tls_u16.get()! == 58 + tls_u16.destroy()! + if v := tls_u16.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_u16.set(59) or { assert err.msg().contains('already destroyed') } + + mut tls_u32 := sync.new_tls[u32](570)! + assert tls_u32.get()! == 570 + tls_u32.set(580)! + assert tls_u32.get()! == 580 + tls_u32.destroy()! + if v := tls_u32.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_u32.set(590) or { assert err.msg().contains('already destroyed') } + + mut tls_u64 := sync.new_tls[u64](5700)! + assert tls_u64.get()! == 5700 + tls_u64.set(5800)! + assert tls_u64.get()! == 5800 + tls_u64.destroy()! + if v := tls_u64.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_u64.set(5900) or { assert err.msg().contains('already destroyed') } + + mut tls_isize := sync.new_tls[isize](-57000)! + assert tls_isize.get()! == -57000 + tls_isize.set(-58000)! + assert tls_isize.get()! == -58000 + tls_isize.destroy()! + if v := tls_isize.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_isize.set(-59000) or { assert err.msg().contains('already destroyed') } + + mut tls_usize := sync.new_tls[usize](57000)! + assert tls_usize.get()! == 57000 + tls_usize.set(58000)! + assert tls_usize.get()! == 58000 + tls_usize.destroy()! + if v := tls_usize.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_usize.set(59000) or { assert err.msg().contains('already destroyed') } + + mut tls_int := sync.new_tls[int](-32767)! + assert tls_int.get()! == -32767 + tls_int.set(-32768)! + assert tls_int.get()! == -32768 + tls_int.destroy()! + if v := tls_int.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_int.set(-32769) or { assert err.msg().contains('already destroyed') } + + mut tls_f32 := sync.new_tls[f32](1.5)! + assert tls_f32.get()! == 1.5 + tls_f32.set(2.5)! + assert tls_f32.get()! == 2.5 + tls_f32.destroy()! + if v := tls_f32.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_f32.set(3.5) or { assert err.msg().contains('already destroyed') } + + mut tls_f64 := sync.new_tls[f64](-1.5)! + assert tls_f64.get()! == -1.5 + tls_f64.set(-2.5)! + assert tls_f64.get()! == -2.5 + tls_f64.destroy()! + if v := tls_f64.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } + tls_f64.set(-3.5) or { assert err.msg().contains('already destroyed') } +} + +// Testing pointer type +struct PtrData { + id int + val string +} + +fn test_pointer_tls() { + println('--- Testing pointer TLS ---') + + data := &PtrData{ + id: 100 + val: 'initial' + } + mut tls := sync.new_tls[&PtrData](data) or { panic('Failed to create TLS: ${err}') } + + // Verify initial value + assert tls.get()!.id == 100 + assert tls.get()!.val == 'initial' + + // Update and verify new value + new_data := &PtrData{ + id: 200 + val: 'updated' + } + tls.set(new_data)! + assert tls.get()!.id == 200 + assert tls.get()!.val == 'updated' + + tls.destroy()! + // Attempt to set value after destruction + tls.set(data) or { assert err.msg().contains('already destroyed') } + + // Attempt to get value after destruction + if res_get := tls.get() { + assert false + } else { + assert err.msg().contains('already destroyed') + } +} + +// Thread worker function +fn thread_worker(tls &sync.ThreadLocalStorage[u64], thread_id u64) { + mut mut_tls := unsafe { tls } + // Each thread sets a unique value + mut_tls.set(thread_id * 100) or { panic(err) } + + // Simulate work with a delay + time.sleep(10 * time.millisecond) + + // Retrieve and verify thread-private value + val := mut_tls.get() or { panic(err) } + assert val == thread_id * 100 + println('Thread ${thread_id}: TLS value = ${val}') +} + +// Testing multi-thread isolation +fn test_thread_isolation() { + println('--- Testing multi-thread isolation ---') + + // Create shared TLS instance + mut tls := sync.new_tls[u64](3366) or { panic(err) } + defer { tls.destroy() or { panic(err) } } + + // Create multiple threads + mut threads := []thread{} + for i in 1 .. 5 { + threads << spawn thread_worker(&tls, u64(i)) + } + + // Wait for all threads to complete + threads.wait() + + // Verify main thread value remains unchanged + println('Main thread value after operations: ${tls.get()!}') + assert tls.get()! == 3366 +} + +// Thread worker function +fn worker(int_tls &sync.ThreadLocalStorage[int], + f64_tls &sync.ThreadLocalStorage[f64]) { + mut mut_int_tls := unsafe { int_tls } + mut mut_f64_tls := unsafe { f64_tls } + // Update integer value + mut_int_tls.set(100) or { panic(err) } + v := mut_int_tls.get() or { panic(err) } + assert v == 100 + + // Update floating-point value + mut_f64_tls.set(2.5) or { panic(err) } + s := mut_f64_tls.get() or { panic(err) } + assert s == 2.5 +} + +// Testing cross-thread type safety +fn test_cross_thread_type() { + println('--- Testing cross-thread type safety ---') + + mut int_tls := sync.new_tls[int](42) or { panic(err) } + defer { int_tls.destroy() or { panic(err) } } + + mut f64_tls := sync.new_tls[f64](1.5) or { panic(err) } + defer { f64_tls.destroy() or { panic(err) } } + + shared_int_tls := &int_tls + shared_f64_tls := &f64_tls + + // Create and wait for threads + threads := [ + spawn worker(shared_int_tls, shared_f64_tls), + spawn worker(shared_int_tls, shared_f64_tls), + ] + threads.wait() + + // Verify original values in main thread + assert int_tls.get()! == 42 + assert f64_tls.get()! == 1.5 +} diff --git a/vlib/sync/tls_windows.c.v b/vlib/sync/tls_windows.c.v new file mode 100644 index 0000000000..6b7efc6f41 --- /dev/null +++ b/vlib/sync/tls_windows.c.v @@ -0,0 +1,62 @@ +module sync + +// Windows TLS API functions (via C interface) +fn C.TlsAlloc() u32 +fn C.TlsSetValue(key u32, voidptr) bool +fn C.TlsGetValue(key u32) voidptr +fn C.TlsFree(key u32) bool + +// new_tls creates new TLS storage initialized with the given `value` +pub fn new_tls[T](value T) !ThreadLocalStorage[T] { + $if T !in [i8, i16, i32, i64, u8, u16, u32, u64, isize, usize, f32, f64, rune, int, voidptr, + $pointer] { + return error('new_tls: invalid type ${T.name}') + } + + key := C.TlsAlloc() + // Validate key allocation + if key != C.TLS_OUT_OF_INDEXES { + // Initialize storage and verify success + if C.TlsSetValue(key, convert_t_to_voidptr(value)!) { + return ThreadLocalStorage[T]{ + key: key + in_use: true + } + } else { + return error('new_tls: Failed to initialize TLS value: ${value}') + } + } + // Handle allocation failure + return error('new_tls: Failed to allocate TLS index') +} + +// set updates the `value` in TLS storage. +@[inline] +pub fn (mut t ThreadLocalStorage[T]) set(value T) ! { + if t.in_use { + if !C.TlsSetValue(t.key, convert_t_to_voidptr(value)!) { + return error('set: Failed to update TLS value: ${value}') + } + } else { + return error('set: TLS storage is already destroyed') + } +} + +// get retrieves the current value from TLS storage. +@[inline] +pub fn (mut t ThreadLocalStorage[T]) get() !T { + if t.in_use { + return convert_voidptr_to_t[T](C.TlsGetValue(t.key)) + } + return error('get: TLS storage is already destroyed') +} + +// destroy releases TLS resources (must be called manually). +@[inline] +pub fn (mut t ThreadLocalStorage[T]) destroy() ! { + if C.TlsFree(t.key) { + t.in_use = false + } else { + return error('destroy: Failed to release TLS resources') + } +}