In this lesson, you will learn how to design robust error handlers that allow your system to maintain partial functionality when faced with memory or resource exhaustion.
The Problem: When Everything Fails
When an application encounters memory pressure—such as an allocation failure (AllocatorError)—the natural instinct is often to propagate an error that forces the entire process to terminate. However, in long-running systems, resource exhaustion is often transient or localized. If your system manages multiple independent tasks (like a server handling individual requests), you should treat resource exhaustion as a recoverable domain error rather than a fatal event.
Designing for Partial Recovery
To recover from resource exhaustion, you must design your logic to isolate the side effects of an allocation attempt. Instead of allowing an OOM (Out of Memory) or an allocation error to bubble up to the main function, wrap the high-memory operation in a boundary that can handle the failure and release non-essential cached state.
Consider a system that maintains a cache. When it runs out of memory, it shouldn't crash; it should clear the cache and retry the operation:
enum AppError {
TransientResourceExhaustion,
PersistentFailure,
}
fn process_request(data: Data) -> Result<(), AppError> {
match perform_allocation(&data) {
Ok(_) => Ok(()),
Err(e) if e.is_memory_exhaustion() => {
// Clear internal caches to free resources
CACHE.lock().unwrap().clear();
// Retry the operation once
perform_allocation(&data).map_err(|_| AppError::PersistentFailure)
}
Err(_) => Err(AppError::PersistentFailure),
}
}
Strategic Recovery
The key is to define "partial state recovery." By catching resource exhaustion errors, you gain a window to perform cleanup, such as dropping large temporary buffers, shrinking data structures, or signaling backpressure to upstream callers.
When designing your error types, avoid treating all allocation failures as Fatal. Instead, categorize them so your control flow can decide whether to abandon the specific task while preserving the integrity of the rest of the system. This transforms a potential system crash into a manageable, albeit degraded, state of operation.
Key Takeaway: Design your error handlers to treat memory pressure as a signal to purge non-essential state and retry, rather than a trigger for process termination.