Skip to main content

Grove/WASM/
MemoryManager.rs

1//! WASM Memory Manager
2//!
3//! Manages memory allocation, deallocation, and limits for WebAssembly
4//! instances. Enforces memory constraints and provides tracking for debugging.
5
6use std::sync::{
7	Arc,
8	atomic::{AtomicU64, Ordering},
9};
10
11use anyhow::{Context, Result};
12use serde::{Deserialize, Serialize};
13#[allow(unused_imports)]
14use wasmtime::{Memory, MemoryType};
15
16use crate::dev_log;
17
18/// Memory limits for WASM instances
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct MemoryLimits {
21	/// Maximum memory per instance in MB
22	pub max_memory_mb:u64,
23	/// Initial memory allocation per instance in MB
24	pub initial_memory_mb:u64,
25	/// Maximum table size (number of elements)
26	pub max_table_size:u32,
27	/// Maximum number of memory instances
28	pub max_memories:usize,
29	/// Maximum number of table instances
30	pub max_tables:usize,
31	/// Maximum number of instances that can be created
32	pub max_instances:usize,
33}
34
35impl Default for MemoryLimits {
36	fn default() -> Self {
37		Self {
38			max_memory_mb:512,
39			initial_memory_mb:64,
40			max_table_size:1024,
41			max_memories:10,
42			max_tables:10,
43			max_instances:100,
44		}
45	}
46}
47
48impl MemoryLimits {
49	/// Create custom memory limits
50	pub fn new(max_memory_mb:u64, initial_memory_mb:u64, max_instances:usize) -> Self {
51		Self { max_memory_mb, initial_memory_mb, max_instances, ..Default::default() }
52	}
53
54	/// Convert max memory to bytes
55	pub fn max_memory_bytes(&self) -> u64 { self.max_memory_mb * 1024 * 1024 }
56
57	/// Convert initial memory to bytes
58	pub fn initial_memory_bytes(&self) -> u64 { self.initial_memory_mb * 1024 * 1024 }
59
60	/// Validate memory request
61	pub fn validate_request(&self, requested_bytes:u64, current_usage:u64) -> Result<()> {
62		if current_usage + requested_bytes > self.max_memory_bytes() {
63			return Err(anyhow::anyhow!(
64				"Memory request exceeds limit: {} + {} > {} bytes",
65				current_usage,
66				requested_bytes,
67				self.max_memory_bytes()
68			));
69		}
70		Ok(())
71	}
72}
73
74/// Memory allocation record for tracking
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct MemoryAllocation {
77	/// Unique allocation identifier
78	pub id:String,
79	/// Instance ID that owns this memory
80	pub instance_id:String,
81	/// Memory type/identifier
82	pub memory_type:String,
83	/// Amount of memory allocated in bytes
84	pub size_bytes:u64,
85	/// Maximum size this allocation can grow to
86	pub max_size_bytes:u64,
87	/// Allocation timestamp
88	pub allocated_at:u64,
89	/// Whether this memory is shared
90	pub is_shared:bool,
91}
92
93/// Memory statistics
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct MemoryStats {
96	/// Total memory allocated in bytes
97	pub total_allocated:u64,
98	/// Total memory allocated in MB
99	pub total_allocated_mb:f64,
100	/// Number of memory allocations
101	pub allocation_count:usize,
102	/// Number of memory deallocations
103	pub deallocation_count:usize,
104	/// Peak memory usage in bytes
105	pub peak_memory_bytes:u64,
106	/// Peak memory usage in MB
107	pub peak_memory_mb:f64,
108}
109
110impl Default for MemoryStats {
111	fn default() -> Self {
112		Self {
113			total_allocated:0,
114			total_allocated_mb:0.0,
115			allocation_count:0,
116			deallocation_count:0,
117			peak_memory_bytes:0,
118			peak_memory_mb:0.0,
119		}
120	}
121}
122
123impl MemoryStats {
124	/// Update stats with new allocation
125	pub fn record_allocation(&mut self, size_bytes:u64) {
126		self.total_allocated += size_bytes;
127		self.allocation_count += 1;
128		if self.total_allocated > self.peak_memory_bytes {
129			self.peak_memory_bytes = self.total_allocated;
130		}
131		self.total_allocated_mb = self.total_allocated as f64 / (1024.0 * 1024.0);
132		self.peak_memory_mb = self.peak_memory_bytes as f64 / (1024.0 * 1024.0);
133	}
134
135	/// Update stats with deallocation
136	pub fn record_deallocation(&mut self, size_bytes:u64) {
137		self.total_allocated = self.total_allocated.saturating_sub(size_bytes);
138		self.deallocation_count += 1;
139		self.total_allocated_mb = self.total_allocated as f64 / (1024.0 * 1024.0);
140	}
141}
142
143/// WASM Memory Manager
144#[derive(Debug)]
145pub struct MemoryManagerImpl {
146	limits:MemoryLimits,
147	allocations:Vec<MemoryAllocation>,
148	stats:Arc<MemoryStats>,
149	peak_usage:Arc<AtomicU64>,
150}
151
152impl MemoryManagerImpl {
153	/// Create a new memory manager with the given limits
154	pub fn new(limits:MemoryLimits) -> Self {
155		Self {
156			limits,
157			allocations:Vec::new(),
158			stats:Arc::new(MemoryStats::default()),
159			peak_usage:Arc::new(AtomicU64::new(0)),
160		}
161	}
162
163	/// Get the current memory limits
164	pub fn limits(&self) -> &MemoryLimits { &self.limits }
165
166	/// Get current memory statistics
167	pub fn stats(&self) -> &MemoryStats { &self.stats }
168
169	/// Get peak memory usage
170	pub fn peak_usage_bytes(&self) -> u64 { self.peak_usage.load(Ordering::Relaxed) }
171
172	/// Get peak memory usage in MB
173	pub fn peak_usage_mb(&self) -> f64 { self.peak_usage.load(Ordering::Relaxed) as f64 / (1024.0 * 1024.0) }
174
175	/// Get current memory usage in bytes
176	pub fn current_usage_bytes(&self) -> u64 { self.allocations.iter().map(|a| a.size_bytes).sum() }
177
178	/// Get current memory usage in MB
179	pub fn current_usage_mb(&self) -> f64 { self.current_usage_bytes() as f64 / (1024.0 * 1024.0) }
180
181	/// Check if memory can be allocated
182	pub fn can_allocate(&self, requested_bytes:u64) -> bool {
183		let current = self.current_usage_bytes();
184		current + requested_bytes <= self.limits.max_memory_bytes()
185	}
186
187	/// Allocate memory for a WASM instance
188	pub fn allocate_memory(&mut self, instance_id:&str, memory_type:&str, requested_bytes:u64) -> Result<u64> {
189		dev_log!(
190			"wasm",
191			"Allocating {} bytes for instance {} (type: {})",
192			requested_bytes,
193			instance_id,
194			memory_type
195		);
196
197		let current_usage = self.current_usage_bytes();
198
199		// Validate against limits
200		self.limits
201			.validate_request(requested_bytes, current_usage)
202			.context("Memory allocation validation failed")?;
203
204		// Check allocation count limit
205		if self.allocations.len() >= self.limits.max_memories {
206			return Err(anyhow::anyhow!(
207				"Maximum number of memory allocations reached: {}",
208				self.limits.max_memories
209			));
210		}
211
212		// Create allocation record
213		let allocation = MemoryAllocation {
214			id:format!("alloc-{}", uuid::Uuid::new_v4()),
215			instance_id:instance_id.to_string(),
216			memory_type:memory_type.to_string(),
217			size_bytes:requested_bytes,
218			max_size_bytes:self.limits.max_memory_bytes() - current_usage,
219			allocated_at:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(),
220			is_shared:false,
221		};
222
223		self.allocations.push(allocation);
224
225		// Update stats
226		Arc::make_mut(&mut self.stats).record_allocation(requested_bytes);
227
228		// Update peak usage
229		let new_peak = self.current_usage_bytes();
230		let current_peak = self.peak_usage.load(Ordering::Relaxed);
231		if new_peak > current_peak {
232			self.peak_usage.store(new_peak, Ordering::Relaxed);
233		}
234
235		dev_log!(
236			"wasm",
237			"Memory allocated successfully. Total usage: {} MB",
238			self.current_usage_mb()
239		);
240
241		Ok(requested_bytes)
242	}
243
244	/// Deallocate memory for a WASM instance
245	pub fn deallocate_memory(&mut self, instance_id:&str, memory_id:&str) -> Result<bool> {
246		dev_log!("wasm", "Deallocating memory {} for instance {}", memory_id, instance_id);
247
248		let pos = self
249			.allocations
250			.iter()
251			.position(|a| a.instance_id == instance_id && a.id == memory_id);
252
253		if let Some(pos) = pos {
254			let allocation = self.allocations.remove(pos);
255
256			// Update stats
257			Arc::make_mut(&mut self.stats).record_deallocation(allocation.size_bytes);
258
259			dev_log!(
260				"wasm",
261				"Memory deallocated successfully. Remaining usage: {} MB",
262				self.current_usage_mb()
263			);
264
265			Ok(true)
266		} else {
267			dev_log!(
268				"wasm",
269				"warn: memory allocation not found: {} for instance {}",
270				memory_id,
271				instance_id
272			);
273			Ok(false)
274		}
275	}
276
277	/// Deallocate all memory for an instance
278	pub fn deallocate_all_for_instance(&mut self, instance_id:&str) -> usize {
279		dev_log!("wasm", "Deallocating all memory for instance {}", instance_id);
280
281		let initial_count = self.allocations.len();
282
283		self.allocations.retain(|a| a.instance_id != instance_id);
284
285		let deallocated_count = initial_count - self.allocations.len();
286
287		if deallocated_count > 0 {
288			dev_log!(
289				"wasm",
290				"Deallocated {} memory allocations for instance {}",
291				deallocated_count,
292				instance_id
293			);
294		}
295
296		deallocated_count
297	}
298
299	/// Grow existing memory allocation
300	pub fn grow_memory(&mut self, instance_id:&str, memory_id:&str, additional_bytes:u64) -> Result<u64> {
301		dev_log!(
302			"wasm",
303			"Growing memory {} for instance {} by {} bytes",
304			memory_id,
305			instance_id,
306			additional_bytes
307		);
308
309		// Calculate current usage before mutable borrow
310		let current_usage = self.current_usage_bytes();
311
312		let allocation = self
313			.allocations
314			.iter_mut()
315			.find(|a| a.instance_id == instance_id && a.id == memory_id)
316			.ok_or_else(|| anyhow::anyhow!("Memory allocation not found"))?;
317
318		// Validate against limits
319		self.limits
320			.validate_request(additional_bytes, current_usage)
321			.context("Memory growth validation failed")?;
322
323		allocation.size_bytes += additional_bytes;
324
325		dev_log!("wasm", "Memory grown successfully. New size: {} bytes", allocation.size_bytes);
326
327		Ok(allocation.size_bytes)
328	}
329
330	/// Get all allocations for an instance
331	pub fn get_allocations_for_instance(&self, instance_id:&str) -> Vec<&MemoryAllocation> {
332		self.allocations.iter().filter(|a| a.instance_id == instance_id).collect()
333	}
334
335	/// Check if memory limits are exceeded
336	pub fn is_exceeded(&self) -> bool { self.current_usage_bytes() > self.limits.max_memory_bytes() }
337
338	/// Get memory usage percentage
339	pub fn usage_percentage(&self) -> f64 {
340		(self.current_usage_bytes() as f64 / self.limits.max_memory_bytes() as f64) * 100.0
341	}
342
343	/// Reset all allocations and stats (use with caution)
344	pub fn reset(&mut self) {
345		self.allocations.clear();
346		self.stats = Arc::new(MemoryStats::default());
347		self.peak_usage.store(0, Ordering::Relaxed);
348		dev_log!("wasm", "Memory manager reset");
349	}
350}
351
352#[cfg(test)]
353mod tests {
354	use super::*;
355
356	#[test]
357	fn test_memory_limits_default() {
358		let limits = MemoryLimits::default();
359		assert_eq!(limits.max_memory_mb, 512);
360		assert_eq!(limits.initial_memory_mb, 64);
361	}
362
363	#[test]
364	fn test_memory_limits_custom() {
365		let limits = MemoryLimits::new(1024, 128, 50);
366		assert_eq!(limits.max_memory_mb, 1024);
367		assert_eq!(limits.initial_memory_mb, 128);
368		assert_eq!(limits.max_instances, 50);
369	}
370
371	#[test]
372	fn test_memory_limits_validation() {
373		let limits = MemoryLimits::new(100, 10, 10);
374
375		// Valid request
376		assert!(limits.validate_request(50, 0).is_ok());
377
378		// Exceeds limit
379		assert!(limits.validate_request(150, 0).is_err());
380		assert!(limits.validate_request(50, 60).is_err());
381	}
382
383	#[test]
384	fn test_memory_manager_creation() {
385		let limits = MemoryLimits::default();
386		let manager = MemoryManagerImpl::new(limits);
387		assert_eq!(manager.current_usage_bytes(), 0);
388		assert_eq!(manager.allocations.len(), 0);
389	}
390
391	#[test]
392	fn test_memory_allocation() {
393		let limits = MemoryLimits::default();
394		let mut manager = MemoryManagerImpl::new(limits);
395
396		let result = manager.allocate_memory("test-instance", "heap", 1024);
397		assert!(result.is_ok());
398		assert_eq!(manager.current_usage_bytes(), 1024);
399		assert_eq!(manager.allocations.len(), 1);
400	}
401
402	#[test]
403	fn test_memory_deallocation() {
404		let limits = MemoryLimits::default();
405		let mut manager = MemoryManagerImpl::new(limits);
406
407		manager.allocate_memory("test-instance", "heap", 1024).unwrap();
408		let allocation = &manager.allocations[0];
409		let memory_id = allocation.id.clone();
410
411		let result = manager.deallocate_memory("test-instance", &memory_id);
412		assert!(result.is_ok());
413		assert_eq!(manager.current_usage_bytes(), 0);
414		assert_eq!(manager.allocations.len(), 0);
415	}
416
417	#[test]
418	fn test_memory_stats() {
419		let mut stats = MemoryStats::default();
420		stats.record_allocation(1024);
421		assert_eq!(stats.allocation_count, 1);
422		assert_eq!(stats.total_allocated, 1024);
423
424		stats.record_deallocation(512);
425		assert_eq!(stats.deallocation_count, 1);
426		assert_eq!(stats.total_allocated, 512);
427	}
428
429	#[test]
430	fn test_memory_usage_percentage() {
431		let limits = MemoryLimits::new(1000, 0, 0);
432		let mut manager = MemoryManagerImpl::new(limits);
433
434		manager.allocate_memory("test", "heap", 500).unwrap();
435		assert_eq!(manager.usage_percentage(), 50.0);
436	}
437}