mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
sync: add thread local storage (TLS) support (#24849)
This commit is contained in:
parent
952f63ef43
commit
fcbe2e6ce7
@ -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())
|
||||
}
|
||||
|
111
vlib/sync/tls.v
Normal file
111
vlib/sync/tls.v
Normal file
@ -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')
|
||||
}
|
62
vlib/sync/tls_default.c.v
Normal file
62
vlib/sync/tls_default.c.v
Normal file
@ -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')
|
||||
}
|
||||
}
|
284
vlib/sync/tls_test.v
Normal file
284
vlib/sync/tls_test.v
Normal file
@ -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
|
||||
}
|
62
vlib/sync/tls_windows.c.v
Normal file
62
vlib/sync/tls_windows.c.v
Normal file
@ -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')
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user