Background threads in a shared library

I'm trying to make a shared library that can be loaded with libloading. My hope was for this library to have an init() function that would spawn a thread in the background, which other functions could send data to through channels, but I'm having a bit of an issue when unloading the library.

Just as a minimum reproducible example, I have two crates, test_lib, compiled as a dylib and libloader, just a binary.

In libloader/src/main.rs:

use libloading::{Library, Symbol};

fn main() {
    unsafe {
        let lib = Library::new("test_lib").unwrap();
        
        let init: Symbol<unsafe extern "C" fn()> = lib.get(b"init").unwrap();
        init();

        lib.close().unwrap();
    }
}

In test_lib/src/lib.rs:

#[no_mangle]
pub extern "C" fn init() {
    std::thread::spawn(move || {
        // Would not use in actual code,
        // just need to keep the thread alive until drop.
        loop {}
    });
}

Everything works perfectly fine until lib.close(), where the program crashes with the following error message:
error: process didn't exit successfully: `target\debug\libloader.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

Is there any way to have background threads in a shared library?
Do I need to store the thread's JoinHandle to join it in some sort of drop() method?
Any help would be greatly appreciated.

I'm unsure what is happening.
But I want to remark that unloading a shared library is sometimes not possible, sometimes related to thread-local storage.
Are you working on Windows or portable?
If you are prototyping, you can try to ignore unloading for the moment, and solve the issues later.

Rust threads are OS threads. In most OSes, including Windows, DLLs and SOs don't own threads; the process does. When you unload the library, the thread continues execution, but its code pages are no longer mapped.

You need to add an entry point to your library which joins the thread. You need to manually call it before dropping the lib.

2 Likes