Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5eed05f

Browse files
authoredFeb 21, 2025··
improve error handling (#116)
* improve error handling see #115 See sqlpage/SQLPage#814 * improve error message in destroy_pthread_attr * remove code duplication * cleanup code and add comments * Make error handling code easier to read * update formatting https://github.com/rust-lang/stacker/pull/116/files#r1961535064 * put the beautiful unicode apostrophe back https://github.com/rust-lang/stacker/pull/116/files#r1961536979 * use one file per guess_os_stack_limit implementation * fix error handling on openbsd too * beautiful unicode apostrophe * format * fix windows build * remove unused unsafe * fix error handling on windows VirtualQuery is a faillible function that was treated as infaillible https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualquery * SetThreadStackGuarantee can also error on windows * simplify pthread error handling
1 parent a4027ca commit 5eed05f

File tree

7 files changed

+263
-196
lines changed

7 files changed

+263
-196
lines changed
 

‎src/backends/fallback.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[inline(always)]
2+
pub unsafe fn guess_os_stack_limit() -> Option<usize> {
3+
None
4+
}

‎src/backends/macos.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub unsafe fn guess_os_stack_limit() -> Option<usize> {
2+
Some(
3+
libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize
4+
- libc::pthread_get_stacksize_np(libc::pthread_self()) as usize,
5+
)
6+
}

‎src/backends/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cfg_if! {
2+
if #[cfg(miri)] {
3+
mod fallback;
4+
pub use fallback::guess_os_stack_limit;
5+
} else if #[cfg(windows)] {
6+
pub(crate) mod windows;
7+
pub use windows::guess_os_stack_limit;
8+
} else if #[cfg(any(
9+
target_os = "linux",
10+
target_os = "solaris",
11+
target_os = "netbsd",
12+
target_os = "freebsd",
13+
target_os = "dragonfly",
14+
target_os = "illumos"
15+
))] {
16+
mod unix;
17+
pub use unix::guess_os_stack_limit;
18+
} else if #[cfg(target_os = "openbsd")] {
19+
mod openbsd;
20+
pub use openbsd::guess_os_stack_limit;
21+
} else if #[cfg(target_os = "macos")] {
22+
mod macos;
23+
pub use macos::guess_os_stack_limit;
24+
} else {
25+
mod fallback;
26+
pub use fallback::guess_os_stack_limit;
27+
}
28+
}

‎src/backends/openbsd.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub unsafe fn guess_os_stack_limit() -> Option<usize> {
2+
let mut stackinfo = std::mem::MaybeUninit::<libc::stack_t>::uninit();
3+
let res = libc::pthread_stackseg_np(libc::pthread_self(), stackinfo.as_mut_ptr());
4+
if res != 0 {
5+
return None;
6+
}
7+
Some(stackinfo.assume_init().ss_sp as usize - stackinfo.assume_init().ss_size)
8+
}

‎src/backends/unix.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "illumos"))]
2+
use libc::pthread_attr_get_np as get_attr;
3+
#[cfg(any(target_os = "linux", target_os = "solaris", target_os = "netbsd"))]
4+
use libc::pthread_getattr_np as get_attr;
5+
6+
pub unsafe fn guess_os_stack_limit() -> Option<usize> {
7+
let mut attr = PthreadAttr::new()?;
8+
9+
handle_pthread_err(get_attr(libc::pthread_self(), attr.as_mut_ptr()))?;
10+
11+
let mut stackaddr = std::ptr::null_mut();
12+
let mut stacksize = 0;
13+
handle_pthread_err(libc::pthread_attr_getstack(
14+
attr.as_mut_ptr(),
15+
&mut stackaddr,
16+
&mut stacksize,
17+
))?;
18+
19+
Some(stackaddr as usize)
20+
}
21+
22+
struct PthreadAttr(std::mem::MaybeUninit<libc::pthread_attr_t>);
23+
24+
impl Drop for PthreadAttr {
25+
fn drop(&mut self) {
26+
unsafe {
27+
let ret = libc::pthread_attr_destroy(self.0.as_mut_ptr());
28+
if ret != 0 {
29+
let err = std::io::Error::last_os_error();
30+
panic!(
31+
"pthread_attr_destroy failed with error code {}: {}",
32+
ret, err
33+
);
34+
}
35+
}
36+
}
37+
}
38+
39+
fn handle_pthread_err(ret: libc::c_int) -> Option<()> {
40+
if ret != 0 {
41+
return None;
42+
}
43+
Some(())
44+
}
45+
46+
impl PthreadAttr {
47+
unsafe fn new() -> Option<Self> {
48+
let mut attr = std::mem::MaybeUninit::<libc::pthread_attr_t>::uninit();
49+
if libc::pthread_attr_init(attr.as_mut_ptr()) != 0 {
50+
return None;
51+
}
52+
Some(PthreadAttr(attr))
53+
}
54+
55+
fn as_mut_ptr(&mut self) -> *mut libc::pthread_attr_t {
56+
self.0.as_mut_ptr()
57+
}
58+
}

