pub fn execute_in_isolated_process<F, T>(
callable: F,
) -> Result<T, MemIsolateError>
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:
- The first level describes the effect of the error on the
callable
(e.g. did your callable function execute or not) - The second level describes what
mem-isolate
operation caused the error (e.g. did serialization fail) - 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.