From 464b2e2364919f4555d6f8c5025273afd4277366 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 28 Jan 2014 11:48:30 +0100 Subject: [PATCH 1/3] Add libc::consts::os::posix01::PTHREAD_STACK_MIN Represents the minimum size of a thread's stack. As such, it's both platform and architecture-specific. I put it under posix01 even though it predates POSIX.1-2001 by some years. I believe it was first formalized in SUSv2. I doubt anyone cares, though. --- src/libstd/libc.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/libstd/libc.rs b/src/libstd/libc.rs index dccadf2e00b70..11a7b5dd19171 100644 --- a/src/libstd/libc.rs +++ b/src/libstd/libc.rs @@ -2174,7 +2174,7 @@ pub mod consts { pub static EDQUOT: c_int = 1133; } pub mod posix01 { - use libc::types::os::arch::c95::c_int; + use libc::types::os::arch::c95::{c_int, size_t}; pub static SIGTRAP : c_int = 5; @@ -2228,6 +2228,17 @@ pub mod consts { pub static PTHREAD_CREATE_JOINABLE: c_int = 0; pub static PTHREAD_CREATE_DETACHED: c_int = 1; + #[cfg(target_os = "android")] + pub static PTHREAD_STACK_MIN: size_t = 8192; + + #[cfg(target_arch = "arm", target_os = "linux")] + #[cfg(target_arch = "x86", target_os = "linux")] + #[cfg(target_arch = "x86_64", target_os = "linux")] + pub static PTHREAD_STACK_MIN: size_t = 16384; + + #[cfg(target_arch = "mips", target_os = "linux")] + pub static PTHREAD_STACK_MIN: size_t = 131072; + pub static CLOCK_REALTIME: c_int = 0; pub static CLOCK_MONOTONIC: c_int = 1; } @@ -2608,7 +2619,7 @@ pub mod consts { pub static ELAST : c_int = 99; } pub mod posix01 { - use libc::types::os::arch::c95::c_int; + use libc::types::os::arch::c95::{c_int, size_t}; pub static SIGTRAP : c_int = 5; @@ -2662,6 +2673,14 @@ pub mod consts { pub static PTHREAD_CREATE_JOINABLE: c_int = 0; pub static PTHREAD_CREATE_DETACHED: c_int = 1; + #[cfg(target_arch = "arm")] + pub static PTHREAD_STACK_MIN: size_t = 4096; + + #[cfg(target_arch = "mips")] + #[cfg(target_arch = "x86")] + #[cfg(target_arch = "x86_64")] + pub static PTHREAD_STACK_MIN: size_t = 2048; + pub static CLOCK_REALTIME: c_int = 0; pub static CLOCK_MONOTONIC: c_int = 4; } @@ -2990,7 +3009,7 @@ pub mod consts { pub static ELAST : c_int = 106; } pub mod posix01 { - use libc::types::os::arch::c95::c_int; + use libc::types::os::arch::c95::{c_int, size_t}; pub static SIGTRAP : c_int = 5; @@ -3043,6 +3062,7 @@ pub mod consts { pub static PTHREAD_CREATE_JOINABLE: c_int = 1; pub static PTHREAD_CREATE_DETACHED: c_int = 2; + pub static PTHREAD_STACK_MIN: size_t = 8192; } pub mod posix08 { } From b02b5cdcf42b4fe6b1e3ebe56ad0cd43fd907489 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 28 Jan 2014 13:21:31 +0100 Subject: [PATCH 2/3] Retry on EINVAL from pthread_attr_setstacksize() Enforce that the stack size is > RED_ZONE + PTHREAD_STACK_MIN. If the call to pthread_attr_setstacksize() subsequently fails with EINVAL, it means that the platform requires the stack size to be a multiple of the page size. In that case, round up to the nearest page and retry. Fixes #11694. --- src/libstd/rt/thread.rs | 49 ++++++++++++++++++++++++++++++++---- src/libstd/unstable/stack.rs | 2 +- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/libstd/rt/thread.rs b/src/libstd/rt/thread.rs index 83f5ca346a936..605923ac99c6e 100644 --- a/src/libstd/rt/thread.rs +++ b/src/libstd/rt/thread.rs @@ -145,18 +145,30 @@ impl Drop for Thread { #[cfg(windows)] mod imp { use cast; + use cmp; use libc; use libc::types::os::arch::extra::{LPSECURITY_ATTRIBUTES, SIZE_T, BOOL, LPVOID, DWORD, LPDWORD, HANDLE}; use ptr; + use unstable::stack::RED_ZONE; pub type rust_thread = HANDLE; pub type rust_thread_return = DWORD; pub unsafe fn create(stack: uint, p: ~proc()) -> rust_thread { let arg: *mut libc::c_void = cast::transmute(p); - CreateThread(ptr::mut_null(), stack as libc::size_t, super::thread_start, - arg, 0, ptr::mut_null()) + // FIXME On UNIX, we guard against stack sizes that are too small but + // that's because pthreads enforces that stacks are at least + // PTHREAD_STACK_MIN bytes big. Windows has no such lower limit, it's + // just that below a certain threshold you can't do anything useful. + // That threshold is application and architecture-specific, however. + // For now, the only requirement is that it's big enough to hold the + // red zone. Round up to the next 64 kB because that's what the NT + // kernel does, might as well make it explicit. With the current + // 20 kB red zone, that makes for a 64 kB minimum stack. + let stack_size = (cmp::max(stack, RED_ZONE) + 0xfffe) & (-0xfffe - 1); + CreateThread(ptr::mut_null(), stack_size as libc::size_t, + super::thread_start, arg, 0, ptr::mut_null()) } pub unsafe fn join(native: rust_thread) { @@ -190,10 +202,13 @@ mod imp { #[cfg(unix)] mod imp { use cast; - use libc::consts::os::posix01::PTHREAD_CREATE_JOINABLE; + use cmp; + use libc::consts::os::posix01::{PTHREAD_CREATE_JOINABLE, PTHREAD_STACK_MIN}; use libc; + use os; use ptr; use unstable::intrinsics; + use unstable::stack::RED_ZONE; pub type rust_thread = libc::pthread_t; pub type rust_thread_return = *u8; @@ -202,11 +217,29 @@ mod imp { let mut native: libc::pthread_t = intrinsics::uninit(); let mut attr: libc::pthread_attr_t = intrinsics::uninit(); assert_eq!(pthread_attr_init(&mut attr), 0); - assert_eq!(pthread_attr_setstacksize(&mut attr, - stack as libc::size_t), 0); assert_eq!(pthread_attr_setdetachstate(&mut attr, PTHREAD_CREATE_JOINABLE), 0); + // Reserve room for the red zone, the runtime's stack of last resort. + let stack_size = cmp::max(stack, RED_ZONE + PTHREAD_STACK_MIN as uint); + match pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t) { + 0 => { + }, + libc::EINVAL => { + // EINVAL means |stack_size| is either too small or not a + // multiple of the system page size. Because it's definitely + // >= PTHREAD_STACK_MIN, it must be an alignment issue. + // Round up to the neareast page and try again. + let page_size = os::page_size(); + let stack_size = (stack_size + page_size - 1) & (-(page_size - 1) - 1); + assert_eq!(pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t), 0); + }, + errno => { + // This cannot really happen. + fail!("pthread_attr_setstacksize() error: {} ({})", os::last_os_error(), errno); + }, + }; + let arg: *libc::c_void = cast::transmute(p); assert_eq!(pthread_create(&mut native, &attr, super::thread_start, arg), 0); @@ -262,4 +295,10 @@ mod tests { #[test] fn detached() { Thread::spawn(proc () {}) } + + #[test] + fn small_stacks() { + assert_eq!(42, Thread::start_stack(0, proc () 42).join()); + assert_eq!(42, Thread::start_stack(1, proc () 42).join()); + } } diff --git a/src/libstd/unstable/stack.rs b/src/libstd/unstable/stack.rs index 66a9d18aaec80..90c7888973afd 100644 --- a/src/libstd/unstable/stack.rs +++ b/src/libstd/unstable/stack.rs @@ -24,7 +24,7 @@ //! detection is not guaranteed to continue in the future. Usage of this module //! is discouraged unless absolutely necessary. -static RED_ZONE: uint = 20 * 1024; +pub static RED_ZONE: uint = 20 * 1024; /// This function is invoked from rust's current __morestack function. Segmented /// stacks are currently not enabled as segmented stacks, but rather one giant From 431edacbef23e691d1b192da78b4112c35addfbf Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 29 Jan 2014 18:19:23 +0100 Subject: [PATCH 3/3] Use __pthread_get_minstack() when available. glibc >= 2.15 has a __pthread_get_minstack() function that returns PTHREAD_STACK_MIN plus however many bytes are needed for thread-local storage. Use it when it's available because just PTHREAD_STACK_MIN is not enough in applications that have big thread-local storage requirements. Fixes #6233. --- src/libstd/rt/thread.rs | 47 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/libstd/rt/thread.rs b/src/libstd/rt/thread.rs index 605923ac99c6e..b762c1173f56b 100644 --- a/src/libstd/rt/thread.rs +++ b/src/libstd/rt/thread.rs @@ -221,7 +221,7 @@ mod imp { PTHREAD_CREATE_JOINABLE), 0); // Reserve room for the red zone, the runtime's stack of last resort. - let stack_size = cmp::max(stack, RED_ZONE + PTHREAD_STACK_MIN as uint); + let stack_size = cmp::max(stack, RED_ZONE + __pthread_get_minstack(&attr) as uint); match pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t) { 0 => { }, @@ -261,6 +261,51 @@ mod imp { #[cfg(not(target_os = "macos"), not(target_os = "android"))] pub unsafe fn yield_now() { assert_eq!(pthread_yield(), 0); } + #[cfg(not(target_os = "linux"))] + unsafe fn __pthread_get_minstack(_: *libc::pthread_attr_t) -> libc::size_t { + libc::PTHREAD_STACK_MIN + } + + // glibc >= 2.15 has a __pthread_get_minstack() function that returns + // PTHREAD_STACK_MIN plus however many bytes are needed for thread-local + // storage. We need that information to avoid blowing up when a small stack + // is created in an application with big thread-local storage requirements. + // See #6233 for rationale and details. + // + // Dynamically resolve the symbol for compatibility with older versions + // of glibc. Assumes that we've been dynamically linked to libpthread + // but that is currently always the case. Note that this means we take + // a dlopen/dlsym/dlclose hit for every new thread. Mitigating that by + // caching the symbol or the function's return value has its drawbacks: + // + // * Caching the symbol breaks when libpthread.so is reloaded because + // its address changes. + // + // * Caching the return value assumes that it's a fixed quantity. + // Not very future-proof and untrue in the presence of guard pages + // The reason __pthread_get_minstack() takes a *libc::pthread_attr_t + // as its argument is because it takes pthread_attr_setguardsize() into + // account. + // + // A better solution is to define __pthread_get_minstack() as a weak symbol + // but there is currently no way to express that in Rust code. + #[cfg(target_os = "linux")] + unsafe fn __pthread_get_minstack(attr: *libc::pthread_attr_t) -> libc::size_t { + use option::None; + use result::{Err, Ok}; + use unstable::dynamic_lib; + match dynamic_lib::DynamicLibrary::open(None) { + Err(err) => fail!("DynamicLibrary::open(): {}", err), + Ok(handle) => { + match handle.symbol:: + libc::size_t>("__pthread_get_minstack") { + Err(_) => libc::PTHREAD_STACK_MIN, + Ok(__pthread_get_minstack) => __pthread_get_minstack(attr), + } + } + } + } + extern { fn pthread_create(native: *mut libc::pthread_t, attr: *libc::pthread_attr_t,