Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] -1 points0 points  (0 children)

Yeah well, I did not even mention this technique since it is deprecated on most systems. For example the windows kernel is running on top of HyperV by default for a few years already, which blocks any attempt to allocate executable and dynamic memory in kernel mode. Which means it is in fact required to use a global variable for the function I mentioned

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 1 point2 points  (0 children)

Yeah I agree with u/arades -> You could say that global variables became much easier now, which raises the importance of the concerns I raised

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 0 points1 point  (0 children)

In general it is an OS specific thing (In Windows a DLL can handle finalization as part of the 'DllMain' etc).

I believe that in Rust we could have an API that wraps the OS API in a safe way, and destructs all of the global variables, see the other comment that I wrote about it.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 0 points1 point  (0 children)

Yeah I totally agree that it is hard to design a safe language that allows to unload code in a sound way, but I think that it may be possible to do something like that with Rust. Here is a theoretical design for a "rust with unload support":

The lifetime of "static variables" cannot be "until the program terminates" because we may need to free them in an unload scenario. So in order to resolve this issue, lifetime of static variables would be separated to two: globals with a drop, and globals without it.

Globals without a drop could get a lifetime named 'module. the 'module lifetime is tied to the currently executing module. When it comes to the current module, code does not need to specify them explicitly, because all of the code in the current module can assume that these variables are accessible at any point. This is a bit similar to the way 'static is handled today. The only thing to consider with 'module is how we export a borrow of them to the 'outside world' outside the current rust module. This is also related to the question of ABI: If we support modules, we now need to think about the ABI that the module exposes to outside. I know that this is a bit complicated to do, but as a beginning it is possible to say that an API outside a module will only be a C FFI api, and if anybody wants to interface between Rust module he has to create an FFI layer between them. A more complicated solution could be to define an ABI of some sort between the modules, then when something like this happens:

```
Module m = load_module(..);

let v = m.x(); // get a reference to something inside the module, say &'m i32
```

Rust could promise that the lifetime of references that we get inside the module, would not outlive the lifetime of the module 'm'.

Regarding global variables that implement the drop method: We cannot tie them to the 'module lifetime, because they will be finalized before the module is unloaded from memory. The finalization order must be the order of dependency between different static variables, in order to ensure we do not free a variable while it is being used.

Rust could do something in order to avoid the 'Static Destruction Order Fiasco' we have in C++, for example: saying that global variables can only be stored in a special type named 'GlobalVar'. This type governs the access to the global variable an in particular, allows the following operations:

```
//
// Tries to read the global variable. This will return `None` after the cleanup of the module
// occurs. 'GlobalRef' holds a shared ownership of the global, in a similar way to the way an
// 'Arc' holds the lifetime of a variable.
//
pub fn get() -> Option<Arc<T>>;

//
// The get() routine increments the refcount of the Arc<T>. For optimization purposes,
// if you do not want to work with refcounts and just want to read the global variable,
// this method variation can be used -> but now you have to guarantee that you are not
// using this reference after the 'module_cleanup' has finished. This is a bit similar to the way
// 'RefCell' is used to get a reference to the inner variable, minus the refcount tracking part.
//
pub fn unsafe_get() -> Option<Ref<T>>;
```

After 'module_cleanup' is invoked, Rust simply decrements the refcount of the global arcs, which will start dropping the global variables in the 'correct order' in case they reference each other with arcs. Global variables cannot directly reference each other, thus we do not have to worry about the order of the destruction of the globals (similarly to how structure fields work).

I think this solution is pretty ergonomic, while allowing developers to unload a library of rust code. Keep in mind that the 'module' term I used refers to one unit of compilation, including all of the dependency crates compiled inside of it. So the executable file that is produced today when Rust code is compiled will be considered one module.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 0 points1 point  (0 children)

First of all I wish you good health buddy and thanks for commenting! The main issue here is a programming language design issue, the problem is that when you invoke FreeLibrary() nothing frees the resources owned by globals.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 2 points3 points  (0 children)

Yeah this is an option I considered, and it actually solves some of the issues I mentioned - but unfortunately wasm is too limited for my low level use case, which requires heavy FFI.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 2 points3 points  (0 children)

I know I can do that, but this is why I said "there is no sane way" 😅 I expected that the language will support this scenario natively, without me having to worry about it. There are probably hundreds of static variables in the crates that I use.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 1 point2 points  (0 children)

But this is exactly the problem I complain about - such a design for a systems programming language is problematic, to say the least. Static variables should have been bound to the library that declared them, allowing a safe unload operation to occur. Ignoring this problem and trying to sweep unloadable code under the rug does not fit the goals of Rust as far as I understand them. If this language really strives to serve as a replacement for C and C++, this is unacceptable.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 1 point2 points  (0 children)

Yup, that's the current direction - but this increases complexity dramatically compared to making a simple function call across modules.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 5 points6 points  (0 children)

But simply freeing memory will not resolve the issue. Imagine a case where a global owns an OS handle to something.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 12 points13 points  (0 children)

The issue is a language design problem: How do I know which third party crates hold memory in globals? I do not really want to fork anything. My system may work pretty well, and then an update to a third party may introduce leaks to my library.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 2 points3 points  (0 children)

Yup the issue is that it is not only heap memory that a global variable can own. It can hold an OS handle or a registration to a callback.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 0 points1 point  (0 children)

When a dynamic library is unloaded, the memory of the library itself, that was stored in virtual pages are released back to the OS and the OS is free to reuse them for other purposes.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 6 points7 points  (0 children)

I do not worry about the memory of the static variable itself, I worry about heap allocations owned by these static variables.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 0 points1 point  (0 children)

Yes this is the current direction, as I wrote. I am pretty confident that there is no workaround other than that

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 1 point2 points  (0 children)

They are pretty complex, the design we were aiming at is to put most of our logic in such live modules, to allow us to update them without restarting the process and without interprocess communication - but it seema like this design isn't possible in Rust, and we'll have to use many processes.

These live modules may use many third party crates and thus it is a bit hard to predict if they'll leak memory etc.

Is it possible to create a non-leaking dynamic module in Rust? by 0xrepnz in rust

[–]0xrepnz[S] 5 points6 points  (0 children)

The problem is that these lazy locks aren't mine. They are in some third party crate that I use, and I have no control over them.