Skip to content

Commit a1f157b

Browse files
committed
auto merge of #11885 : bnoordhuis/rust/issue11694, r=alexcrichton
EINVAL means that the requested stack size is either not a multiple of the system page size or that it's smaller than PTHREAD_STACK_MIN. Figure out what the case is, fix it up and retry. If it still fails, give up, like before. Suggestions for future improvements: * don't fail!() but instead signal a condition, or * silently ignore the error and use a default sized stack. Fixes #11694. The first two commits put the framework in place, the third one contains the meat.
2 parents 83f0f6e + 431edac commit a1f157b

File tree

3 files changed

+113
-9
lines changed

3 files changed

+113
-9
lines changed

Diff for: src/libstd/libc.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -2174,7 +2174,7 @@ pub mod consts {
21742174
pub static EDQUOT: c_int = 1133;
21752175
}
21762176
pub mod posix01 {
2177-
use libc::types::os::arch::c95::c_int;
2177+
use libc::types::os::arch::c95::{c_int, size_t};
21782178

21792179
pub static SIGTRAP : c_int = 5;
21802180

@@ -2228,6 +2228,17 @@ pub mod consts {
22282228
pub static PTHREAD_CREATE_JOINABLE: c_int = 0;
22292229
pub static PTHREAD_CREATE_DETACHED: c_int = 1;
22302230

2231+
#[cfg(target_os = "android")]
2232+
pub static PTHREAD_STACK_MIN: size_t = 8192;
2233+
2234+
#[cfg(target_arch = "arm", target_os = "linux")]
2235+
#[cfg(target_arch = "x86", target_os = "linux")]
2236+
#[cfg(target_arch = "x86_64", target_os = "linux")]
2237+
pub static PTHREAD_STACK_MIN: size_t = 16384;
2238+
2239+
#[cfg(target_arch = "mips", target_os = "linux")]
2240+
pub static PTHREAD_STACK_MIN: size_t = 131072;
2241+
22312242
pub static CLOCK_REALTIME: c_int = 0;
22322243
pub static CLOCK_MONOTONIC: c_int = 1;
22332244
}
@@ -2608,7 +2619,7 @@ pub mod consts {
26082619
pub static ELAST : c_int = 99;
26092620
}
26102621
pub mod posix01 {
2611-
use libc::types::os::arch::c95::c_int;
2622+
use libc::types::os::arch::c95::{c_int, size_t};
26122623

26132624
pub static SIGTRAP : c_int = 5;
26142625

@@ -2662,6 +2673,14 @@ pub mod consts {
26622673
pub static PTHREAD_CREATE_JOINABLE: c_int = 0;
26632674
pub static PTHREAD_CREATE_DETACHED: c_int = 1;
26642675

2676+
#[cfg(target_arch = "arm")]
2677+
pub static PTHREAD_STACK_MIN: size_t = 4096;
2678+
2679+
#[cfg(target_arch = "mips")]
2680+
#[cfg(target_arch = "x86")]
2681+
#[cfg(target_arch = "x86_64")]
2682+
pub static PTHREAD_STACK_MIN: size_t = 2048;
2683+
26652684
pub static CLOCK_REALTIME: c_int = 0;
26662685
pub static CLOCK_MONOTONIC: c_int = 4;
26672686
}
@@ -2990,7 +3009,7 @@ pub mod consts {
29903009
pub static ELAST : c_int = 106;
29913010
}
29923011
pub mod posix01 {
2993-
use libc::types::os::arch::c95::c_int;
3012+
use libc::types::os::arch::c95::{c_int, size_t};
29943013

29953014
pub static SIGTRAP : c_int = 5;
29963015

@@ -3043,6 +3062,7 @@ pub mod consts {
30433062

30443063
pub static PTHREAD_CREATE_JOINABLE: c_int = 1;
30453064
pub static PTHREAD_CREATE_DETACHED: c_int = 2;
3065+
pub static PTHREAD_STACK_MIN: size_t = 8192;
30463066
}
30473067
pub mod posix08 {
30483068
}

Diff for: src/libstd/rt/thread.rs

+89-5
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,30 @@ impl<T: Send> Drop for Thread<T> {
145145
#[cfg(windows)]
146146
mod imp {
147147
use cast;
148+
use cmp;
148149
use libc;
149150
use libc::types::os::arch::extra::{LPSECURITY_ATTRIBUTES, SIZE_T, BOOL,
150151
LPVOID, DWORD, LPDWORD, HANDLE};
151152
use ptr;
153+
use unstable::stack::RED_ZONE;
152154

153155
pub type rust_thread = HANDLE;
154156
pub type rust_thread_return = DWORD;
155157

156158
pub unsafe fn create(stack: uint, p: ~proc()) -> rust_thread {
157159
let arg: *mut libc::c_void = cast::transmute(p);
158-
CreateThread(ptr::mut_null(), stack as libc::size_t, super::thread_start,
159-
arg, 0, ptr::mut_null())
160+
// FIXME On UNIX, we guard against stack sizes that are too small but
161+
// that's because pthreads enforces that stacks are at least
162+
// PTHREAD_STACK_MIN bytes big. Windows has no such lower limit, it's
163+
// just that below a certain threshold you can't do anything useful.
164+
// That threshold is application and architecture-specific, however.
165+
// For now, the only requirement is that it's big enough to hold the
166+
// red zone. Round up to the next 64 kB because that's what the NT
167+
// kernel does, might as well make it explicit. With the current
168+
// 20 kB red zone, that makes for a 64 kB minimum stack.
169+
let stack_size = (cmp::max(stack, RED_ZONE) + 0xfffe) & (-0xfffe - 1);
170+
CreateThread(ptr::mut_null(), stack_size as libc::size_t,
171+
super::thread_start, arg, 0, ptr::mut_null())
160172
}
161173

162174
pub unsafe fn join(native: rust_thread) {
@@ -190,10 +202,13 @@ mod imp {
190202
#[cfg(unix)]
191203
mod imp {
192204
use cast;
193-
use libc::consts::os::posix01::PTHREAD_CREATE_JOINABLE;
205+
use cmp;
206+
use libc::consts::os::posix01::{PTHREAD_CREATE_JOINABLE, PTHREAD_STACK_MIN};
194207
use libc;
208+
use os;
195209
use ptr;
196210
use unstable::intrinsics;
211+
use unstable::stack::RED_ZONE;
197212

198213
pub type rust_thread = libc::pthread_t;
199214
pub type rust_thread_return = *u8;
@@ -202,11 +217,29 @@ mod imp {
202217
let mut native: libc::pthread_t = intrinsics::uninit();
203218
let mut attr: libc::pthread_attr_t = intrinsics::uninit();
204219
assert_eq!(pthread_attr_init(&mut attr), 0);
205-
assert_eq!(pthread_attr_setstacksize(&mut attr,
206-
stack as libc::size_t), 0);
207220
assert_eq!(pthread_attr_setdetachstate(&mut attr,
208221
PTHREAD_CREATE_JOINABLE), 0);
209222

223+
// Reserve room for the red zone, the runtime's stack of last resort.
224+
let stack_size = cmp::max(stack, RED_ZONE + __pthread_get_minstack(&attr) as uint);
225+
match pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t) {
226+
0 => {
227+
},
228+
libc::EINVAL => {
229+
// EINVAL means |stack_size| is either too small or not a
230+
// multiple of the system page size. Because it's definitely
231+
// >= PTHREAD_STACK_MIN, it must be an alignment issue.
232+
// Round up to the neareast page and try again.
233+
let page_size = os::page_size();
234+
let stack_size = (stack_size + page_size - 1) & (-(page_size - 1) - 1);
235+
assert_eq!(pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t), 0);
236+
},
237+
errno => {
238+
// This cannot really happen.
239+
fail!("pthread_attr_setstacksize() error: {} ({})", os::last_os_error(), errno);
240+
},
241+
};
242+
210243
let arg: *libc::c_void = cast::transmute(p);
211244
assert_eq!(pthread_create(&mut native, &attr,
212245
super::thread_start, arg), 0);
@@ -228,6 +261,51 @@ mod imp {
228261
#[cfg(not(target_os = "macos"), not(target_os = "android"))]
229262
pub unsafe fn yield_now() { assert_eq!(pthread_yield(), 0); }
230263

264+
#[cfg(not(target_os = "linux"))]
265+
unsafe fn __pthread_get_minstack(_: *libc::pthread_attr_t) -> libc::size_t {
266+
libc::PTHREAD_STACK_MIN
267+
}
268+
269+
// glibc >= 2.15 has a __pthread_get_minstack() function that returns
270+
// PTHREAD_STACK_MIN plus however many bytes are needed for thread-local
271+
// storage. We need that information to avoid blowing up when a small stack
272+
// is created in an application with big thread-local storage requirements.
273+
// See #6233 for rationale and details.
274+
//
275+
// Dynamically resolve the symbol for compatibility with older versions
276+
// of glibc. Assumes that we've been dynamically linked to libpthread
277+
// but that is currently always the case. Note that this means we take
278+
// a dlopen/dlsym/dlclose hit for every new thread. Mitigating that by
279+
// caching the symbol or the function's return value has its drawbacks:
280+
//
281+
// * Caching the symbol breaks when libpthread.so is reloaded because
282+
// its address changes.
283+
//
284+
// * Caching the return value assumes that it's a fixed quantity.
285+
// Not very future-proof and untrue in the presence of guard pages
286+
// The reason __pthread_get_minstack() takes a *libc::pthread_attr_t
287+
// as its argument is because it takes pthread_attr_setguardsize() into
288+
// account.
289+
//
290+
// A better solution is to define __pthread_get_minstack() as a weak symbol
291+
// but there is currently no way to express that in Rust code.
292+
#[cfg(target_os = "linux")]
293+
unsafe fn __pthread_get_minstack(attr: *libc::pthread_attr_t) -> libc::size_t {
294+
use option::None;
295+
use result::{Err, Ok};
296+
use unstable::dynamic_lib;
297+
match dynamic_lib::DynamicLibrary::open(None) {
298+
Err(err) => fail!("DynamicLibrary::open(): {}", err),
299+
Ok(handle) => {
300+
match handle.symbol::<extern "C" fn(*libc::pthread_attr_t) ->
301+
libc::size_t>("__pthread_get_minstack") {
302+
Err(_) => libc::PTHREAD_STACK_MIN,
303+
Ok(__pthread_get_minstack) => __pthread_get_minstack(attr),
304+
}
305+
}
306+
}
307+
}
308+
231309
extern {
232310
fn pthread_create(native: *mut libc::pthread_t,
233311
attr: *libc::pthread_attr_t,
@@ -262,4 +340,10 @@ mod tests {
262340

263341
#[test]
264342
fn detached() { Thread::spawn(proc () {}) }
343+
344+
#[test]
345+
fn small_stacks() {
346+
assert_eq!(42, Thread::start_stack(0, proc () 42).join());
347+
assert_eq!(42, Thread::start_stack(1, proc () 42).join());
348+
}
265349
}

Diff for: src/libstd/unstable/stack.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
//! detection is not guaranteed to continue in the future. Usage of this module
2525
//! is discouraged unless absolutely necessary.
2626
27-
static RED_ZONE: uint = 20 * 1024;
27+
pub static RED_ZONE: uint = 20 * 1024;
2828

2929
/// This function is invoked from rust's current __morestack function. Segmented
3030
/// stacks are currently not enabled as segmented stacks, but rather one giant

0 commit comments

Comments
 (0)