]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/thread.rs
Rollup merge of #84466 - jyn514:prim-str, r=GuillaumeGomez
[rust.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(target_os = "netbsd"),
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     target_os = "netbsd",
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::io;
235     use crate::ops::Range;
236     use crate::sync::atomic::{AtomicUsize, Ordering};
237     use crate::sys::os;
238
239     // This is initialized in init() and only read from after
240     static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
241
242     pub type Guard = Range<usize>;
243
244     #[cfg(target_os = "solaris")]
245     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
246         let mut current_stack: libc::stack_t = crate::mem::zeroed();
247         assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
248         Some(current_stack.ss_sp)
249     }
250
251     #[cfg(target_os = "macos")]
252     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
253         let th = libc::pthread_self();
254         let stackaddr =
255             libc::pthread_get_stackaddr_np(th) as usize - libc::pthread_get_stacksize_np(th);
256         Some(stackaddr as *mut libc::c_void)
257     }
258
259     #[cfg(target_os = "openbsd")]
260     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
261         let mut current_stack: libc::stack_t = crate::mem::zeroed();
262         assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
263
264         let stackaddr = if libc::pthread_main_np() == 1 {
265             // main thread
266             current_stack.ss_sp as usize - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
267         } else {
268             // new thread
269             current_stack.ss_sp as usize - current_stack.ss_size
270         };
271         Some(stackaddr as *mut libc::c_void)
272     }
273
274     #[cfg(any(
275         target_os = "android",
276         target_os = "freebsd",
277         target_os = "linux",
278         target_os = "netbsd",
279         target_os = "l4re"
280     ))]
281     unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
282         let mut ret = None;
283         let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
284         #[cfg(target_os = "freebsd")]
285         assert_eq!(libc::pthread_attr_init(&mut attr), 0);
286         #[cfg(target_os = "freebsd")]
287         let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
288         #[cfg(not(target_os = "freebsd"))]
289         let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
290         if e == 0 {
291             let mut stackaddr = crate::ptr::null_mut();
292             let mut stacksize = 0;
293             assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
294             ret = Some(stackaddr);
295         }
296         if e == 0 || cfg!(target_os = "freebsd") {
297             assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
298         }
299         ret
300     }
301
302     // Precondition: PAGE_SIZE is initialized.
303     unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
304         let page_size = PAGE_SIZE.load(Ordering::Relaxed);
305         assert!(page_size != 0);
306         let stackaddr = get_stack_start()?;
307
308         // Ensure stackaddr is page aligned! A parent process might
309         // have reset RLIMIT_STACK to be non-page aligned. The
310         // pthread_attr_getstack() reports the usable stack area
311         // stackaddr < stackaddr + stacksize, so if stackaddr is not
312         // page-aligned, calculate the fix such that stackaddr <
313         // new_page_aligned_stackaddr < stackaddr + stacksize
314         let remainder = (stackaddr as usize) % page_size;
315         Some(if remainder == 0 {
316             stackaddr
317         } else {
318             ((stackaddr as usize) + page_size - remainder) as *mut libc::c_void
319         })
320     }
321
322     pub unsafe fn init() -> Option<Guard> {
323         let page_size = os::page_size();
324         PAGE_SIZE.store(page_size, Ordering::Relaxed);
325
326         if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
327             // Linux doesn't allocate the whole stack right away, and
328             // the kernel has its own stack-guard mechanism to fault
329             // when growing too close to an existing mapping.  If we map
330             // our own guard, then the kernel starts enforcing a rather
331             // large gap above that, rendering much of the possible
332             // stack space useless.  See #43052.
333             //
334             // Instead, we'll just note where we expect rlimit to start
335             // faulting, so our handler can report "stack overflow", and
336             // trust that the kernel's own stack guard will work.
337             let stackaddr = get_stack_start_aligned()?;
338             let stackaddr = stackaddr as usize;
339             Some(stackaddr - page_size..stackaddr)
340         } else if cfg!(all(target_os = "linux", target_env = "musl")) {
341             // For the main thread, the musl's pthread_attr_getstack
342             // returns the current stack size, rather than maximum size
343             // it can eventually grow to. It cannot be used to determine
344             // the position of kernel's stack guard.
345             None
346         } else if cfg!(target_os = "freebsd") {
347             // FreeBSD's stack autogrows, and optionally includes a guard page
348             // at the bottom.  If we try to remap the bottom of the stack
349             // ourselves, FreeBSD's guard page moves upwards.  So we'll just use
350             // the builtin guard page.
351             let stackaddr = get_stack_start_aligned()?;
352             let guardaddr = stackaddr as usize;
353             // Technically the number of guard pages is tunable and controlled
354             // by the security.bsd.stack_guard_page sysctl, but there are
355             // few reasons to change it from the default.  The default value has
356             // been 1 ever since FreeBSD 11.1 and 10.4.
357             const GUARD_PAGES: usize = 1;
358             let guard = guardaddr..guardaddr + GUARD_PAGES * page_size;
359             Some(guard)
360         } else {
361             // Reallocate the last page of the stack.
362             // This ensures SIGBUS will be raised on
363             // stack overflow.
364             // Systems which enforce strict PAX MPROTECT do not allow
365             // to mprotect() a mapping with less restrictive permissions
366             // than the initial mmap() used, so we mmap() here with
367             // read/write permissions and only then mprotect() it to
368             // no permissions at all. See issue #50313.
369             let stackaddr = get_stack_start_aligned()?;
370             let result = mmap(
371                 stackaddr,
372                 page_size,
373                 PROT_READ | PROT_WRITE,
374                 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
375                 -1,
376                 0,
377             );
378             if result != stackaddr || result == MAP_FAILED {
379                 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
380             }
381
382             let result = mprotect(stackaddr, page_size, PROT_NONE);
383             if result != 0 {
384                 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
385             }
386
387             let guardaddr = stackaddr as usize;
388
389             Some(guardaddr..guardaddr + page_size)
390         }
391     }
392
393     #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
394     pub unsafe fn current() -> Option<Guard> {
395         let stackaddr = get_stack_start()? as usize;
396         Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
397     }
398
399     #[cfg(any(
400         target_os = "android",
401         target_os = "freebsd",
402         target_os = "linux",
403         target_os = "netbsd",
404         target_os = "l4re"
405     ))]
406     pub unsafe fn current() -> Option<Guard> {
407         let mut ret = None;
408         let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
409         #[cfg(target_os = "freebsd")]
410         assert_eq!(libc::pthread_attr_init(&mut attr), 0);
411         #[cfg(target_os = "freebsd")]
412         let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
413         #[cfg(not(target_os = "freebsd"))]
414         let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
415         if e == 0 {
416             let mut guardsize = 0;
417             assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
418             if guardsize == 0 {
419                 if cfg!(all(target_os = "linux", target_env = "musl")) {
420                     // musl versions before 1.1.19 always reported guard
421                     // size obtained from pthread_attr_get_np as zero.
422                     // Use page size as a fallback.
423                     guardsize = PAGE_SIZE.load(Ordering::Relaxed);
424                 } else {
425                     panic!("there is no guard page");
426                 }
427             }
428             let mut stackaddr = crate::ptr::null_mut();
429             let mut size = 0;
430             assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut size), 0);
431
432             let stackaddr = stackaddr as usize;
433             ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd")) {
434                 Some(stackaddr - guardsize..stackaddr)
435             } else if cfg!(all(target_os = "linux", target_env = "musl")) {
436                 Some(stackaddr - guardsize..stackaddr)
437             } else if cfg!(all(target_os = "linux", target_env = "gnu")) {
438                 // glibc used to include the guard area within the stack, as noted in the BUGS
439                 // section of `man pthread_attr_getguardsize`.  This has been corrected starting
440                 // with glibc 2.27, and in some distro backports, so the guard is now placed at the
441                 // end (below) the stack.  There's no easy way for us to know which we have at
442                 // runtime, so we'll just match any fault in the range right above or below the
443                 // stack base to call that fault a stack overflow.
444                 Some(stackaddr - guardsize..stackaddr + guardsize)
445             } else {
446                 Some(stackaddr..stackaddr + guardsize)
447             };
448         }
449         if e == 0 || cfg!(target_os = "freebsd") {
450             assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
451         }
452         ret
453     }
454 }
455
456 // glibc >= 2.15 has a __pthread_get_minstack() function that returns
457 // PTHREAD_STACK_MIN plus bytes needed for thread-local storage.
458 // We need that information to avoid blowing up when a small stack
459 // is created in an application with big thread-local storage requirements.
460 // See #6233 for rationale and details.
461 #[cfg(target_os = "linux")]
462 #[allow(deprecated)]
463 fn min_stack_size(attr: *const libc::pthread_attr_t) -> usize {
464     weak!(fn __pthread_get_minstack(*const libc::pthread_attr_t) -> libc::size_t);
465
466     match __pthread_get_minstack.get() {
467         None => libc::PTHREAD_STACK_MIN,
468         Some(f) => unsafe { f(attr) },
469     }
470 }
471
472 // No point in looking up __pthread_get_minstack() on non-glibc
473 // platforms.
474 #[cfg(all(not(target_os = "linux"), not(target_os = "netbsd")))]
475 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
476     libc::PTHREAD_STACK_MIN
477 }
478
479 #[cfg(target_os = "netbsd")]
480 fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
481     2048 // just a guess
482 }