nagisa / rust_libloading

Bindings around the platform's dynamic library loading primitives with greatly improved memory safety.
https://docs.rs/libloading
ISC License
1.22k stars 100 forks source link

Thread-local RefCell containing heap-allocated object produces unexpected library re-loading behavior #148

Open collinoc opened 4 months ago

collinoc commented 4 months ago

I'm running into an issue with static thread local storage that contains a heap-allocated object when loading a library.

Given a simple library:

#[no_mangle]
pub fn lib_func() {
    thread_local! {
        static CELL: RefCell<Box<u8>> = RefCell::new(Box::new(1));
    }

    CELL.with_borrow(|cell| {
        println!("Cell: {cell}");
    });
}

and a main program that will repeatedly load said library:

loop {
    unsafe {
        let lib = libloading::Library::new("../target/release/libplugins.so")?;
        let lib_func: libloading::Symbol<fn()> = lib.get(b"lib_func")?;

        lib_func();
    }

    thread::sleep(Duration::from_secs(1));
}

The issue I'm seeing: Calling lib_func from the main loop initially prints Cell: 1 as expected. If you change the value to say, 2, within the RefCell in lib_func and recompile, the main loop will still repeatedly print Cell: 1.

If you replace the RefCell with a plain Cell, then once changing the value to 2 and recompiling, it would start printing Cell: 2, as I would expect.

The Box<u8> within the RefCell can be replaced with any other heap-allocated object such as a String or Vec to see the same unexpected behavior.

Full Example

Rust info:

$ cargo -V -v
cargo 1.78.0 (54d8815d0 2024-03-26)
release: 1.78.0
commit-hash: 54d8815d04fa3816edc207bbc4dd36bf18014dbc
commit-date: 2024-03-26
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.2 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Ubuntu 22.04 (jammy) [64-bit]
collinoc commented 3 months ago

Trimmed down the examples and explanation for clarity. Also included Rust version info.

AlexanderSchuetz97 commented 2 months ago

You are aware that what you are doing here is highly platform dependant. For example musl-libc doesnt even support library unloading. Its a noop there.

Glibc to my knowlege has a refcounter and does actually to some extend unload librarys.

I do not know with which criteria windows decides to remove a dll from the process address space after you call CloseHandle on the libraries handle.

I personally try to never unload shared objects/libraries as most libs do not even implement unloading properly. You are probably observing just this in your example.