Function execute_in_isolated_process

Source
pub fn execute_in_isolated_process<F, T>(
    callable: F,
) -> Result<T, MemIsolateError>
where F: FnOnce() -> T, T: Serialize + DeserializeOwned,
Expand description

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.

§Example

use mem_isolate::execute_in_isolated_process;

let leaky_fn = || {
    // Leak 1KiB of memory
    let data: Vec<u8> = Vec::with_capacity(1024);
    let data = Box::new(data);
    Box::leak(data);
};

let _ = execute_in_isolated_process(leaky_fn);
// However, the memory is not leaked in the parent process here

§Errors

Error handling is organized into three levels:

  1. The first level describes the effect of the error on the callable (e.g. did your callable function execute or not)
  2. The second level describes what mem-isolate operation caused the error (e.g. did serialization fail)
  3. The third level is the underlying OS error if it is available (e.g. an io::Error)

For most applications, you’ll care only about the first level:

use mem_isolate::{execute_in_isolated_process, MemIsolateError};

// Function that might cause memory issues
let result = execute_in_isolated_process(|| {
    // Some operation
    "Success!".to_string()
});

match result {
    Ok(value) => println!("Callable succeeded: {}", value),
    Err(MemIsolateError::CallableDidNotExecute(_)) => {
        // Safe to retry, callable never executed
        println!("Callable did not execute, can safely retry");
    },
    Err(MemIsolateError::CallableExecuted(_)) => {
        // Do not retry unless idempotent
        println!("Callable executed but result couldn't be returned");
    },
    Err(MemIsolateError::CallableStatusUnknown(_)) => {
        // Retry only if idempotent
        println!("Unknown if callable executed, retry only if idempotent");
    }
}

For a more detailed look at error handling, see the documentation in the errors module.

§Important Note on Closures

When using closures that capture and mutate variables from their environment, these mutations only occur in the isolated child process and do not affect the parent process’s memory. For example, it may seem surprising that the following code will leave the parent’s counter variable unchanged:

use mem_isolate::execute_in_isolated_process;

let mut counter = 0;
let result = execute_in_isolated_process(|| {
    counter += 1;  // This increment only happens in the child process
    counter        // Returns 1
});
assert_eq!(counter, 0);  // Parent's counter remains unchanged

This is the intended behavior as the function’s purpose is to isolate all memory effects of the callable. However, this can be surprising, especially for FnMut or FnOnce closures.