‎src/backends/windows.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use libc::c_void;
2+
use std::io;
3+
use std::ptr;
4+
use windows_sys::Win32::Foundation::BOOL;
5+
use windows_sys::Win32::System::Memory::VirtualQuery;
6+
use windows_sys::Win32::System::Threading::{
7+
ConvertFiberToThread, ConvertThreadToFiber, CreateFiber, DeleteFiber, IsThreadAFiber,
8+
SetThreadStackGuarantee, SwitchToFiber,
9+
};
10+
11+
// Make sure the libstacker.a (implemented in C) is linked.
12+
// See https://github.com/rust-lang/rust/issues/65610
13+
#[link(name = "stacker")]
14+
extern "C" {
15+
fn __stacker_get_current_fiber() -> *mut c_void;
16+
}
17+
18+
struct FiberInfo<F> {
19+
callback: std::mem::MaybeUninit<F>,
20+
panic: Option<Box<dyn std::any::Any + Send + 'static>>,
21+
parent_fiber: *mut c_void,
22+
}
23+
24+
unsafe extern "system" fn fiber_proc<F: FnOnce()>(data: *mut c_void) {
25+
// This function is the entry point to our inner fiber, and as argument we get an
26+
// instance of `FiberInfo`. We will set-up the "runtime" for the callback and execute
27+
// it.
28+
let data = &mut *(data as *mut FiberInfo<F>);
29+
let old_stack_limit = crate::get_stack_limit();
30+
crate::set_stack_limit(guess_os_stack_limit());
31+
let callback = data.callback.as_ptr();
32+
data.panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(callback.read())).err();
33+
34+
// Restore to the previous Fiber
35+
crate::set_stack_limit(old_stack_limit);
36+
SwitchToFiber(data.parent_fiber);
37+
}
38+
39+
pub fn _grow(stack_size: usize, callback: &mut dyn FnMut()) {
40+
// Fibers (or stackful coroutines) is the only official way to create new stacks on the
41+
// same thread on Windows. So in order to extend the stack we create fiber and switch
42+
// to it so we can use it's stack. After running `callback` within our fiber, we switch
43+
// back to the current stack and destroy the fiber and its associated stack.
44+
unsafe {
45+
let was_fiber = IsThreadAFiber() == 1 as BOOL;
46+
let mut data = FiberInfo {
47+
callback: std::mem::MaybeUninit::new(callback),
48+
panic: None,
49+
parent_fiber: {
50+
if was_fiber {
51+
// Get a handle to the current fiber. We need to use a C implementation
52+
// for this as GetCurrentFiber is an header only function.
53+
__stacker_get_current_fiber()
54+
} else {
55+
// Convert the current thread to a fiber, so we are able to switch back
56+
// to the current stack. Threads coverted to fibers still act like
57+
// regular threads, but they have associated fiber data. We later
58+
// convert it back to a regular thread and free the fiber data.
59+
ConvertThreadToFiber(ptr::null_mut())
60+
}
61+
},
62+
};
63+
64+
if data.parent_fiber.is_null() {
65+
panic!(
66+
"unable to convert thread to fiber: {}",
67+
io::Error::last_os_error()
68+
);
69+
}
70+
71+
let fiber = CreateFiber(
72+
stack_size as usize,
73+
Some(fiber_proc::<&mut dyn FnMut()>),
74+
&mut data as *mut FiberInfo<&mut dyn FnMut()> as *mut _,
75+
);
76+
if fiber.is_null() {
77+
panic!("unable to allocate fiber: {}", io::Error::last_os_error());
78+
}
79+
80+
// Switch to the fiber we created. This changes stacks and starts executing
81+
// fiber_proc on it. fiber_proc will run `callback` and then switch back to run the
82+
// next statement.
83+
SwitchToFiber(fiber);
84+
DeleteFiber(fiber);
85+
86+
// Clean-up.
87+
if !was_fiber && ConvertFiberToThread() == 0 {
88+
// FIXME: Perhaps should not panic here?
89+
panic!(
90+
"unable to convert back to thread: {}",
91+
io::Error::last_os_error()
92+
);
93+
}
94+
95+
if let Some(p) = data.panic {
96+
std::panic::resume_unwind(p);
97+
}
98+
}
99+
}
100+
101+
#[inline(always)]
102+
fn get_thread_stack_guarantee() -> Option<usize> {
103+
let min_guarantee = if cfg!(target_pointer_width = "32") {
104+
0x1000
105+
} else {
106+
0x2000
107+
};
108+
let mut stack_guarantee = 0;
109+
unsafe {
110+
// Read the current thread stack guarantee
111+
// This is the stack reserved for stack overflow
112+
// exception handling.
113+
// This doesn't return the true value so we need
114+
// some further logic to calculate the real stack
115+
// guarantee. This logic is what is used on x86-32 and
116+
// x86-64 Windows 10. Other versions and platforms may differ
117+
let ret = SetThreadStackGuarantee(&mut stack_guarantee);
118+
if ret == 0 {
119+
return None;
120+
}
121+
};
122+
Some(std::cmp::max(stack_guarantee, min_guarantee) as usize + 0x1000)
123+
}
124+
125+
#[inline(always)]
126+
pub unsafe fn guess_os_stack_limit() -> Option<usize> {
127+
// Query the allocation which contains our stack pointer in order
128+
// to discover the size of the stack
129+
//
130+
// FIXME: we could read stack base from the TIB, specifically the 3rd element of it.
131+
type QueryT = windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION;
132+
let mut mi = std::mem::MaybeUninit::<QueryT>::uninit();
133+
let res = VirtualQuery(
134+
psm::stack_pointer() as *const _,
135+
mi.as_mut_ptr(),
136+
std::mem::size_of::<QueryT>() as usize,
137+
);
138+
if res == 0 {
139+
return None;
140+
}
141+
Some(mi.assume_init().AllocationBase as usize + get_thread_stack_guarantee()? + 0x1000)
142+
}

