]> git.proxmox.com Git - rustc.git/blob - library/std/src/sys/unix/thread.rs
New upstream version 1.49.0~beta.4+dfsg1
[rustc.git] / library / std / src / sys / unix / thread.rs
1 use crate::cmp;
2 use crate::ffi::CStr;
3 use crate::io;
4 use crate::mem;
5 use crate::ptr;
6 use crate::sys::{os, stack_overflow};
7 use crate::time::Duration;
8
9 #[cfg(not(any(target_os = "l4re", target_os = "vxworks")))]
10 pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
11 #[cfg(target_os = "l4re")]
12 pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
13 #[cfg(target_os = "vxworks")]
14 pub const DEFAULT_MIN_STACK_SIZE: usize = 256 * 1024;
15
16 pub struct Thread {
17 id: libc::pthread_t,
18 }
19
20 // Some platforms may have pthread_t as a pointer in which case we still want
21 // a thread to be Send/Sync
22 unsafe impl Send for Thread {}
23 unsafe impl Sync for Thread {}
24
25 impl Thread {
26 // unsafe: see thread::Builder::spawn_unchecked for safety requirements
27 pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
28 let p = Box::into_raw(box p);
29 let mut native: libc::pthread_t = mem::zeroed();
30 let mut attr: libc::pthread_attr_t = mem::zeroed();
31 assert_eq!(libc::pthread_attr_init(&mut attr), 0);
32
33 let stack_size = cmp::max(stack, min_stack_size(&attr));
34
35 match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
36 0 => {}
37 n => {
38 assert_eq!(n, libc::EINVAL);
39 // EINVAL means |stack_size| is either too small or not a
40 // multiple of the system page size. Because it's definitely
41 // >= PTHREAD_STACK_MIN, it must be an alignment issue.
42 // Round up to the nearest page and try again.
43 let page_size = os::page_size();
44 let stack_size =
45 (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
46 assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0);
47 }
48 };
49
50 let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _);
51 // Note: if the thread creation fails and this assert fails, then p will
52 // be leaked. However, an alternative design could cause double-free
53 // which is clearly worse.
54 assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
55
56 return if ret != 0 {
57 // The thread failed to start and as a result p was not consumed. Therefore, it is
58 // safe to reconstruct the box so that it gets deallocated.
59 drop(Box::from_raw(p));
60 Err(io::Error::from_raw_os_error(ret))
61 } else {
62 Ok(Thread { id: native })
63 };
64
65 extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
66 unsafe {
67 // Next, set up our stack overflow handler which may get triggered if we run
68 // out of stack.
69 let _handler = stack_overflow::Handler::new();
70 // Finally, let's run some code.
71 Box::from_raw(main as *mut Box<dyn FnOnce()>)();
72 }
73 ptr::null_mut()
74 }
75 }
76
77 pub fn yield_now() {
78 let ret = unsafe { libc::sched_yield() };
79 debug_assert_eq!(ret, 0);
80 }
81
82 #[cfg(any(target_os = "linux", target_os = "android"))]
83 pub fn set_name(name: &CStr) {
84 const PR_SET_NAME: libc::c_int = 15;
85 // pthread wrapper only appeared in glibc 2.12, so we use syscall
86 // directly.
87 unsafe {
88 libc::prctl(PR_SET_NAME, name.as_ptr() as libc::c_ulong, 0, 0, 0);
89 }
90 }
91
92 #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
93 pub fn set_name(name: &CStr) {
94 unsafe {
95 libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr());
96 }
97 }
98
99 #[cfg(any(target_os = "macos", target_os = "ios"))]
100 pub fn set_name(name: &CStr) {
101 unsafe {
102 libc::pthread_setname_np(name.as_ptr());
103 }
104 }
105
106 #[cfg(target_os = "netbsd")]
107 pub fn set_name(name: &CStr) {
108 use crate::ffi::CString;
109 let cname = CString::new(&b"%s"[..]).unwrap();
110 unsafe {
111 libc::pthread_setname_np(
112 libc::pthread_self(),
113 cname.as_ptr(),
114 name.as_ptr() as *mut libc::c_void,
115 );
116 }
117 }
118
119 #[cfg(any(target_os = "solaris", target_os = "illumos"))]
120 pub fn set_name(name: &CStr) {
121 weak! {
122 fn pthread_setname_np(
123 libc::pthread_t, *const libc::c_char
124 ) -> libc::c_int
125 }
126
127 if let Some(f) = pthread_setname_np.get() {
128 unsafe {
129 f(libc::pthread_self(), name.as_ptr());
130 }
131 }
132 }
133
134 #[cfg(any(
135 target_env = "newlib",
136 target_os = "haiku",
137 target_os = "l4re",
138 target_os = "emscripten",
139 target_os = "redox",
140 target_os = "vxworks"
141 ))]
142 pub fn set_name(_name: &CStr) {
143 // Newlib, Haiku, Emscripten, and VxWorks have no way to set a thread name.
144 }
145 #[cfg(target_os = "fuchsia")]
146 pub fn set_name(_name: &CStr) {
147 // FIXME: determine whether Fuchsia has a way to set a thread name.
148 }
149
150 pub fn sleep(dur: Duration) {
151 let mut secs = dur.as_secs();
152 let mut nsecs = dur.subsec_nanos() as _;
153
154 // If we're awoken with a signal then the return value will be -1 and
155 // nanosleep will fill in `ts` with the remaining time.
156 unsafe {
157 while secs > 0 || nsecs > 0 {
158 let mut ts = libc::timespec {
159 tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
160 tv_nsec: nsecs,
161 };
162 secs -= ts.tv_sec as u64;
163 let ts_ptr = &mut ts as *mut _;
164 if libc::nanosleep(ts_ptr, ts_ptr) == -1 {
165 assert_eq!(os::errno(), libc::EINTR);
166 secs += ts.tv_sec as u64;
167 nsecs = ts.tv_nsec;
168 } else {
169 nsecs = 0;
170 }
171 }
172 }
173 }
174
175 pub fn join(self) {
176 unsafe {
177 let ret = libc::pthread_join(self.id, ptr::null_mut());
178 mem::forget(self);
179 assert!(ret == 0, "failed to join thread: {}", io::Error::from_raw_os_error(ret));
180 }
181 }
182
183 pub fn id(&self) -> libc::pthread_t {
184 self.id
185 }
186
187 pub fn into_id(self) -> libc::pthread_t {
188 let id = self.id;
189 mem::forget(self);
190 id
191 }
192 }
193
194 impl Drop for Thread {
195 fn drop(&mut self) {
196 let ret = unsafe { libc::pthread_detach(self.id) };
197 debug_assert_eq!(ret, 0);
198 }
199 }
200
201 #[cfg(all(
202 not(target_os = "linux"),
203 not(target_os = "freebsd"),
204 not(target_os = "macos"),
205 not(all(target_os = "netbsd", not(target_vendor = "rumprun"))),
206 not(target_os = "openbsd"),
207 not(target_os = "solaris")
208 ))]
209 #[cfg_attr(test, allow(dead_code))]
210 pub mod guard {
211 use crate::ops::Range;
212 pub type Guard = Range<usize>;
213 pub unsafe fn current() -> Option<Guard> {
214 None
215 }
216 pub unsafe fn init() -> Option<Guard> {
217 None
218 }
219 }
220
221 #[cfg(any(
222 target_os = "linux",
223 target_os = "freebsd",
224 target_os = "macos",
225 all(target_os = "netbsd", not(target_vendor = "rumprun")),
226 target_os = "openbsd",
227 target_os = "solaris"
228 ))]
229 #[cfg_attr(test, allow(dead_code))]
230 pub mod guard {
231 use libc::{mmap, mprotect};
232 use libc::{MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
233
234 use crate::ops::Range;
235 use crate::sync::atomic::{AtomicUsize, Ordering};
236 use crate::sys::os;
237
238 // This is initialized in init() and only read from after
239 static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
240
241 pub type Guard = Range<usize>;
242
243 #[cfg(target_os = "solaris")]
244 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
245 let mut current_stack: libc::stack_t = crate::mem::zeroed();
246 assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
247 Some(current_stack.ss_sp)
248 }
249
250 #[cfg(target_os = "macos")]
251 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
252 let th = libc::pthread_self();
253 let stackaddr =
254 libc::pthread_get_stackaddr_np(th) as usize - libc::pthread_get_stacksize_np(th);
255 Some(stackaddr as *mut libc::c_void)
256 }
257
258 #[cfg(target_os = "openbsd")]
259 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
260 let mut current_stack: libc::stack_t = crate::mem::zeroed();
261 assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
262
263 let stackaddr = if libc::pthread_main_np() == 1 {
264 // main thread
265 current_stack.ss_sp as usize - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
266 } else {
267 // new thread
268 current_stack.ss_sp as usize - current_stack.ss_size
269 };
270 Some(stackaddr as *mut libc::c_void)
271 }
272
273 #[cfg(any(
274 target_os = "android",
275 target_os = "freebsd",
276 target_os = "linux",
277 target_os = "netbsd",
278 target_os = "l4re"
279 ))]
280 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
281 let mut ret = None;
282 let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
283 #[cfg(target_os = "freebsd")]
284 assert_eq!(libc::pthread_attr_init(&mut attr), 0);
285 #[cfg(target_os = "freebsd")]
286 let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
287 #[cfg(not(target_os = "freebsd"))]
288 let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
289 if e == 0 {
290 let mut stackaddr = crate::ptr::null_mut();
291 let mut stacksize = 0;
292 assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
293 ret = Some(stackaddr);
294 }
295 if e == 0 || cfg!(target_os = "freebsd") {
296 assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
297 }
298 ret
299 }
300
301 // Precondition: PAGE_SIZE is initialized.
302 unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
303 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
304 assert!(page_size != 0);
305 let stackaddr = get_stack_start()?;
306
307 // Ensure stackaddr is page aligned! A parent process might
308 // have reset RLIMIT_STACK to be non-page aligned. The
309 // pthread_attr_getstack() reports the usable stack area
310 // stackaddr < stackaddr + stacksize, so if stackaddr is not
311 // page-aligned, calculate the fix such that stackaddr <
312 // new_page_aligned_stackaddr < stackaddr + stacksize
313 let remainder = (stackaddr as usize) % page_size;
314 Some(if remainder == 0 {
315 stackaddr
316 } else {
317 ((stackaddr as usize) + page_size - remainder) as *mut libc::c_void
318 })
319 }
320
321 pub unsafe fn init() -> Option<Guard> {
322 let page_size = os::page_size();
323 PAGE_SIZE.store(page_size, Ordering::Relaxed);
324
325 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
326 // Linux doesn't allocate the whole stack right away, and
327 // the kernel has its own stack-guard mechanism to fault
328 // when growing too close to an existing mapping. If we map
329 // our own guard, then the kernel starts enforcing a rather
330 // large gap above that, rendering much of the possible
331 // stack space useless. See #43052.
332 //
333 // Instead, we'll just note where we expect rlimit to start
334 // faulting, so our handler can report "stack overflow", and
335 // trust that the kernel's own stack guard will work.
336 let stackaddr = get_stack_start_aligned()?;
337 let stackaddr = stackaddr as usize;
338 Some(stackaddr - page_size..stackaddr)
339 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
340 // For the main thread, the musl's pthread_attr_getstack
341 // returns the current stack size, rather than maximum size
342 // it can eventually grow to. It cannot be used to determine
343 // the position of kernel's stack guard.
344 None
345 } else {
346 // Reallocate the last page of the stack.
347 // This ensures SIGBUS will be raised on
348 // stack overflow.
349 // Systems which enforce strict PAX MPROTECT do not allow
350 // to mprotect() a mapping with less restrictive permissions
351 // than the initial mmap() used, so we mmap() here with
352 // read/write permissions and only then mprotect() it to
353 // no permissions at all. See issue #50313.
354 let stackaddr = get_stack_start_aligned()?;
355 let result = mmap(
356 stackaddr,
357 page_size,
358 PROT_READ | PROT_WRITE,
359 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
360 -1,
361 0,
362 );
363 if result != stackaddr || result == MAP_FAILED {
364 panic!("failed to allocate a guard page");
365 }
366
367 let result = mprotect(stackaddr, page_size, PROT_NONE);
368 if result != 0 {
369 panic!("failed to protect the guard page");
370 }
371
372 let guardaddr = stackaddr as usize;
373 let offset = if cfg!(target_os = "freebsd") { 2 } else { 1 };
374
375 Some(guardaddr..guardaddr + offset * page_size)
376 }
377 }
378
379 #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
380 pub unsafe fn current() -> Option<Guard> {
381 let stackaddr = get_stack_start()? as usize;
382 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
383 }
384
385 #[cfg(any(
386 target_os = "android",
387 target_os = "freebsd",
388 target_os = "linux",
389 target_os = "netbsd",
390 target_os = "l4re"
391 ))]
392 pub unsafe fn current() -> Option<Guard> {
393 let mut ret = None;
394 let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
395 #[cfg(target_os = "freebsd")]
396 assert_eq!(libc::pthread_attr_init(&mut attr), 0);
397 #[cfg(target_os = "freebsd")]
398 let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
399 #[cfg(not(target_os = "freebsd"))]
400 let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
401 if e == 0 {
402 let mut guardsize = 0;
403 assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
404 if guardsize == 0 {
405 if cfg!(all(target_os = "linux", target_env = "musl")) {
406 // musl versions before 1.1.19 always reported guard
407 // size obtained from pthread_attr_get_np as zero.
408 // Use page size as a fallback.
409 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
410 } else {
411 panic!("there is no guard page");
412 }
413 }
414 let mut stackaddr = crate::ptr::null_mut();
415 let mut size = 0;
416 assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut size), 0);
417
418 let stackaddr = stackaddr as usize;
419 ret = if cfg!(target_os = "freebsd") {
420 // FIXME does freebsd really fault *below* the guard addr?
421 let guardaddr = stackaddr - guardsize;
422 Some(guardaddr - PAGE_SIZE.load(Ordering::Relaxed)..guardaddr)
423 } else if cfg!(target_os = "netbsd") {
424 Some(stackaddr - guardsize..stackaddr)
425 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
426 Some(stackaddr - guardsize..stackaddr)
427 } else if cfg!(all(target_os = "linux", target_env = "gnu")) {
428 // glibc used to include the guard area within the stack, as noted in the BUGS
429 // section of `man pthread_attr_getguardsize`. This has been corrected starting
430 // with glibc 2.27, and in some distro backports, so the guard is now placed at the
431 // end (below) the stack. There's no easy way for us to know which we have at
432 // runtime, so we'll just match any fault in the range right above or below the
433 // stack base to call that fault a stack overflow.
434 Some(stackaddr - guardsize..stackaddr + guardsize)
435 } else {
436 Some(stackaddr..stackaddr + guardsize)
437 };
438 }
439 if e == 0 || cfg!(target_os = "freebsd") {
440 assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
441 }
442 ret
443 }
444 }
445
446 // glibc >= 2.15 has a __pthread_get_minstack() function that returns
447 // PTHREAD_STACK_MIN plus bytes needed for thread-local storage.
448 // We need that information to avoid blowing up when a small stack
449 // is created in an application with big thread-local storage requirements.
450 // See #6233 for rationale and details.
451 #[cfg(target_os = "linux")]
452 #[allow(deprecated)]
453 fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
454 weak!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
455
456 match __pthread_get_minstack.get() {
457 None => libc::PTHREAD_STACK_MIN,
458 Some(f) => unsafe { f(attr) },
459 }
460 }
461
462 // No point in looking up __pthread_get_minstack() on non-glibc
463 // platforms.
464 #[cfg(all(not(target_os = "linux"), not(target_os = "netbsd")))]
465 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
466 libc::PTHREAD_STACK_MIN
467 }
468
469 #[cfg(target_os = "netbsd")]
470 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
471 2048 // just a guess
472 }