blob: dd861847677886db4ec6571b204bf9e080206396 [file]
//! test for <https://github.com/rust-lang/rust/issues/160776>
//@ run-pass
//@ needs-threads
//@ edition: 2024
#![feature(rustc_private)]
use std::alloc::{GlobalAlloc, Layout, System};
#[cfg(unix)]
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::Thread;
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
#[cfg(unix)]
static PTHREAD_ACTIVE: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static PTHREAD_SUCCEEDED_ALLOC: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static PTHREAD_SUCCEEDED_DEALLOC: AtomicBool = AtomicBool::new(false);
#[global_allocator]
static ALLOC: Alloc = Alloc;
struct Alloc;
fn call_function(f: impl FnOnce()) {
ALLOCATED.store(0, Ordering::Relaxed);
f();
assert_eq!(ALLOCATED.load(Ordering::Relaxed), 0);
}
// https://doc.rust-lang.org/nightly/std/alloc/trait.GlobalAlloc.html#re-entrance
fn guarantee_functions() {
call_function(|| {
drop(std::thread::current());
});
call_function(|| {
std::thread::current().unpark();
});
call_function(std::thread::park);
call_function(|| {
drop(std::thread::current().clone());
});
call_function(|| {
LOCAL_FOR_ALLOCATOR_WITH_DROP
.with(|local| assert!(local.0.id() == std::thread::current().id()))
});
}
struct LocalForAllocatorWithDrop(Thread);
thread_local! {
static LOCAL_FOR_ALLOCATOR_WITH_DROP: LocalForAllocatorWithDrop = {
LocalForAllocatorWithDrop(std::thread::current())
}
}
unsafe impl GlobalAlloc for Alloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
#[cfg(unix)]
if PTHREAD_ACTIVE.load(Ordering::Relaxed) {
PTHREAD_SUCCEEDED_ALLOC.store(true, Ordering::Relaxed);
}
let ret = unsafe { System.alloc(layout) };
ALLOCATED.fetch_add(1, Ordering::Relaxed);
ret
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
#[cfg(unix)]
if PTHREAD_ACTIVE.load(Ordering::Relaxed) {
PTHREAD_SUCCEEDED_DEALLOC.store(true, Ordering::Relaxed);
}
unsafe {
System.dealloc(ptr, layout);
}
ALLOCATED.fetch_add(1, Ordering::Relaxed);
}
}
// A non-Rust spawned thread will lazily initialize the thread::current structure
// so needs its own test.
#[cfg(unix)]
fn run_pthread() {
extern crate libc;
use std::{mem, ptr};
extern "C" fn start(c: *mut libc::c_void) -> *mut libc::c_void {
PTHREAD_ACTIVE.swap(true, Ordering::Relaxed);
std::hint::black_box(vec![1, 2]);
guarantee_functions();
unsafe {
let c: *mut u32 = c.cast();
drop(Box::from_raw(c))
}
PTHREAD_ACTIVE.swap(false, Ordering::Relaxed);
ptr::null_mut()
}
unsafe {
let c: *mut u32 = Box::into_raw(Box::new(1));
let mut t: libc::pthread_t = mem::zeroed();
libc::pthread_create(&mut t, ptr::null(), start, c.cast());
libc::pthread_join(t, ptr::null_mut());
}
// test if `start` allocates and deallocates with the global_allocator
// test is needed to know if non-Rust spawned threads can use the global allocator
// when a `#[global_allocator]` is set.
assert!(PTHREAD_SUCCEEDED_ALLOC.load(Ordering::Relaxed));
assert!(PTHREAD_SUCCEEDED_DEALLOC.load(Ordering::Relaxed));
}
fn main() {
#[cfg(unix)]
run_pthread();
std::thread::spawn(|| {
guarantee_functions();
})
.join()
.unwrap();
guarantee_functions();
}