1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! Windows-specific Fiber Local Storage (FLS) implementation for thread attachment tracking.
//!
//! This module provides a Windows-specific alternative to thread-local storage that uses Fiber
//! Local Storage (FLS) callbacks. The key advantage is that FLS callbacks run without holding the
//! Windows loader lock, avoiding potential deadlocks.
//!
//! For example see: <https://github.com/jni-rs/jni-rs/issues/701>
//!
//! On Windows, this sequence of events can trigger a deadlock:
//!
//! 1. A native thread, previously attached to a JVM, is stopped
//! 2. Windows kernel calls the TLS destructor - This happens while [holding Windows Loader
//! Lock](https://doc.rust-lang.org/stable/std/thread/struct.LocalKey.html#synchronization-in-thread-local-destructors)
//! 3. jni-rs `InternalAttachGuard` calls `DetachCurrentThread`. This involves a thread state
//! transition from Native to VM
//! 4. Concurrently, there is a new Java thread being started by some other thread
//! 5. During the Native -> VM transition, the native thread is trapped in a JVM safepoint. While
//! holding the loader lock!
//! 6. The Java thread created at step no. 4 is already runnable from JVM's point of view, and the
//! JVM expects this thread to arrive at the safepoint. But Windows won't execute user code on
//! this thread until it manages to acquire the loader lock. Which is held by the Rust thread
//! that is trapped at safepoint. This means the Java thread cannot make any progress and
//! certainly cannot reach the safepoint. JVM won't release the native thread until all threads
//! are at safepoint -> Deadlock
//!
//! Design Notes:
//! - The goal is not to try and support fibers in a general-purpose way and in fact we don't expect
//! any application using Rust + JNI to be scheduling multiple fibers on the same thread.
//! - At the very-least, if an application does schedule fibers then we're assuming they are never
//! preempting Rust code that is using JNI.
//! - No attempt is made to support fiber switching while there are active `AttachGuard`s.
//! - Trying to support this would raise all kinds of state tracking / safety issues, like, what
//! happens to any pushed local frames, or pending exceptions, etc.
//! - No attempt is made support the freeing of fibers while there are active `AttachGuard`s for the
//! current thread.
//! - This could effectively rug pull the thread's attachment state while JNI is in use by a
//! different fiber context.
//!
//! Consistent with other platforms, we assume that no external code (e.g., other JNI language
//! bindings may spontaneously detach the current thread while there are active `AttachGuard`s. This
//! isn't something we can technically enforce, but we document it as a safety invariant. It would
//! be totally impractical to try and use JNI without being able to make this assumption and
//! probably impossible if code could be arbitrarily preempted by fiber switches (since you wouldn't
//! ever know if your per-thread env pointer is still valid)
//!
//! As with TLS based attachment tracking we do want to be resilient to external code manually
//! detaching threads when there are no active `AttachGuard`s, so attach_current_thread() will
//! always check the real JNI attachment state if there are no active guards.
use ;
use ;
use crate::;
use crate;
/// The FLS index used to store the attachment guard data.
/// This is allocated once when the first thread attaches.
static FLS_INDEX: = new;
/// Data stored in FLS for each attached fiber.
///
/// Note: We don't store the env pointer because:
/// 1. Multiple fibers on the same thread share the same env pointer
/// 2. After the first fiber detaches, the env pointer becomes invalid
/// 3. Even with a single fiber, it's possible for external code to manually detach the thread,
/// making the stored env pointer invalid.
/// FLS callback that runs when a fiber terminates.
///
/// SAFETY: This callback is invoked by Windows when:
/// 1. A fiber terminates or is deleted
/// 2. The FLS slot is freed (via FlsFree)
///
/// When this callback runs, it detaches the current thread from the JVM.
///
/// In the unlikely event that multiple fibers have attached the same thread (e.g., if
/// DetachCurrentThread was called manually by external code), each fiber's termination
/// will attempt to detach the thread, but subsequent detach calls are harmless no-ops.
///
/// # Panics
///
/// This function will panic if called while there are active `AttachGuard` instances
/// (`thread_attach_guard_level() > 0`). Fiber switching or termination must not occur
/// while an `AttachGuard` is in scope - this is a critical safety invariant.
unsafe extern "system"
/// Initialize FLS if not already initialized.
///
/// Returns the FLS index, allocating it if necessary.
/// Attach the current fiber using FLS for cleanup tracking.
///
/// This attaches the current thread and stores attachment data in FLS so that when the fiber
/// terminates, the FLS callback will automatically detach the thread from the JVM (without holding
/// the loader lock).
///
/// Note: This function assumes that the caller has already used GetEnv to check if the thread is
/// already attached, and only calls this function if it is not attached.
///
/// Typically only one fiber per thread will call this function because subsequent fibers on the
/// same thread will find the thread already attached.
///
/// Note: Although it is unlikely, it's possible that this can be called from multiple fibers on the
/// the same thread, if DetachCurrentThread was called manually by external code (we allow this as
/// long as there are no active AttachGuards). In this case we allow the ATTACHED_THREADS counter to
/// be incremented for each fiber with its own FLS data so the number can technically diverge from
/// the real _thread_ attachment count. This is acceptable since the number is only used for
/// debugging and unit tests. In fact, on Windows the unit tests will test for this scenario.
///
/// If we do end up with multiple fibers having FLS data then each fiber's termination will attempt to
/// detach the thread, but subsequent detach calls are harmless no-ops. They will also decrement the
/// ATTACHED_THREADS counter accordingly so the logical count remains correct.
///
/// # Panics
///
/// This function will panic if called while there are active `AttachGuard`s.
pub unsafe
/// Explicitly detach the current fiber if attached via FLS.
///
/// This clears the FLS slot without invoking the callback, since we're detaching manually.
/// If the thread is attached via JNI, it will be detached.
///
/// # Panics
///
/// This function will panic if called while there are active `AttachGuard` instances.
/// Fiber detachment/switching must not occur while an `AttachGuard` is in scope.
pub