Skip to content

Thread spawn hooks can be removed using unwinding handle_alloc_error #159923

Description

@maxdexh

I don't even know if #![feature(thread_spawn_hook)] is still being worked on, but here we go.

Note that the docs of thread::add_spawn_hook specifically state that hooks can only be added, not removed.

The implementation of add_spawn_hook takes the current spawn callback, does some allocations and then stores a new callback, which calls the old callback and the new hook. An unwind during the allocation results in the spawn hooks getting lost.

playground

#![feature(alloc_error_hook, thread_spawn_hook)]

use std::{
    alloc::{GlobalAlloc, System},
    sync::atomic::{AtomicBool, AtomicU32, Ordering},
};

static FAIL: AtomicBool = AtomicBool::new(false);
struct FailingGlobalAlloc;
unsafe impl GlobalAlloc for FailingGlobalAlloc {
    unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 {
        if FAIL.swap(false, Ordering::Relaxed) {
            return std::ptr::null_mut();
        }
        unsafe { System.alloc(layout) }
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
        unsafe { System.dealloc(ptr, layout) }
    }
}
#[global_allocator]
static ALLOC: FailingGlobalAlloc = FailingGlobalAlloc;

fn main() {
    std::alloc::set_alloc_error_hook(|_| panic!());

    static COUNT: AtomicU32 = AtomicU32::new(0);

    // add any number of spawn hooks
    std::thread::add_spawn_hook(|_| || _ = COUNT.fetch_add(1, Ordering::Relaxed));
    std::thread::add_spawn_hook(|_| || _ = COUNT.fetch_add(1, Ordering::Relaxed));
    std::thread::add_spawn_hook(|_| || _ = COUNT.fetch_add(1, Ordering::Relaxed));

    std::thread::scope(|scope| {
        scope.spawn(|| {});
        scope.spawn(|| {});
    });
    std::thread::scope(|scope| {
        scope.spawn(|| {});
    });

    assert_eq!(COUNT.load(Ordering::Relaxed), 9);
    
    // remove hooks by making the allocation fail
    FAIL.store(true, Ordering::Relaxed);
    std::panic::catch_unwind(|| {
        std::thread::add_spawn_hook(|_| || {});
    })
    .unwrap_err();

    // gone :c
    std::thread::scope(|scope| {
        scope.spawn(|| {});
    });
    assert_eq!(COUNT.load(Ordering::Relaxed), 12); // 9 != 12
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-threadArea: `std::thread`C-bugCategory: This is a bug.F-thread_spawn_hook`#![feature(thread_spawn_hook)]`needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.requires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions