|  | 
|  | 1 | +# Memory Monitor | 
|  | 2 | + | 
|  | 3 | +This guide explains how to use the {ruby Async::Container::Supervisor::MemoryMonitor} to detect and restart workers that exceed memory limits or develop memory leaks. | 
|  | 4 | + | 
|  | 5 | +## Overview | 
|  | 6 | + | 
|  | 7 | +Long-running worker processes often accumulate memory over time, either through legitimate growth or memory leaks. Without intervention, workers can consume all available system memory, causing performance degradation or system crashes. The `MemoryMonitor` solves this by automatically detecting and restarting problematic workers before they impact system stability. | 
|  | 8 | + | 
|  | 9 | +Use the `MemoryMonitor` when you need: | 
|  | 10 | + | 
|  | 11 | +- **Memory leak protection**: Automatically restart workers that continuously accumulate memory. | 
|  | 12 | +- **Resource limits**: Enforce maximum memory usage per worker. | 
|  | 13 | +- **System stability**: Prevent runaway processes from exhausting system memory. | 
|  | 14 | +- **Leak diagnosis**: Capture memory samples when leaks are detected for debugging. | 
|  | 15 | + | 
|  | 16 | +The monitor uses the `memory-leak` gem to track process memory usage over time, detecting abnormal growth patterns that indicate leaks. | 
|  | 17 | + | 
|  | 18 | +## Usage | 
|  | 19 | + | 
|  | 20 | +Add a memory monitor to your supervisor service to automatically restart workers that exceed 500MB: | 
|  | 21 | + | 
|  | 22 | +```ruby | 
|  | 23 | +service "supervisor" do | 
|  | 24 | +	include Async::Container::Supervisor::Environment | 
|  | 25 | +	 | 
|  | 26 | +	monitors do | 
|  | 27 | +		[ | 
|  | 28 | +			Async::Container::Supervisor::MemoryMonitor.new( | 
|  | 29 | +				# Check worker memory every 10 seconds: | 
|  | 30 | +				interval: 10, | 
|  | 31 | + | 
|  | 32 | +				# Restart workers exceeding 500MB: | 
|  | 33 | +				maximum_size_limit: 1024 * 1024 * 500 | 
|  | 34 | +			) | 
|  | 35 | +		] | 
|  | 36 | +	end | 
|  | 37 | +end | 
|  | 38 | +``` | 
|  | 39 | + | 
|  | 40 | +When a worker exceeds the limit: | 
|  | 41 | +1. The monitor logs the leak detection. | 
|  | 42 | +2. Optionally captures a memory sample for debugging. | 
|  | 43 | +3. Sends `SIGINT` to gracefully shut down the worker. | 
|  | 44 | +4. The container automatically spawns a replacement worker. | 
|  | 45 | + | 
|  | 46 | +## Configuration Options | 
|  | 47 | + | 
|  | 48 | +The `MemoryMonitor` accepts the following options: | 
|  | 49 | + | 
|  | 50 | +### `interval` | 
|  | 51 | + | 
|  | 52 | +The interval (in seconds) at which to check for memory leaks. Default: `10` seconds. | 
|  | 53 | + | 
|  | 54 | +```ruby | 
|  | 55 | +Async::Container::Supervisor::MemoryMonitor.new(interval: 30) | 
|  | 56 | +``` | 
|  | 57 | + | 
|  | 58 | +### `maximum_size_limit` | 
|  | 59 | + | 
|  | 60 | +The maximum memory size (in bytes) per process. When a process exceeds this limit, it will be restarted. | 
|  | 61 | + | 
|  | 62 | +```ruby | 
|  | 63 | +# 500MB limit | 
|  | 64 | +Async::Container::Supervisor::MemoryMonitor.new(maximum_size_limit: 1024 * 1024 * 500) | 
|  | 65 | + | 
|  | 66 | +# 1GB limit | 
|  | 67 | +Async::Container::Supervisor::MemoryMonitor.new(maximum_size_limit: 1024 * 1024 * 1024) | 
|  | 68 | +``` | 
|  | 69 | + | 
|  | 70 | +### `total_size_limit` | 
|  | 71 | + | 
|  | 72 | +The total size limit (in bytes) for all monitored processes combined. If not specified, only per-process limits are enforced. | 
|  | 73 | + | 
|  | 74 | +```ruby | 
|  | 75 | +# Total limit of 2GB across all workers | 
|  | 76 | +Async::Container::Supervisor::MemoryMonitor.new( | 
|  | 77 | +	maximum_size_limit: 1024 * 1024 * 500,  # 500MB per process | 
|  | 78 | +	total_size_limit: 1024 * 1024 * 1024 * 2  # 2GB total | 
|  | 79 | +) | 
|  | 80 | +``` | 
|  | 81 | + | 
|  | 82 | +### `memory_sample` | 
|  | 83 | + | 
|  | 84 | +Options for capturing memory samples when a leak is detected. If `nil`, memory sampling is disabled. | 
|  | 85 | + | 
|  | 86 | +Default: `{duration: 30, timeout: 120}` | 
|  | 87 | + | 
|  | 88 | +```ruby | 
|  | 89 | +# Customize memory sampling: | 
|  | 90 | +Async::Container::Supervisor::MemoryMonitor.new( | 
|  | 91 | +	memory_sample: { | 
|  | 92 | +		duration: 60,  # Sample for 60 seconds | 
|  | 93 | +		timeout: 180   # Timeout after 180 seconds | 
|  | 94 | +	} | 
|  | 95 | +) | 
|  | 96 | + | 
|  | 97 | +# Disable memory sampling: | 
|  | 98 | +Async::Container::Supervisor::MemoryMonitor.new( | 
|  | 99 | +	memory_sample: nil | 
|  | 100 | +) | 
|  | 101 | +``` | 
|  | 102 | + | 
|  | 103 | +## Memory Leak Detection | 
|  | 104 | + | 
|  | 105 | +When a memory leak is detected, the monitor will: | 
|  | 106 | + | 
|  | 107 | +1. Log the leak detection with process details. | 
|  | 108 | +2. If `memory_sample` is configured, capture a memory sample from the worker. | 
|  | 109 | +3. Send a `SIGINT` signal to gracefully restart the worker. | 
|  | 110 | +4. The container will automatically restart the worker process. | 
|  | 111 | + | 
|  | 112 | +### Memory Sampling | 
|  | 113 | + | 
|  | 114 | +When a memory leak is detected and `memory_sample` is configured, the monitor requests a lightweight memory sample from the worker. This sample: | 
|  | 115 | + | 
|  | 116 | +- Tracks allocations during the sampling period. | 
|  | 117 | +- Forces a garbage collection. | 
|  | 118 | +- Returns a JSON report showing retained objects. | 
|  | 119 | + | 
|  | 120 | +The report includes: | 
|  | 121 | +- `total_allocated`: Total allocated memory and object count. | 
|  | 122 | +- `total_retained`: Total retained memory and count after GC. | 
|  | 123 | +- `by_gem`: Breakdown by gem/library. | 
|  | 124 | +- `by_file`: Breakdown by source file. | 
|  | 125 | +- `by_location`: Breakdown by specific file:line locations. | 
|  | 126 | +- `by_class`: Breakdown by object class. | 
|  | 127 | +- `strings`: String allocation analysis. | 
|  | 128 | + | 
|  | 129 | +This is much more efficient than a full heap dump using `ObjectSpace.dump_all`. | 
0 commit comments