Tracking memory usage per operation */ private array $memoryCheckpoints = []; /** @var array Detected memory leaks */ private array $detectedLeaks = []; /** @var int Maximum allowed memory growth per operation (5MB) */ private const int MAX_MEMORY_GROWTH = 5 * 1024 * 1024; /** @var float Memory leak detection threshold (50% growth) */ private const float LEAK_THRESHOLD = 1.5; public function __construct( private readonly MemoryMonitor $memoryMonitor, private readonly Clock $clock, private readonly ?Logger $logger = null ) { } /** * Create a memory checkpoint before an operation */ public function checkpoint(string $operation): void { // Force garbage collection before checkpoint $this->forceCleanup(); $currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes(); $this->memoryCheckpoints[$operation] = $currentMemory; $this->logger?->debug("Memory checkpoint created for {$operation}: {$currentMemory} bytes"); } /** * Validate memory usage after an operation * @return bool True if memory usage is acceptable, false if potential leak detected */ public function validate(string $operation): bool { if (! isset($this->memoryCheckpoints[$operation])) { $this->logger?->warning("No checkpoint found for operation: {$operation}"); return true; } // Force cleanup before measuring $this->forceCleanup(); $startMemory = $this->memoryCheckpoints[$operation]; $currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes(); $memoryGrowth = $currentMemory - $startMemory; // Remove checkpoint unset($this->memoryCheckpoints[$operation]); // Check for excessive memory growth if ($memoryGrowth > self::MAX_MEMORY_GROWTH) { $leakInfo = new MemoryLeakInfo( operation: $operation, startMemory: Byte::fromBytes($startMemory), endMemory: Byte::fromBytes($currentMemory), growth: Byte::fromBytes($memoryGrowth), growthPercentage: GrowthRate::fromMemoryValues($currentMemory, $startMemory), timestamp: $this->clock->time() ); $this->detectedLeaks[$operation] = $leakInfo; $this->logger?->error("Potential memory leak detected in {$operation}: {$leakInfo}"); // Attempt aggressive cleanup $this->aggressiveCleanup(); return false; } // Check for percentage-based leak if ($startMemory > 0 && $currentMemory > $startMemory * self::LEAK_THRESHOLD) { $growthPercentage = ($currentMemory / $startMemory - 1) * 100; $this->logger?->warning( "High memory growth detected in {$operation}: {$growthPercentage}% increase" ); } return true; } /** * Execute a callback with memory protection * @template T * @param callable(): T $callback * @return T */ public function protect(string $operation, callable $callback): mixed { $this->checkpoint($operation); try { $result = $callback(); if (! $this->validate($operation)) { $this->logger?->warning("Memory leak detected after {$operation}"); } return $result; } catch (\Throwable $e) { // Clean up checkpoint on error unset($this->memoryCheckpoints[$operation]); throw $e; } } /** * Process data in chunks with memory protection * @template T * @param array $items * @param callable(T): void $processor */ public function processWithMemoryGuard(array $items, callable $processor, int $chunkSize = 100): void { $chunks = array_chunk($items, $chunkSize); $chunkNumber = 0; foreach ($chunks as $chunk) { $chunkNumber++; $operation = "chunk_{$chunkNumber}"; $this->protect($operation, function () use ($chunk, $processor) { foreach ($chunk as $item) { $processor($item); } }); // Check if we're approaching memory limits if ($this->isMemoryPressureHigh()) { $this->logger?->warning("High memory pressure detected, forcing cleanup"); $this->aggressiveCleanup(); // Reduce chunk size for next iteration $chunkSize = max(10, (int)($chunkSize * 0.5)); } } } /** * Force garbage collection and cleanup */ public function forceCleanup(): void { // Multiple GC runs for thorough cleanup gc_collect_cycles(); gc_collect_cycles(); // PHP 7.3+ memory cache cleanup if (function_exists('gc_mem_caches')) { gc_mem_caches(); } } /** * Aggressive memory cleanup for critical situations */ public function aggressiveCleanup(): void { // Clear all internal caches $this->clearInternalCaches(); // Force multiple GC runs for ($i = 0; $i < 3; $i++) { gc_collect_cycles(); usleep(10000); // 10ms pause between runs } // Clear opcache if available if (function_exists('opcache_reset')) { opcache_reset(); } // Clear realpath cache clearstatcache(true); $this->logger?->info("Aggressive memory cleanup completed"); } /** * Check if memory pressure is high */ public function isMemoryPressureHigh(): bool { $usage = $this->memoryMonitor->getMemoryUsagePercentage(); return $usage->greaterThan(Percentage::from(80.0)); } /** * Get detected memory leaks * @return array */ public function getDetectedLeaks(): array { return $this->detectedLeaks; } /** * Clear detected leaks history */ public function clearLeakHistory(): void { $this->detectedLeaks = []; } /** * Get memory guard statistics */ public function getStatistics(): array { return [ 'active_checkpoints' => count($this->memoryCheckpoints), 'detected_leaks' => count($this->detectedLeaks), 'current_memory' => $this->memoryMonitor->getCurrentMemory()->toHumanReadable(), 'peak_memory' => $this->memoryMonitor->getPeakMemory()->toHumanReadable(), 'memory_usage' => $this->memoryMonitor->getMemoryUsagePercentage()->getValue() . '%', 'is_high_pressure' => $this->isMemoryPressureHigh(), ]; } /** * Clear internal caches (to be extended by specific implementations) */ private function clearInternalCaches(): void { // Clear any static caches // This is a hook for clearing application-specific caches } }