| //@ run-pass |
| //@ needs-threads |
| |
| use std::alloc::{GlobalAlloc, Layout, System}; |
| use std::hint::black_box; |
| use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; |
| use std::thread::{Thread, ThreadId}; |
| |
| static GLOBAL: AtomicUsize = AtomicUsize::new(0); |
| static SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS: AtomicBool = AtomicBool::new(false); |
| static LOCAL_TRY_WITH_SUCCEEDED_ALLOC: AtomicBool = AtomicBool::new(false); |
| static LOCAL_TRY_WITH_SUCCEEDED_DEALLOC: AtomicBool = AtomicBool::new(false); |
| |
| struct LocalForAllocatorWithoutDrop(ThreadId); |
| |
| struct LocalForAllocatorWithDrop(Thread); |
| |
| impl Drop for LocalForAllocatorWithDrop { |
| fn drop(&mut self) { |
| GLOBAL.fetch_or(2, Ordering::Relaxed); |
| } |
| } |
| |
| struct LocalForUser(u32); |
| |
| impl Drop for LocalForUser { |
| // A user might call the global allocator in a thread-local drop. |
| fn drop(&mut self) { |
| self.0 += 1; |
| drop(black_box(Box::new(self.0))) |
| } |
| } |
| |
| thread_local! { |
| static LOCAL_FOR_USER0: LocalForUser = LocalForUser(0); |
| static LOCAL_FOR_ALLOCATOR_WITHOUT_DROP: LocalForAllocatorWithoutDrop = { |
| LocalForAllocatorWithoutDrop(std::thread::current().id()) |
| }; |
| static LOCAL_FOR_ALLOCATOR_WITH_DROP: LocalForAllocatorWithDrop = { |
| GLOBAL.fetch_or(1, Ordering::Relaxed); |
| LocalForAllocatorWithDrop(std::thread::current()) |
| }; |
| static LOCAL_FOR_USER1: LocalForUser = LocalForUser(1); |
| } |
| |
| #[global_allocator] |
| static ALLOC: Alloc = Alloc; |
| struct Alloc; |
| |
| unsafe impl GlobalAlloc for Alloc { |
| unsafe fn alloc(&self, layout: Layout) -> *mut u8 { |
| // Make sure we aren't re-entrant. |
| assert!(!SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.load(Ordering::Relaxed)); |
| SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.store(true, Ordering::Relaxed); |
| |
| // Should be infallible. |
| LOCAL_FOR_ALLOCATOR_WITHOUT_DROP.with(|local| { |
| assert!(local.0 == std::thread::current().id()); |
| }); |
| |
| // May fail once thread-local destructors start running, and ours has |
| // been ran. |
| let try_with_ret = LOCAL_FOR_ALLOCATOR_WITH_DROP.try_with(|local| { |
| assert!(local.0.id() == std::thread::current().id()); |
| }); |
| LOCAL_TRY_WITH_SUCCEEDED_ALLOC.fetch_or(try_with_ret.is_ok(), Ordering::Relaxed); |
| |
| let ret = unsafe { System.alloc(layout) }; |
| SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.store(false, Ordering::Relaxed); |
| ret |
| } |
| |
| unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { |
| // Make sure we aren't re-entrant. |
| assert!(!SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.load(Ordering::Relaxed)); |
| SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.store(true, Ordering::Relaxed); |
| |
| // Should be infallible. |
| LOCAL_FOR_ALLOCATOR_WITHOUT_DROP.with(|local| { |
| assert!(local.0 == std::thread::current().id()); |
| }); |
| |
| // May fail once thread-local destructors start running, and ours has |
| // been ran. |
| let try_with_ret = LOCAL_FOR_ALLOCATOR_WITH_DROP.try_with(|local| { |
| assert!(local.0.id() == std::thread::current().id()); |
| }); |
| LOCAL_TRY_WITH_SUCCEEDED_DEALLOC.fetch_or(try_with_ret.is_ok(), Ordering::Relaxed); |
| |
| unsafe { System.dealloc(ptr, layout) } |
| SHOULD_PANIC_ON_GLOBAL_ALLOC_ACCESS.store(false, Ordering::Relaxed); |
| } |
| } |
| |
| fn main() { |
| std::thread::spawn(|| { |
| LOCAL_FOR_USER0.with(|l| assert!(l.0 == 0)); |
| std::hint::black_box(vec![1, 2]); |
| assert!(GLOBAL.load(Ordering::Relaxed) == 1); |
| LOCAL_FOR_USER1.with(|l| assert!(l.0 == 1)); |
| }) |
| .join() |
| .unwrap(); |
| assert!(GLOBAL.load(Ordering::Relaxed) == 3); |
| assert!(LOCAL_TRY_WITH_SUCCEEDED_ALLOC.load(Ordering::Relaxed)); |
| assert!(LOCAL_TRY_WITH_SUCCEEDED_DEALLOC.load(Ordering::Relaxed)); |
| } |