1
- use crate :: mem:: ManuallyDrop ;
1
+ use crate :: mem;
2
2
use crate :: ptr;
3
- use crate :: sync:: atomic:: AtomicPtr ;
4
- use crate :: sync:: atomic:: Ordering :: SeqCst ;
3
+ use crate :: sync:: atomic:: {
4
+ compiler_fence, AtomicPtr , AtomicUsize ,
5
+ Ordering :: { Relaxed , Release } ,
6
+ } ;
5
7
use crate :: sys:: c;
6
8
7
9
pub type Key = c:: DWORD ;
@@ -19,106 +21,93 @@ pub type Dtor = unsafe extern "C" fn(*mut u8);
19
21
// somewhere to run arbitrary code on thread termination. With this in place
20
22
// we'll be able to run anything we like, including all TLS destructors!
21
23
//
22
- // To accomplish this feat, we perform a number of threads, all contained
23
- // within this module:
24
- //
25
- // * All TLS destructors are tracked by *us*, not the windows runtime. This
26
- // means that we have a global list of destructors for each TLS key that
27
- // we know about.
28
- // * When a thread exits, we run over the entire list and run dtors for all
29
- // non-null keys. This attempts to match Unix semantics in this regard.
30
- //
31
- // This ends up having the overhead of using a global list, having some
32
- // locks here and there, and in general just adding some more code bloat. We
33
- // attempt to optimize runtime by forgetting keys that don't have
34
- // destructors, but this only gets us so far.
24
+ // Since the maximum number of keys is 1088 [3] and key values are always lower
25
+ // than 1088 [4], we can just use a static array to store the destructor functions
26
+ // and use the TLS key as index. This avoids all synchronization problems
27
+ // encountered with linked lists or other kinds of storage.
35
28
//
36
29
// For more details and nitty-gritty, see the code sections below!
37
30
//
38
31
// [1]: https://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
39
- // [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
40
- // /threading/thread_local_storage_win.cc#L42
32
+ // [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base/threading/thread_local_storage_win.cc#L42
33
+ // [3]: https://learn.microsoft.com/en-us/windows/win32/procthread/thread-local-storage
34
+ // [4]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlssetvalue
41
35
42
- // -------------------------------------------------------------------------
43
- // Native bindings
44
- //
45
- // This section is just raw bindings to the native functions that Windows
46
- // provides, There's a few extra calls to deal with destructors.
36
+ static DTORS : [ AtomicPtr < ( ) > ; 1088 ] = [ const { AtomicPtr :: new ( ptr :: null_mut ( ) ) } ; 1088 ] ;
37
+ // The highest key that has a destructor associated with it. Used as an
38
+ // optimization so we don't need to iterate over the whole array when there
39
+ // are only a few keys.
40
+ static HIGHEST : AtomicUsize = AtomicUsize :: new ( 0 ) ;
47
41
48
42
#[ inline]
49
43
pub unsafe fn create ( dtor : Option < Dtor > ) -> Key {
50
44
let key = c:: TlsAlloc ( ) ;
51
45
assert ! ( key != c:: TLS_OUT_OF_INDEXES ) ;
52
- if let Some ( f) = dtor {
53
- register_dtor ( key, f) ;
46
+
47
+ if let Some ( dtor) = dtor {
48
+ DTORS [ key as usize ] . store ( mem:: transmute :: < Dtor , * mut ( ) > ( dtor) , Relaxed ) ;
49
+ HIGHEST . fetch_max ( key as usize , Relaxed ) ;
50
+ // If the destructors are run in a signal handler running after this
51
+ // code, we need to guarantee that the changes have been performed
52
+ // before the handler is triggered.
53
+ compiler_fence ( Release ) ;
54
54
}
55
- key
55
+
56
+ // Ensure that the key is always non-null. Since key values are below
57
+ // 1088, this cannot overflow.
58
+ key + 1
56
59
}
57
60
58
61
#[ inline]
59
62
pub unsafe fn set ( key : Key , value : * mut u8 ) {
60
- let r = c:: TlsSetValue ( key, value as c:: LPVOID ) ;
63
+ let r = c:: TlsSetValue ( key - 1 , value as c:: LPVOID ) ;
61
64
debug_assert ! ( r != 0 ) ;
62
65
}
63
66
64
67
#[ inline]
65
68
pub unsafe fn get ( key : Key ) -> * mut u8 {
66
- c:: TlsGetValue ( key) as * mut u8
67
- }
68
-
69
- #[ inline]
70
- pub unsafe fn destroy ( _key : Key ) {
71
- rtabort ! ( "can't destroy tls keys on windows" )
69
+ c:: TlsGetValue ( key - 1 ) as * mut u8
72
70
}
73
71
74
72
#[ inline]
75
- pub fn requires_synchronized_create ( ) -> bool {
76
- true
77
- }
78
-
79
- // -------------------------------------------------------------------------
80
- // Dtor registration
81
- //
82
- // Windows has no native support for running destructors so we manage our own
83
- // list of destructors to keep track of how to destroy keys. We then install a
84
- // callback later to get invoked whenever a thread exits, running all
85
- // appropriate destructors.
86
- //
87
- // Currently unregistration from this list is not supported. A destructor can be
88
- // registered but cannot be unregistered. There's various simplifying reasons
89
- // for doing this, the big ones being:
90
- //
91
- // 1. Currently we don't even support deallocating TLS keys, so normal operation
92
- // doesn't need to deallocate a destructor.
93
- // 2. There is no point in time where we know we can unregister a destructor
94
- // because it could always be getting run by some remote thread.
95
- //
96
- // Typically processes have a statically known set of TLS keys which is pretty
97
- // small, and we'd want to keep this memory alive for the whole process anyway
98
- // really.
99
- //
100
- // Perhaps one day we can fold the `Box` here into a static allocation,
101
- // expanding the `StaticKey` structure to contain not only a slot for the TLS
102
- // key but also a slot for the destructor queue on windows. An optimization for
103
- // another day!
104
-
105
- static DTORS : AtomicPtr < Node > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
106
-
107
- struct Node {
108
- dtor : Dtor ,
109
- key : Key ,
110
- next : * mut Node ,
73
+ pub unsafe fn destroy ( key : Key ) {
74
+ DTORS [ ( key - 1 ) as usize ] . store ( ptr:: null_mut ( ) , Relaxed ) ;
75
+ let r = c:: TlsFree ( key - 1 ) ;
76
+ // Use release ordering for the same reason as above.
77
+ compiler_fence ( Release ) ;
78
+ debug_assert ! ( r != 0 ) ;
111
79
}
112
80
113
- unsafe fn register_dtor ( key : Key , dtor : Dtor ) {
114
- let mut node = ManuallyDrop :: new ( Box :: new ( Node { key, dtor, next : ptr:: null_mut ( ) } ) ) ;
81
+ #[ allow( dead_code) ] // actually called below
82
+ unsafe fn run_dtors ( ) {
83
+ let mut iterations = 5 ;
84
+ while iterations != 0 {
85
+ let mut any_run = false ;
86
+ // All keys have either been created by the current thread or must
87
+ // have been propagated through other means of synchronization, so
88
+ // we can just use relaxed ordering here and still observe all
89
+ // changes relevant to us.
90
+ let highest = HIGHEST . load ( Relaxed ) ;
91
+ for ( index, dtor) in DTORS [ ..highest] . iter ( ) . enumerate ( ) {
92
+ let dtor = mem:: transmute :: < * mut ( ) , Option < Dtor > > ( dtor. load ( Relaxed ) ) ;
93
+ if let Some ( dtor) = dtor {
94
+ let ptr = c:: TlsGetValue ( index as Key ) as * mut u8 ;
95
+ if !ptr. is_null ( ) {
96
+ let r = c:: TlsSetValue ( index as Key , ptr:: null_mut ( ) ) ;
97
+ debug_assert ! ( r != 0 ) ;
98
+
99
+ ( dtor) ( ptr) ;
100
+ any_run = true ;
101
+ }
102
+ }
103
+ }
115
104
116
- let mut head = DTORS . load ( SeqCst ) ;
117
- loop {
118
- node . next = head ;
119
- match DTORS . compare_exchange ( head , & mut * * node , SeqCst , SeqCst ) {
120
- Ok ( _ ) => return , // nothing to drop, we successfully added the node to the list
121
- Err ( cur ) => head = cur ,
105
+ iterations -= 1 ;
106
+ // If no destructors where run, no new keys have been initialized,
107
+ // so we are done. FIXME: Maybe use TLS to store the number of active
108
+ // keys per thread.
109
+ if !any_run {
110
+ return ;
122
111
}
123
112
}
124
113
}
@@ -154,16 +143,6 @@ unsafe fn register_dtor(key: Key, dtor: Dtor) {
154
143
// thread or a process "detaches" (exits). The process part happens for the
155
144
// last thread and the thread part happens for any normal thread.
156
145
//
157
- // # Ok, what's up with running all these destructors?
158
- //
159
- // This will likely need to be improved over time, but this function
160
- // attempts a "poor man's" destructor callback system. Once we've got a list
161
- // of what to run, we iterate over all keys, check their values, and then run
162
- // destructors if the values turn out to be non null (setting them to null just
163
- // beforehand). We do this a few times in a loop to basically match Unix
164
- // semantics. If we don't reach a fixed point after a short while then we just
165
- // inevitably leak something most likely.
166
- //
167
146
// # The article mentions weird stuff about "/INCLUDE"?
168
147
//
169
148
// It sure does! Specifically we're talking about this quote:
@@ -213,26 +192,3 @@ unsafe extern "system" fn on_tls_callback(h: c::LPVOID, dwReason: c::DWORD, pv:
213
192
#[ cfg( not( target_env = "msvc" ) ) ]
214
193
unsafe fn reference_tls_used ( ) { }
215
194
}
216
-
217
- #[ allow( dead_code) ] // actually called above
218
- unsafe fn run_dtors ( ) {
219
- let mut any_run = true ;
220
- for _ in 0 ..5 {
221
- if !any_run {
222
- break ;
223
- }
224
- any_run = false ;
225
- let mut cur = DTORS . load ( SeqCst ) ;
226
- while !cur. is_null ( ) {
227
- let ptr = c:: TlsGetValue ( ( * cur) . key ) ;
228
-
229
- if !ptr. is_null ( ) {
230
- c:: TlsSetValue ( ( * cur) . key , ptr:: null_mut ( ) ) ;
231
- ( ( * cur) . dtor ) ( ptr as * mut _ ) ;
232
- any_run = true ;
233
- }
234
-
235
- cur = ( * cur) . next ;
236
- }
237
- }
238
- }
0 commit comments