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