Description
🚀 Feature
The main idea of this is to produce a stable way to avoid and maintain memory leaks as low as possible. Even if we use options like Rust, most of our dependencies are C/C++ and this forces us to have a standard way of tracking, solving and maintaining memory leaks and undefined behaviors to the minimum.
Is your feature request related to a problem?
Yes, recently I have suffered with Python Loader in order to track some memory leaks in the Garbage Collector when refactoring to the new import system. Introducing new code is hard and error prone. Memory leaks are very hard to track, specially when related to Garbage Collectors or similar, where there is much more noise that hides the real problem. For example, a bad reference counter in Python can lead to a segmentation fault. Or a circular dependency can lead to memory leaks and eventually (specially in intensive contexts like FaaS) can generate a Out-Of-Memory Kill of the process.
Describe the solution you'd like
There are some solutions that I came up with, they are not mutually exclusive, and it is just an approximation, we can find others or improve these ones.
-
Prepare a Docker image or some environment (maybe Guix?) building all runtimes with sanitizer and debug flags (for example Python allows to configure with debug and sanitizer options before build, https://devguide.python.org/clang/ or https://github.com/python/cpython/blob/d1b81574edd75e33ae85c525ac988ce772675a07/configure.ac#L3011 ), NetCore also needs to compile with those flags otherwise address sanitizer doesn't work well ( https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/coreclr/enablesanitizers.sh ). Valgrind and Asan are mutually exclusive, so we should have this kind of tests duplicated, once to run with static analysis and another for running with dynamic analysis. Some Valgrind parts will need of suppression files to avoid false positives (specially with free lock data structures and atomics). This approach can be automated with very intensive testing (even fuzzers), which may be so hard to run (in terms of time, cpu and memory consumption) but very effective to detect a lot of errors.
-
Move to Rust the implementation of the loaders, for example using PyO3 ( https://github.com/PyO3/pyo3 ). Rust can avoid many memory leaks and improve security. But it is not a silver bullet neither. Most projects like PyO3, Neon ( https://github.com/neon-bindings/neon ) and Rusty_v8 ( https://github.com/denoland/rusty_v8 ) assume that the unsafe calls to C/C++ are already safe because they trust the developers of Python, NodeJS and V8. This assumption can introduce bugs that escape to the borrow checker. Another drawback is that Rust does not interface well with C/C++, instead of that, rusty_v8 has to generate wrappers around V8 that expose it as C API, similar to NodeJS NAPI. This is acceptable but implies to maintain the interface. Probably there are other automated options nowadays ( https://rust-lang.github.io/rust-bindgen/cpp.html ) but it is something that must be taken into account.
-
Follow the pattern of NodeJS Loader ( https://github.com/metacall/core/blob/a5b97437655d7186c87a1b7d535c423e3625b80e/source/loaders/node_loader/bootstrap/lib/bootstrap.js ), C# Loader ( https://github.com/metacall/core/tree/develop/source/loaders/cs_loader/netcore/source ) or TypeScript Loader ( https://github.com/metacall/core/blob/a5b97437655d7186c87a1b7d535c423e3625b80e/source/loaders/ts_loader/bootstrap/lib/bootstrap.ts ) and move all the code we can from C/C++ to Python, and bootstrap with higher level languages the loaders (this can still lead to leaks but it's usually much less than in C/C++). Some loaders can be completely written in the language itself, for example Crystal Loader (which is not implemented yet) can be done almost completely in Crystal ( https://github.com/metacall/core/blob/develop/source/loaders/cr_loader/crystal/cr_loader_impl.cr ).