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