Crate mem_isolate

Source
Expand description

§mem-isolate: Contain memory leaks and fragmentation

It runs your function via a fork(), waits for the result, and returns it.

This grants your code access to an exact copy of memory and state at the time just before the call, but guarantees that the function will not affect the parent process’s memory footprint in any way.

It forces functions to be memory pure (pure with respect to memory), even if they aren’t.

use mem_isolate::execute_in_isolated_process;

// No heap, stack, or program memory out here...
let result = mem_isolate::execute_in_isolated_process(|| {
    // ...Can be affected by anything in here
    Box::leak(Box::new(vec![42; 1024]));
});

To keep things simple, this crate exposes only two public interfaces:

For more code examples, see examples/. This one in particular shows how you should think about error handling.

For more information, see the README.

§Limitations

§Performance & Usability
  • Works only on POSIX systems (Linux, macOS, BSD)
  • Data returned from the callable function must be serialized to and from the child process (using serde), which can be expensive for large data.
  • Excluding serialization/deserialization cost, execute_in_isolated_process() introduces runtime overhead on the order of ~1ms compared to a direct invocation of the callable.

In performance-critical systems, these overheads can be no joke. However, for many use cases, this is an affordable trade-off for the memory safety and snapshotting behavior that mem-isolate provides.

§Safety & Correctness

The use of fork(), which this crate uses under the hood, has a slew of potentially dangerous side effects and surprises if you’re not careful.

  • For single-threaded use only: It is generally unsound to fork() in multi-threaded environments, especially when mutexes are involved. Only the thread that calls fork() will be cloned and live on in the new process. This can easily lead to deadlocks and hung child processes if other threads are holding resource locks that the child process expects to acquire.
  • Signals delivered to the parent process won’t be automatically forwarded to the child process running your callable during its execution. See one of the examples/blocking-signals-* files for an example of how to handle this.
  • Channels can’t be used to communicate between the parent and child processes. Consider using shared mmaps, pipes, or the filesystem instead.
  • Shared mmaps break the isolation guarantees of this crate. The child process will be able to mutate mmap(..., MAP_SHARED, ...) regions created by the parent process.
  • Panics in your callable won’t panic the rest of your program, as they would without mem-isolate. That’s as useful as it is harmful, depending on your use case, but it’s worth noting.
  • Mutable references, static variables, and raw pointers accessible to your callable won’t be modified as you would expect them to. That’s kind of the whole point of this crate… ;)

Failing to understand or respect these limitations will make your code more susceptible to both undefined behavior (UB) and heap corruption, not less.

§Feature Flags

The following crate feature flags are available:

By default, no additional features are enabled.

Re-exports§

pub use errors::MemIsolateError;

Modules§

errors
Error handling is an important part of the mem-isolate crate. If something went wrong, we want to give the caller as much context as possible about how that error affected their callable, so they are well-equipped to know what to do about it.

Macros§

trace
Conditionally emits a trace-level log message when the “tracing” feature is enabled.

Traits§

DeserializeOwned
A data structure that can be deserialized without borrowing any data from the deserializer.
Serialize
A data structure that can be serialized into any data format supported by Serde.

Functions§

execute_in_isolated_process
Executes a user-supplied callable in a forked child process so that any memory changes during execution do not affect the parent. The child serializes its result (using bincode) and writes it through a pipe, which the parent reads and deserializes.

Derive Macros§

Serialize