logger->info('Starting dependency resolution for completed job', [ 'job_id' => $jobId, 'successful' => $successful, ]); $resolvedJobs = []; // 1. Resolve individual job dependencies $dependentJobs = $this->dependencyManager->resolveJobCompletion($jobId, $successful); foreach ($dependentJobs as $dependentJobId) { $resolvedJobs[] = [ 'job_id' => $dependentJobId, 'type' => 'dependency_resolved', 'trigger_job' => $jobId, ]; } // 2. Handle job chain progression $this->chainManager->handleJobCompletion($jobId, $successful); // Get next jobs in any chains this job belongs to $chains = $this->chainManager->getChainsForJob($jobId); foreach ($chains as $chainEntry) { if ($chainEntry->isRunning()) { $nextJobId = $this->chainManager->getNextJobInChain($chainEntry->chainId, $jobId); if ($nextJobId !== null) { // Check if the next job has any other unsatisfied dependencies if ($this->dependencyManager->canJobBeExecuted($nextJobId)) { $resolvedJobs[] = [ 'job_id' => $nextJobId, 'type' => 'chain_progression', 'chain_id' => $chainEntry->chainId, 'trigger_job' => $jobId, ]; } else { $this->logger->info('Next job in chain has unsatisfied dependencies', [ 'chain_id' => $chainEntry->chainId, 'next_job_id' => $nextJobId, 'unsatisfied_deps' => count($this->dependencyManager->getUnsatisfiedDependencies($nextJobId)), ]); } } } } $this->logger->info('Dependency resolution completed', [ 'trigger_job_id' => $jobId, 'resolved_jobs_count' => count($resolvedJobs), 'resolved_jobs' => array_column($resolvedJobs, 'job_id'), ]); return $resolvedJobs; } /** * Get all jobs that are ready to be executed (no unsatisfied dependencies) */ public function getExecutableJobs(): array { $readyJobs = $this->dependencyManager->getReadyJobs(); $this->logger->debug('Found jobs ready for execution', [ 'ready_jobs_count' => count($readyJobs), 'ready_jobs' => $readyJobs, ]); return $readyJobs; } /** * Validate a job dependency graph for circular dependencies */ public function validateDependencyGraph(array $jobIds): array { $validationResults = []; foreach ($jobIds as $jobId) { $hasCircularDeps = $this->dependencyManager->hasCircularDependencies($jobId); if ($hasCircularDeps) { $dependencyChain = $this->dependencyManager->getDependencyChain($jobId); $validationResults[] = [ 'job_id' => $jobId, 'valid' => false, 'error' => 'circular_dependency', 'dependency_chain' => $dependencyChain, ]; $this->logger->warning('Circular dependency detected', [ 'job_id' => $jobId, 'dependency_chain' => $dependencyChain, ]); } else { $validationResults[] = [ 'job_id' => $jobId, 'valid' => true, ]; } } return $validationResults; } /** * Get dependency analysis for a job */ public function analyzeDependencies(string $jobId): array { $dependencies = $this->dependencyManager->getDependencies($jobId); $dependents = $this->dependencyManager->getDependents($jobId); $unsatisfiedDeps = $this->dependencyManager->getUnsatisfiedDependencies($jobId); $dependencyChain = $this->dependencyManager->getDependencyChain($jobId); $canExecute = $this->dependencyManager->canJobBeExecuted($jobId); return [ 'job_id' => $jobId, 'can_execute' => $canExecute, 'direct_dependencies' => array_map(fn ($dep) => [ 'depends_on_job_id' => $dep->dependsOnJobId, 'dependency_type' => $dep->dependencyType, 'is_satisfied' => $dep->isSatisfied, 'condition' => $dep->conditionExpression, ], $dependencies), 'dependent_jobs' => array_map(fn ($dep) => [ 'dependent_job_id' => $dep->dependentJobId, 'dependency_type' => $dep->dependencyType, 'is_satisfied' => $dep->isSatisfied, ], $dependents), 'unsatisfied_dependencies' => array_map(fn ($dep) => [ 'depends_on_job_id' => $dep->dependsOnJobId, 'dependency_type' => $dep->dependencyType, 'condition' => $dep->conditionExpression, ], $unsatisfiedDeps), 'full_dependency_chain' => $dependencyChain, 'statistics' => [ 'total_dependencies' => count($dependencies), 'satisfied_dependencies' => count($dependencies) - count($unsatisfiedDeps), 'unsatisfied_dependencies' => count($unsatisfiedDeps), 'total_dependents' => count($dependents), 'chain_depth' => count($dependencyChain), ], ]; } /** * Get analysis for job chains */ public function analyzeChains(string $jobId): array { $chains = $this->chainManager->getChainsForJob($jobId); $chainAnalysis = []; foreach ($chains as $chainEntry) { $progress = $this->chainManager->getChainProgress($chainEntry->chainId); $nextJob = $this->chainManager->getNextJobInChain($chainEntry->chainId, $jobId); $chainAnalysis[] = [ 'chain_id' => $chainEntry->chainId, 'name' => $chainEntry->name, 'status' => $chainEntry->status, 'execution_mode' => $chainEntry->executionMode, 'stop_on_failure' => $chainEntry->stopOnFailure, 'progress' => $progress, 'next_job_after_current' => $nextJob, 'job_position' => $this->getJobPositionInChain($jobId, $chainEntry), 'total_jobs' => count($chainEntry->getJobIdsArray()), ]; } return [ 'job_id' => $jobId, 'chains' => $chainAnalysis, 'total_chains' => count($chains), ]; } /** * Comprehensive dependency health check */ public function performHealthCheck(): array { $this->logger->info('Starting dependency system health check'); $activeChains = $this->chainManager->getActiveChains(); $pendingChains = $this->chainManager->getPendingChains(); $readyJobs = $this->dependencyManager->getReadyJobs(); // Check for potential issues $issues = []; // Check for stalled chains (running for too long) foreach ($activeChains as $chain) { if ($chain->startedAt) { $startTime = strtotime($chain->startedAt); $hoursRunning = (time() - $startTime) / 3600; if ($hoursRunning > 24) { // Configurable threshold $issues[] = [ 'type' => 'stalled_chain', 'chain_id' => $chain->chainId, 'hours_running' => round($hoursRunning, 2), ]; } } } // Check for jobs with many unsatisfied dependencies (potential deadlocks) foreach ($readyJobs as $jobId) { $analysis = $this->analyzeDependencies($jobId); if ($analysis['statistics']['unsatisfied_dependencies'] > 10) { // Configurable threshold $issues[] = [ 'type' => 'many_unsatisfied_dependencies', 'job_id' => $jobId, 'unsatisfied_count' => $analysis['statistics']['unsatisfied_dependencies'], ]; } } $healthReport = [ 'status' => count($issues) === 0 ? 'healthy' : 'issues_detected', 'timestamp' => date('Y-m-d H:i:s'), 'statistics' => [ 'active_chains' => count($activeChains), 'pending_chains' => count($pendingChains), 'ready_jobs' => count($readyJobs), 'detected_issues' => count($issues), ], 'issues' => $issues, ]; $this->logger->info('Dependency system health check completed', [ 'status' => $healthReport['status'], 'issues_count' => count($issues), ]); return $healthReport; } private function getJobPositionInChain(string $jobId, object $chainEntry): ?int { $jobIds = $chainEntry->getJobIdsArray(); return array_search($jobId, $jobIds, true) ?: null; } }