‎src/lib.rs

Lines changed: 17 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ extern crate windows_sys;
3232
#[macro_use]
3333
extern crate psm;
3434

35+
mod backends;
36+
3537
use std::cell::Cell;
3638

3739
/// Grows the call stack if necessary.
@@ -116,7 +118,7 @@ psm_stack_information!(
116118

117119
thread_local! {
118120
static STACK_LIMIT: Cell<Option<usize>> = Cell::new(unsafe {
119-
guess_os_stack_limit()
121+
backends::guess_os_stack_limit()
120122
})
121123
}
122124

@@ -163,10 +165,12 @@ psm_stack_manipulation! {
163165
-1, // Some implementations assert fd = -1 if MAP_ANON is specified
164166
0
165167
);
166-
if new_stack == libc::MAP_FAILED {
167-
let error = std::io::Error::last_os_error();
168-
panic!("allocating stack failed with: {}", error)
169-
}
168+
assert_ne!(
169+
new_stack,
170+
libc::MAP_FAILED,
171+
"mmap failed to allocate stack: {}",
172+
std::io::Error::last_os_error()
173+
);
170174
let guard = StackRestoreGuard {
171175
new_stack,
172176
stack_bytes,
@@ -191,11 +195,12 @@ psm_stack_manipulation! {
191195
} else {
192196
-1
193197
};
194-
if result == -1 {
195-
let error = std::io::Error::last_os_error();
196-
drop(guard);
197-
panic!("setting stack permissions failed with: {}", error)
198-
}
198+
assert_ne!(
199+
result,
200+
-1,
201+
"mprotect/mmap failed: {}",
202+
std::io::Error::last_os_error()
203+
);
199204
guard
200205
}
201206
}
@@ -271,191 +276,7 @@ psm_stack_manipulation! {
271276
let _ = stack_size;
272277
callback();
273278
}
274-
}
275-
}
276-
277-
cfg_if! {
278-
if #[cfg(miri)] {
279-
// Miri doesn't have a stack limit
280-
#[inline(always)]
281-
unsafe fn guess_os_stack_limit() -> Option<usize> {
282-
None
283-
}
284-
} else if #[cfg(windows)] {
285-
use std::ptr;
286-
use std::io;
287-
use libc::c_void;
288-
use windows_sys::Win32::System::Threading::{SwitchToFiber, IsThreadAFiber, ConvertThreadToFiber,
289-
CreateFiber, DeleteFiber, ConvertFiberToThread, SetThreadStackGuarantee
290-
};
291-
use windows_sys::Win32::Foundation::BOOL;
292-
use windows_sys::Win32::System::Memory::VirtualQuery;
293-
294-
// Make sure the libstacker.a (implemented in C) is linked.
295-
// See https://github.com/rust-lang/rust/issues/65610
296-
#[link(name="stacker")]
297-
extern {
298-
fn __stacker_get_current_fiber() -> *mut c_void;
299-
}
300-
301-
struct FiberInfo<F> {
302-
callback: std::mem::MaybeUninit<F>,
303-
panic: Option<Box<dyn std::any::Any + Send + 'static>>,
304-
parent_fiber: *mut c_void,
305-
}
306-
307-
unsafe extern "system" fn fiber_proc<F: FnOnce()>(data: *mut c_void) {
308-
// This function is the entry point to our inner fiber, and as argument we get an
309-
// instance of `FiberInfo`. We will set-up the "runtime" for the callback and execute
310-
// it.
311-
let data = &mut *(data as *mut FiberInfo<F>);
312-
let old_stack_limit = get_stack_limit();
313-
set_stack_limit(guess_os_stack_limit());
314-
let callback = data.callback.as_ptr();
315-
data.panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(callback.read())).err();
316-
317-
// Restore to the previous Fiber
318-
set_stack_limit(old_stack_limit);
319-
SwitchToFiber(data.parent_fiber);
320-
}
321-
322-
fn _grow(stack_size: usize, callback: &mut dyn FnMut()) {
323-
// Fibers (or stackful coroutines) is the only official way to create new stacks on the
324-
// same thread on Windows. So in order to extend the stack we create fiber and switch
325-
// to it so we can use it's stack. After running `callback` within our fiber, we switch
326-
// back to the current stack and destroy the fiber and its associated stack.
327-
unsafe {
328-
let was_fiber = IsThreadAFiber() == 1 as BOOL;
329-
let mut data = FiberInfo {
330-
callback: std::mem::MaybeUninit::new(callback),
331-
panic: None,
332-
parent_fiber: {
333-
if was_fiber {
334-
// Get a handle to the current fiber. We need to use a C implementation
335-
// for this as GetCurrentFiber is an header only function.
336-
__stacker_get_current_fiber()
337-
} else {
338-
// Convert the current thread to a fiber, so we are able to switch back
339-
// to the current stack. Threads coverted to fibers still act like
340-
// regular threads, but they have associated fiber data. We later
341-
// convert it back to a regular thread and free the fiber data.
342-
ConvertThreadToFiber(ptr::null_mut())
343-
}
344-
},
345-
};
346-
347-
if data.parent_fiber.is_null() {
348-
panic!("unable to convert thread to fiber: {}", io::Error::last_os_error());
349-
}
350-
351-
let fiber = CreateFiber(
352-
stack_size as usize,
353-
Some(fiber_proc::<&mut dyn FnMut()>),
354-
&mut data as *mut FiberInfo<&mut dyn FnMut()> as *mut _,
355-
);
356-
if fiber.is_null() {
357-
panic!("unable to allocate fiber: {}", io::Error::last_os_error());
358-
}
359-
360-
// Switch to the fiber we created. This changes stacks and starts executing
361-
// fiber_proc on it. fiber_proc will run `callback` and then switch back to run the
362-
// next statement.
363-
SwitchToFiber(fiber);
364-
DeleteFiber(fiber);
365-
366-
// Clean-up.
367-
if !was_fiber && ConvertFiberToThread() == 0 {
368-
// FIXME: Perhaps should not panic here?
369-
panic!("unable to convert back to thread: {}", io::Error::last_os_error());
370-
}
371-
372-
if let Some(p) = data.panic {
373-
std::panic::resume_unwind(p);
374-
}
375-
}
376-
}
377-
378-
#[inline(always)]
379-
fn get_thread_stack_guarantee() -> usize {
380-
let min_guarantee = if cfg!(target_pointer_width = "32") {
381-
0x1000
382-
} else {
383-
0x2000
384-
};
385-
let mut stack_guarantee = 0;
386-
unsafe {
387-
// Read the current thread stack guarantee
388-
// This is the stack reserved for stack overflow
389-
// exception handling.
390-
// This doesn't return the true value so we need
391-
// some further logic to calculate the real stack
392-
// guarantee. This logic is what is used on x86-32 and
393-
// x86-64 Windows 10. Other versions and platforms may differ
394-
SetThreadStackGuarantee(&mut stack_guarantee)
395-
};
396-
std::cmp::max(stack_guarantee, min_guarantee) as usize + 0x1000
397-
}
398-
399-
#[inline(always)]
400-
unsafe fn guess_os_stack_limit() -> Option<usize> {
401-
// Query the allocation which contains our stack pointer in order
402-
// to discover the size of the stack
403-
//
404-
// FIXME: we could read stack base from the TIB, specifically the 3rd element of it.
405-
type QueryT = windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION;
406-
let mut mi = std::mem::MaybeUninit::<QueryT>::uninit();
407-
VirtualQuery(
408-
psm::stack_pointer() as *const _,
409-
mi.as_mut_ptr(),
410-
std::mem::size_of::<QueryT>() as usize,
411-
);
412-
Some(mi.assume_init().AllocationBase as usize + get_thread_stack_guarantee() + 0x1000)
413-
}
414-
} else if #[cfg(any(target_os = "linux", target_os="solaris", target_os = "netbsd"))] {
415-
unsafe fn guess_os_stack_limit() -> Option<usize> {
416-
let mut attr = std::mem::MaybeUninit::<libc::pthread_attr_t>::uninit();
417-
assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
418-
assert_eq!(libc::pthread_getattr_np(libc::pthread_self(),
419-
attr.as_mut_ptr()), 0);
420-
let mut stackaddr = std::ptr::null_mut();
421-
let mut stacksize = 0;
422-
assert_eq!(libc::pthread_attr_getstack(
423-
attr.as_ptr(), &mut stackaddr, &mut stacksize
424-
), 0);
425-
assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
426-
Some(stackaddr as usize)
427-
}
428-
} else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "illumos"))] {
429-
unsafe fn guess_os_stack_limit() -> Option<usize> {
430-
let mut attr = std::mem::MaybeUninit::<libc::pthread_attr_t>::uninit();
431-
assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
432-
assert_eq!(libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr()), 0);
433-
let mut stackaddr = std::ptr::null_mut();
434-
let mut stacksize = 0;
435-
assert_eq!(libc::pthread_attr_getstack(
436-
attr.as_ptr(), &mut stackaddr, &mut stacksize
437-
), 0);
438-
assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
439-
Some(stackaddr as usize)
440-
}
441-
} else if #[cfg(target_os = "openbsd")] {
442-
unsafe fn guess_os_stack_limit() -> Option<usize> {
443-
let mut stackinfo = std::mem::MaybeUninit::<libc::stack_t>::uninit();
444-
assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), stackinfo.as_mut_ptr()), 0);
445-
Some(stackinfo.assume_init().ss_sp as usize - stackinfo.assume_init().ss_size)
446-
}
447-
} else if #[cfg(target_os = "macos")] {
448-
unsafe fn guess_os_stack_limit() -> Option<usize> {
449-
Some(libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
450-
libc::pthread_get_stacksize_np(libc::pthread_self()) as usize)
451-
}
452-
} else {
453-
// fallback for other platforms is to always increase the stack if we're on
454-
// the root stack. After we increased the stack once, we know the new stack
455-
// size and don't need this pessimization anymore
456-
#[inline(always)]
457-
unsafe fn guess_os_stack_limit() -> Option<usize> {
458-
None
459-
}
279+
#[cfg(windows)]
280+
use backends::windows::_grow;
460281
}
461282
}

0 commit comments

Comments
 (0)
Please sign in to comment.