[PHP]pcntl_wifexited関数の使い方|子プロセスが正常終了したかを正確に判定する方法

PHP

PHPでマルチプロセッシングを実装する際、子プロセスが正常に終了したのか、それともシグナルで強制終了されたのかを判定することは非常に重要です。そんな時に必要不可欠なのがpcntl_wifexited関数です。

この記事では、pcntl_wifexited関数の基本的な使い方から、実践的なプロセス状態管理システムの構築まで詳しく解説します。

pcntl_wifexited関数とは?

pcntl_wifexited()は、PHPのプロセス制御関数の一つで、pcntl_wait()またはpcntl_waitpid()から返された終了ステータスが、子プロセスが正常に終了(exit()で明示的に終了)したことを示しているかを確認する関数です。

基本構文

bool pcntl_wifexited(int $status)
  • パラメータ:
    • $status: pcntl_wait()またはpcntl_waitpid()から返された終了ステータス
  • 戻り値: 子プロセスが正常に終了した場合はtrue、そうでない場合はfalse
  • 重要: この関数がtrueを返す場合のみ、pcntl_wexitstatus()で終了コードを取得できます

終了タイプの分類

PHPでプロセスが終了する場合、以下の3つのタイプがあります:

  1. 正常終了: exit()で明示的に終了 → pcntl_wifexited()trueを返す
  2. シグナル終了: シグナルで強制終了 → pcntl_wifsignaled()trueを返す
  3. 停止状態: プロセスが一時停止中 → pcntl_wifstopped()trueを返す

基本的な使い方

シンプルな例

<?php
// 子プロセスの作成
$pid = pcntl_fork();

if ($pid === 0) {
    // 子プロセス
    echo "子プロセス実行中 (PID: " . getmypid() . ")\n";
    sleep(2);
    
    // 正常に終了
    exit(0);
    
} elseif ($pid > 0) {
    // 親プロセス
    $status = 0;
    pcntl_waitpid($pid, $status);
    
    // 終了タイプを判定
    if (pcntl_wifexited($status)) {
        // 正常終了
        $exitCode = pcntl_wexitstatus($status);
        echo "子プロセスが正常終了しました (終了コード: {$exitCode})\n";
        
    } elseif (pcntl_wifsignaled($status)) {
        // シグナルによる終了
        $signal = pcntl_wtermsig($status);
        echo "子プロセスがシグナル #{$signal} で強制終了されました\n";
        
    } elseif (pcntl_wifstopped($status)) {
        // 停止状態
        $signal = pcntl_wstopsig($status);
        echo "子プロセスが停止中です (シグナル #{$signal})\n";
    }
}
?>

終了タイプの判定フロー

<?php
function analyzeProcessStatus($status) {
    echo "=== プロセス終了ステータス分析 ===\n";
    echo "生のステータス値: {$status}\n\n";
    
    if (pcntl_wifexited($status)) {
        // === 正常終了 ===
        echo "結論: 子プロセスが正常に終了しました\n";
        
        $exitCode = pcntl_wexitstatus($status);
        echo "終了コード: {$exitCode}\n";
        
        // 終了コードの意味を解釈
        echo "解釈: ";
        switch ($exitCode) {
            case 0:
                echo "成功 (正常動作完了)";
                break;
            case 1:
                echo "一般的なエラー";
                break;
            case 2:
                echo "シェルの使用方法エラー";
                break;
            default:
                echo "カスタム終了コード #{$exitCode}";
        }
        echo "\n";
        
        return [
            'type' => 'normal_exit',
            'exit_code' => $exitCode,
            'description' => '正常終了'
        ];
        
    } elseif (pcntl_wifsignaled($status)) {
        // === シグナルによる終了 ===
        echo "結論: 子プロセスがシグナルで強制終了されました\n";
        
        $signal = pcntl_wtermsig($status);
        echo "シグナル番号: {$signal}\n";
        
        // シグナル名を表示
        $signalNames = [
            SIGHUP => 'SIGHUP',
            SIGINT => 'SIGINT',
            SIGQUIT => 'SIGQUIT',
            SIGILL => 'SIGILL',
            SIGTRAP => 'SIGTRAP',
            SIGABRT => 'SIGABRT',
            SIGFPE => 'SIGFPE',
            SIGKILL => 'SIGKILL',
            SIGSEGV => 'SIGSEGV',
            SIGPIPE => 'SIGPIPE',
            SIGALRM => 'SIGALRM',
            SIGTERM => 'SIGTERM'
        ];
        
        $signalName = $signalNames[$signal] ?? "SIGNAL #{$signal}";
        echo "シグナル名: {$signalName}\n";
        
        // コアダンプの確認
        if (function_exists('pcntl_wcoredump') && pcntl_wcoredump($status)) {
            echo "⚠️ コアダンプが生成されました\n";
        }
        
        return [
            'type' => 'signaled_exit',
            'signal' => $signal,
            'signal_name' => $signalName,
            'description' => "シグナル {$signalName} で強制終了"
        ];
        
    } elseif (pcntl_wifstopped($status)) {
        // === 停止状態 ===
        echo "結論: 子プロセスが停止中です\n";
        
        $signal = pcntl_wstopsig($status);
        echo "停止シグナル: {$signal}\n";
        
        return [
            'type' => 'stopped',
            'signal' => $signal,
            'description' => "シグナル #{$signal} で停止"
        ];
        
    } else {
        // === 不明な状態 ===
        echo "結論: プロセスの状態が不明です\n";
        
        return [
            'type' => 'unknown',
            'description' => '終了状態が判定できません'
        ];
    }
}

// 複数の異なる終了条件でテスト
echo "--- テスト1: 正常終了 ---\n";
$pid = pcntl_fork();
if ($pid === 0) {
    exit(0);
} else {
    $status = 0;
    pcntl_waitpid($pid, $status);
    $result1 = analyzeProcessStatus($status);
    echo "\n";
}

echo "--- テスト2: エラーコード付き終了 ---\n";
$pid = pcntl_fork();
if ($pid === 0) {
    exit(42);
} else {
    $status = 0;
    pcntl_waitpid($pid, $status);
    $result2 = analyzeProcessStatus($status);
    echo "\n";
}

echo "--- テスト3: シグナルによる強制終了 ---\n";
$pid = pcntl_fork();
if ($pid === 0) {
    sleep(10);
} else {
    usleep(500000); // 0.5秒後に終了
    posix_kill($pid, SIGTERM);
    $status = 0;
    pcntl_waitpid($pid, $status);
    $result3 = analyzeProcessStatus($status);
}
?>

実践的な活用例

1. 包括的なプロセス状態管理システム

<?php
class ComprehensiveProcessManager {
    private $processStates = [];
    private $processMetrics = [];
    private $exitHandler = null;
    
    public function __construct($exitHandler = null) {
        $this->exitHandler = $exitHandler;
        // シグナルハンドラーを設定
        pcntl_signal(SIGCHLD, [$this, 'handleChildSignal'], false);
    }
    
    public function handleChildSignal($signo) {
        // 非ブロッキングですべての子プロセスをチェック
        while (true) {
            $pid = pcntl_waitpid(-1, $status, WNOHANG);
            
            if ($pid <= 0) break;
            
            if (isset($this->processStates[$pid])) {
                $this->categorizeProcessExit($pid, $status);
            }
        }
    }
    
    public function startProcess($callback, $processName = 'unnamed') {
        $pid = pcntl_fork();
        
        if ($pid === 0) {
            // 子プロセス
            try {
                $result = $callback();
                exit($result === true ? 0 : 1);
            } catch (Exception $e) {
                error_log("プロセスエラー: " . $e->getMessage());
                exit(1);
            }
            
        } elseif ($pid > 0) {
            // 親プロセス
            $this->processStates[$pid] = [
                'name' => $processName,
                'start_time' => microtime(true),
                'status' => 'running',
                'exit_type' => null,
                'exit_details' => null
            ];
            
            return $pid;
        }
        
        return false;
    }
    
    private function categorizeProcessExit($pid, $status) {
        $startTime = microtime(true);
        
        $this->processStates[$pid]['exit_status_code'] = $status;
        $this->processStates[$pid]['status'] = 'completed';
        $this->processStates[$pid]['completion_time'] = microtime(true);
        $this->processStates[$pid]['duration'] = 
            $this->processStates[$pid]['completion_time'] - $this->processStates[$pid]['start_time'];
        
        // === 状態分類 ===
        
        if (pcntl_wifexited($status)) {
            // 正常終了
            $exitCode = pcntl_wexitstatus($status);
            
            $this->processStates[$pid]['exit_type'] = 'normal_exit';
            $this->processStates[$pid]['exit_details'] = [
                'method' => '正常終了(exit())',
                'exit_code' => $exitCode,
                'success' => ($exitCode === 0),
                'interpretation' => $this->interpretExitCode($exitCode),
                'category' => $this->categorizeExitCode($exitCode)
            ];
            
            // ログ出力
            $name = $this->processStates[$pid]['name'];
            if ($exitCode === 0) {
                echo "✓ {$name} (PID:{$pid}) が正常終了しました\n";
            } else {
                echo "✗ {$name} (PID:{$pid}) がエラーコード {$exitCode} で終了しました\n";
            }
            
        } elseif (pcntl_wifsignaled($status)) {
            // シグナルによる終了
            $signal = pcntl_wtermsig($status);
            $coredump = function_exists('pcntl_wcoredump') ? pcntl_wcoredump($status) : false;
            
            $this->processStates[$pid]['exit_type'] = 'signaled_exit';
            $this->processStates[$pid]['exit_details'] = [
                'method' => 'シグナルで強制終了',
                'signal_number' => $signal,
                'signal_name' => $this->getSignalName($signal),
                'core_dump' => $coredump,
                'signal_description' => $this->getSignalDescription($signal)
            ];
            
            // ログ出力
            $name = $this->processStates[$pid]['name'];
            $signalName = $this->getSignalName($signal);
            echo "⚠ {$name} (PID:{$pid}) がシグナル {$signalName}({$signal}) で強制終了\n";
            
        } elseif (pcntl_wifstopped($status)) {
            // 停止状態
            $signal = pcntl_wstopsig($status);
            
            $this->processStates[$pid]['exit_type'] = 'stopped';
            $this->processStates[$pid]['exit_details'] = [
                'method' => '一時停止',
                'stop_signal' => $signal,
                'signal_name' => $this->getSignalName($signal),
                'signal_description' => $this->getSignalDescription($signal)
            ];
            
            // ログ出力
            $name = $this->processStates[$pid]['name'];
            echo "⏸ {$name} (PID:{$pid}) が一時停止中\n";
            
        } else {
            // 不明な状態
            $this->processStates[$pid]['exit_type'] = 'unknown';
            $this->processStates[$pid]['exit_details'] = [
                'method' => '不明',
                'description' => '終了状態が判定できません'
            ];
        }
        
        // カスタム終了ハンドラーを実行
        if ($this->exitHandler !== null) {
            try {
                call_user_func($this->exitHandler, $pid, $this->processStates[$pid]);
            } catch (Exception $e) {
                error_log("終了ハンドラーエラー: " . $e->getMessage());
            }
        }
        
        // パフォーマンスメトリクスを記録
        $this->recordMetrics($pid);
    }
    
    private function interpretExitCode($code) {
        $interpretations = [
            0 => '成功 - 正常に動作完了',
            1 => '一般エラー - エラーが発生',
            2 => 'シェル誤用 - コマンド使用方法エラー',
            126 => 'コマンド不可 - 実行権限なし',
            127 => 'コマンドなし - プログラムが見つかりません',
            128 => '無効な引数 - 引数エラー',
            130 => 'SIGINT - Ctrl+Cで中断',
            255 => '範囲外 - 終了コード範囲外'
        ];
        
        return $interpretations[$code] ?? "カスタムコード #{$code}";
    }
    
    private function categorizeExitCode($code) {
        if ($code === 0) {
            return 'success';
        } elseif ($code < 128) {
            return 'error';
        } else {
            return 'signal_derived';
        }
    }
    
    private function getSignalName($signal) {
        $names = [
            SIGHUP => 'SIGHUP',
            SIGINT => 'SIGINT',
            SIGQUIT => 'SIGQUIT',
            SIGILL => 'SIGILL',
            SIGTRAP => 'SIGTRAP',
            SIGABRT => 'SIGABRT',
            SIGFPE => 'SIGFPE',
            SIGKILL => 'SIGKILL',
            SIGSEGV => 'SIGSEGV',
            SIGPIPE => 'SIGPIPE',
            SIGALRM => 'SIGALRM',
            SIGTERM => 'SIGTERM',
            SIGSTOP => 'SIGSTOP',
            SIGTSTP => 'SIGTSTP'
        ];
        
        return $names[$signal] ?? "SIGNAL#{$signal}";
    }
    
    private function getSignalDescription($signal) {
        $descriptions = [
            SIGHUP => 'ハングアップ検出 - 端末接続喪失',
            SIGINT => 'インタラプト - Ctrl+C で送信',
            SIGQUIT => '終了 - Ctrl+\ で送信',
            SIGILL => '不正命令 - 非法的な命令実行',
            SIGTRAP => 'トラップ - デバッガ用',
            SIGABRT => '中止 - abort()で送信',
            SIGFPE => '浮動小数点例外 - ゼロ除算など',
            SIGKILL => '強制終了 - 必ず終了',
            SIGSEGV => 'セグメンテーション違反 - メモリアクセス失敗',
            SIGPIPE => 'パイプ破損 - パイプ相手が終了',
            SIGALRM => 'アラーム - alarm()で設定したタイマー',
            SIGTERM => '終了シグナル - 正常終了要求',
            SIGSTOP => '停止 - 必ず一時停止',
            SIGTSTP => 'キーボード停止 - Ctrl+Z で送信'
        ];
        
        return $descriptions[$signal] ?? "シグナル #{$signal}";
    }
    
    private function recordMetrics($pid) {
        $state = $this->processStates[$pid];
        
        $this->processMetrics[] = [
            'pid' => $pid,
            'name' => $state['name'],
            'duration' => $state['duration'],
            'exit_type' => $state['exit_type'],
            'success' => $state['exit_details']['success'] ?? false,
            'timestamp' => time()
        ];
    }
    
    public function waitForAllProcesses() {
        while (!empty($this->processStates)) {
            $running = array_filter($this->processStates, function($proc) {
                return $proc['status'] === 'running';
            });
            
            if (empty($running)) break;
            
            pcntl_signal_dispatch();
            usleep(100000); // 0.1秒待機
        }
    }
    
    public function getProcessState($pid) {
        return $this->processStates[$pid] ?? null;
    }
    
    public function getAllProcessStates() {
        return $this->processStates;
    }
    
    public function getMetrics() {
        return $this->processMetrics;
    }
    
    public function generateReport() {
        $report = [
            'summary' => [
                'total_processes' => count($this->processStates),
                'successful_processes' => 0,
                'failed_processes' => 0,
                'signaled_processes' => 0,
                'stopped_processes' => 0,
                'total_runtime' => 0
            ],
            'details' => []
        ];
        
        foreach ($this->processStates as $pid => $state) {
            $report['details'][$pid] = $state;
            
            // 統計計算
            $report['summary']['total_runtime'] += $state['duration'] ?? 0;
            
            switch ($state['exit_type']) {
                case 'normal_exit':
                    if ($state['exit_details']['success']) {
                        $report['summary']['successful_processes']++;
                    } else {
                        $report['summary']['failed_processes']++;
                    }
                    break;
                case 'signaled_exit':
                    $report['summary']['signaled_processes']++;
                    break;
                case 'stopped':
                    $report['summary']['stopped_processes']++;
                    break;
            }
        }
        
        return $report;
    }
}

// 使用例
echo "=== 包括的なプロセス管理デモ ===\n\n";

$manager = new ComprehensiveProcessManager(
    function($pid, $processState) {
        // 終了時のコールバック処理
        $details = $processState['exit_details'];
        
        // 失敗時に追加処理が必要な場合
        if (isset($details['success']) && !$details['success']) {
            error_log("プロセス失敗の詳細: " . json_encode($details));
        }
    }
);

// 異なるシナリオでプロセスを実行
echo "--- テスト1: 成功するプロセス ---\n";
$manager->startProcess(function() {
    echo "  処理実行中...\n";
    sleep(1);
    echo "  処理完了\n";
    return true;
}, 'SuccessTask');

echo "\n--- テスト2: エラーで終了するプロセス ---\n";
$manager->startProcess(function() {
    echo "  エラー処理中...\n";
    sleep(1);
    throw new Exception("意図的なエラー");
}, 'ErrorTask');

echo "\n--- テスト3: カスタム終了コード ---\n";
$manager->startProcess(function() {
    echo "  特別な処理...\n";
    sleep(1);
    exit(42); // カスタム終了コード
}, 'CustomCodeTask');

// すべてのプロセス完了を待つ
echo "\nすべてのプロセスの完了を待機中...\n";
$manager->waitForAllProcesses();

// レポート表示
echo "\n=== 実行レポート ===\n";
$report = $manager->generateReport();

echo "総プロセス数: " . $report['summary']['total_processes'] . "\n";
echo "成功: " . $report['summary']['successful_processes'] . "\n";
echo "失敗: " . $report['summary']['failed_processes'] . "\n";
echo "シグナル終了: " . $report['summary']['signaled_processes'] . "\n";
echo "一時停止中: " . $report['summary']['stopped_processes'] . "\n";
echo "総実行時間: " . round($report['summary']['total_runtime'], 2) . "秒\n";

echo "\n=== 詳細情報 ===\n";
foreach ($report['details'] as $pid => $state) {
    echo "\nPID {$pid} ({$state['name']}):\n";
    echo "  終了タイプ: {$state['exit_type']}\n";
    echo "  実行時間: " . round($state['duration'], 2) . "秒\n";
    
    if ($state['exit_details']) {
        echo "  詳細:\n";
        foreach ($state['exit_details'] as $key => $value) {
            if (is_array($value)) {
                $value = json_encode($value);
            }
            echo "    - {$key}: {$value}\n";
        }
    }
}
?>

2. 条件付きプロセス継続システム

<?php
class ConditionalProcessContinuation {
    private $processes = [];
    private $continuationRules = [];
    
    public function addContinuationRule($sourceName, $exitCondition, $nextTaskName, $nextCallback) {
        $this->continuationRules[] = [
            'source' => $sourceName,
            'exit_condition' => $exitCondition,
            'next_task' => $nextTaskName,
            'next_callback' => $nextCallback
        ];
    }
    
    public function executeConditionalChain($initialTask, $initialCallback) {
        $currentTask = $initialTask;
        $currentCallback = $initialCallback;
        $chainLog = [];
        
        while ($currentTask !== null) {
            echo "\n>>> タスク '{$currentTask}' を実行\n";
            
            // プロセスを実行
            $pid = pcntl_fork();
            
            if ($pid === 0) {
                // 子プロセス
                try {
                    $result = $currentCallback();
                    exit($result === true ? 0 : 1);
                } catch (Exception $e) {
                    error_log("タスク '{$currentTask}' エラー: " . $e->getMessage());
                    exit(1);
                }
                
            } elseif ($pid > 0) {
                // 親プロセス
                $status = 0;
                pcntl_waitpid($pid, $status);
                
                $chainLog[] = [
                    'task' => $currentTask,
                    'pid' => $pid,
                    'status' => $status
                ];
                
                // 次のタスクを決定
                $nextTask = $this->determineNextTask($currentTask, $status);
                
                if ($nextTask !== null) {
                    $currentTask = $nextTask['name'];
                    $currentCallback = $nextTask['callback'];
                } else {
                    $currentTask = null;
                }
                
            } else {
                echo "プロセス作成失敗\n";
                break;
            }
        }
        
        return [
            'completion_type' => 'chain_complete',
            'chain_log' => $chainLog,
            'total_tasks' => count($chainLog)
        ];
    }
    
    private function determineNextTask($currentTask, $status) {
        foreach ($this->continuationRules as $rule) {
            if ($rule['source'] !== $currentTask) {
                continue;
            }
            
            // 終了条件をチェック
            if ($rule['exit_condition']($status)) {
                return [
                    'name' => $rule['next_task'],
                    'callback' => $rule['next_callback']
                ];
            }
        }
        
        return null;
    }
}

// 使用例:条件付きパイプライン
echo "=== 条件付きプロセスチェーン実行 ===\n";

$executor = new ConditionalProcessContinuation();

// ルール定義:タスク1が成功 → タスク2を実行
$executor->addContinuationRule(
    'task_1',
    function($status) {
        return pcntl_wifexited($status) && pcntl_wexitstatus($status) === 0;
    },
    'task_2',
    function() {
        echo "タスク2: 前のタスクが成功したので実行\n";
        sleep(1);
        return true;
    }
);

// ルール定義:タスク2が成功 → タスク3を実行
$executor->addContinuationRule(
    'task_2',
    function($status) {
        return pcntl_wifexited($status) && pcntl_wexitstatus($status) === 0;
    },
    'task_3',
    function() {
        echo "タスク3: 最終タスク\n";
        sleep(1);
        return true;
    }
);

// チェーン実行
$result = $executor->executeConditionalChain(
    'task_1',
    function() {
        echo "タスク1: 初期タスク\n";
        sleep(1);
        return true;
    }
);

echo "\n=== チェーン実行結果 ===\n";
echo "実行タスク数: " . $result['total_tasks'] . "\n";
foreach ($result['chain_log'] as $index => $log) {
    echo "  " . ($index + 1) . ". {$log['task']} (PID: {$log['pid']})\n";
}
?>

3. プロセス終了状態のリアルタイム監視

<?php
class RealTimeProcessMonitor {
    private $processes = [];
    private $monitoringActive = false;
    
    public function startProcessWithMonitoring($callback, $processName) {
        $pid = pcntl_fork();
        
        if ($pid === 0) {
            // 子プロセス
            try {
                $callback();
                exit(0);
            } catch (Exception $e) {
                exit(1);
            }
            
        } elseif ($pid > 0) {
            // 親プロセス
            $this->processes[$pid] = [
                'name' => $processName,
                'start_time' => microtime(true),
                'status_history' => [],
                'is_running' => true
            ];
            
            return $pid;
        }
        
        return false;
    }
    
    public function monitorAllProcesses() {
        ob_implicit_flush(true);
        
        while (!empty(array_filter($this->processes, function($p) {
            return $p['is_running'];
        }))) {
            echo "\r[" . date('H:i:s') . "] 監視中: " . $this->getProcessSummary() . "   ";
            
            // 非ブロッキングで終了を確認
            $pid = pcntl_waitpid(-1, $status, WNOHANG);
            
            if ($pid > 0 && isset($this->processes[$pid])) {
                $this->updateProcessStatus($pid, $status);
            }
            
            usleep(500000); // 0.5秒ごと
        }
        
        echo "\n\n=== 監視完了 ===\n";
    }
    
    private function updateProcessStatus($pid, $status) {
        if (!isset($this->processes[$pid])) {
            return;
        }
        
        $process = &$this->processes[$pid];
        $statusEntry = [
            'timestamp' => microtime(true),
            'exit_type' => null,
            'details' => null
        ];
        
        if (pcntl_wifexited($status)) {
            // 正常終了
            $exitCode = pcntl_wexitstatus($status);
            $statusEntry['exit_type'] = 'normal_exit';
            $statusEntry['details'] = [
                'exit_code' => $exitCode,
                'success' => ($exitCode === 0)
            ];
            
            echo "\n✓ {$process['name']} が正常終了(コード: {$exitCode})";
            
        } elseif (pcntl_wifsignaled($status)) {
            // シグナル終了
            $signal = pcntl_wtermsig($status);
            $statusEntry['exit_type'] = 'signaled';
            $statusEntry['details'] = ['signal' => $signal];
            
            echo "\n⚠ {$process['name']} がシグナル #{$signal} で終了";
            
        } elseif (pcntl_wifstopped($status)) {
            // 停止状態
            $signal = pcntl_wstopsig($status);
            $statusEntry['exit_type'] = 'stopped';
            $statusEntry['details'] = ['signal' => $signal];
            
            echo "\n⏸ {$process['name']} が停止中(シグナル: #{$signal})";
        }
        
        $process['status_history'][] = $statusEntry;
        $process['is_running'] = false;
        $process['end_time'] = microtime(true);
        $process['duration'] = $process['end_time'] - $process['start_time'];
    }
    
    private function getProcessSummary() {
        $running = count(array_filter($this->processes, function($p) {
            return $p['is_running'];
        }));
        $completed = count($this->processes) - $running;
        
        return "実行中: {$running}, 完了: {$completed}, 合計: " . count($this->processes);
    }
    
    public function getMonitoringReport() {
        $report = [
            'total_processes' => count($this->processes),
            'completed_processes' => 0,
            'summary_by_exit_type' => [
                'normal_exit' => 0,
                'signaled' => 0,
                'stopped' => 0,
                'unknown' => 0
            ],
            'success_statistics' => [
                'successful' => 0,
                'failed' => 0,
                'success_rate' => 0
            ],
            'timing_statistics' => [
                'total_runtime' => 0,
                'average_runtime' => 0,
                'fastest' => null,
                'slowest' => null
            ],
            'processes' => []
        ];
        
        $runtimes = [];
        
        foreach ($this->processes as $pid => $process) {
            $report['completed_processes']++;
            
            if (empty($process['status_history'])) {
                $report['summary_by_exit_type']['unknown']++;
                continue;
            }
            
            $lastStatus = end($process['status_history']);
            $exitType = $lastStatus['exit_type'];
            $report['summary_by_exit_type'][$exitType]++;
            
            if ($exitType === 'normal_exit' && $lastStatus['details']['success']) {
                $report['success_statistics']['successful']++;
            } else {
                $report['success_statistics']['failed']++;
            }
            
            if (isset($process['duration'])) {
                $runtimes[] = $process['duration'];
                $report['timing_statistics']['total_runtime'] += $process['duration'];
            }
            
            $report['processes'][$pid] = [
                'name' => $process['name'],
                'duration' => $process['duration'] ?? 0,
                'exit_type' => $lastStatus['exit_type'],
                'details' => $lastStatus['details']
            ];
        }
        
        // 統計計算
        if (!empty($runtimes)) {
            $report['timing_statistics']['average_runtime'] = array_sum($runtimes) / count($runtimes);
            $report['timing_statistics']['fastest'] = min($runtimes);
            $report['timing_statistics']['slowest'] = max($runtimes);
        }
        
        $total = $report['success_statistics']['successful'] + $report['success_statistics']['failed'];
        if ($total > 0) {
            $report['success_statistics']['success_rate'] = 
                round(($report['success_statistics']['successful'] / $total) * 100, 1);
        }
        
        return $report;
    }
}

// 使用例:リアルタイム監視
echo "=== リアルタイムプロセス監視デモ ===\n";

$monitor = new RealTimeProcessMonitor();

// 複数のプロセスを開始
echo "プロセスを起動中...\n";

for ($i = 1; $i <= 4; $i++) {
    $monitor->startProcessWithMonitoring(
        function() use ($i) {
            sleep(rand(1, 3));
            if ($i === 3) {
                // プロセス3はエラーで終了
                exit(1);
            }
            return true;
        },
        "Process-{$i}"
    );
}

// リアルタイムで監視
$monitor->monitorAllProcesses();

// レポート表示
echo "\n=== 監視レポート ===\n";
$report = $monitor->getMonitoringReport();

echo "総プロセス数: " . $report['total_processes'] . "\n";
echo "完了プロセス: " . $report['completed_processes'] . "\n\n";

echo "終了タイプ別:\n";
foreach ($report['summary_by_exit_type'] as $type => $count) {
    echo "  {$type}: {$count}\n";
}

echo "\n成功統計:\n";
echo "  成功: " . $report['success_statistics']['successful'] . "\n";
echo "  失敗: " . $report['success_statistics']['failed'] . "\n";
echo "  成功率: " . $report['success_statistics']['success_rate'] . "%\n";

echo "\n実行時間統計:\n";
echo "  総計: " . round($report['timing_statistics']['total_runtime'], 2) . "秒\n";
echo "  平均: " . round($report['timing_statistics']['average_runtime'], 2) . "秒\n";
echo "  最速: " . round($report['timing_statistics']['fastest'], 2) . "秒\n";
echo "  最遅: " . round($report['timing_statistics']['slowest'], 2) . "秒\n";

echo "\n=== プロセス詳細 ===\n";
foreach ($report['processes'] as $pid => $proc) {
    echo "{$proc['name']} (PID: {$pid}):\n";
    echo "  実行時間: " . round($proc['duration'], 2) . "秒\n";
    echo "  終了タイプ: {$proc['exit_type']}\n";
    
    if ($proc['exit_type'] === 'normal_exit') {
        $status = $proc['details']['success'] ? '成功' : '失敗';
        echo "  ステータス: {$status}\n";
    }
}
?>

ベストプラクティスと注意点

1. 正しいステータス判定フロー

<?php
class ProperStatusCheckingFlowExample {
    
    public static function correctFlow($status) {
        echo "=== 正しいステータス判定フロー ===\n\n";
        
        // ステップ1:まず正常終了をチェック
        if (pcntl_wifexited($status)) {
            echo "✓ ステップ1: pcntl_wifexited() = true\n";
            echo "  → 子プロセスが正常に終了しました\n\n";
            
            // ステップ2:終了コードを取得(wifexitedがtrueの場合のみ可)
            $exitCode = pcntl_wexitstatus($status);
            echo "✓ ステップ2: pcntl_wexitstatus() = {$exitCode}\n";
            echo "  → 終了コードが取得できました\n\n";
            
            // ステップ3:終了コードの意味を解釈
            echo "✓ ステップ3: 終了コードの解釈\n";
            echo "  → コード {$exitCode} は " . 
                 ($exitCode === 0 ? '成功' : 'エラー') . "\n";
            
            return 'normal_exit';
            
        } elseif (pcntl_wifsignaled($status)) {
            // ステップ1の判定がfalseの場合、次はシグナル終了をチェック
            echo "✓ ステップ1: pcntl_wifexited() = false\n";
            echo "✓ ステップ2: pcntl_wifsignaled() = true\n";
            echo "  → 子プロセスがシグナルで強制終了されました\n\n";
            
            $signal = pcntl_wtermsig($status);
            echo "✓ ステップ3: pcntl_wtermsig() = {$signal}\n";
            echo "  → シグナル番号が取得できました\n";
            
            return 'signaled_exit';
            
        } elseif (pcntl_wifstopped($status)) {
            // その他:停止状態
            echo "✓ ステップ1: pcntl_wifexited() = false\n";
            echo "✓ ステップ2: pcntl_wifsignaled() = false\n";
            echo "✓ ステップ3: pcntl_wifstopped() = true\n";
            echo "  → 子プロセスが一時停止中です\n";
            
            return 'stopped';
        }
        
        // 予期しない状態
        echo "✗ どの条件にも該当しません\n";
        return 'unknown';
    }
    
    public static function badFlow($status) {
        echo "=== 誤った判定方法(アンチパターン) ===\n\n";
        
        // ❌ 誤り1:wifexitedをチェックせずにwexitstatusを呼ぶ
        echo "❌ 誤り1: pcntl_wifexited()をチェックせずにwexitstatus()を呼ぶ\n";
        echo "  問題: シグナル終了の場合、意味不明な値を返します\n";
        echo "  結果: \$exitCode = " . pcntl_wexitstatus($status) . "\n\n";
        
        // ❌ 誤り2:単にステータス値で判定
        echo "❌ 誤り2: ステータス値を直接比較\n";
        echo "  悪い例: if (\$status === 0) { ... }\n";
        echo "  問題: ステータス値は複合情報を含んでいます\n";
        echo "  正しい例: if (pcntl_wifexited(\$status) && pcntl_wexitstatus(\$status) === 0)\n\n";
        
        // ❌ 誤り3:複数のチェック関数を同時に呼ぶ
        echo "❌ 誤り3: 複数のチェック関数を条件なしに呼ぶ\n";
        echo "  悪い例:\n";
        echo "    \$code = pcntl_wexitstatus(\$status);\n";
        echo "    \$signal = pcntl_wtermsig(\$status);\n";
        echo "  問題: 不適切な値が混在します\n";
    }
}

// デモ実行
$pid = pcntl_fork();
if ($pid === 0) {
    exit(42);
} else {
    $status = 0;
    pcntl_waitpid($pid, $status);
    
    echo "実例での判定:\n";
    ProperStatusCheckingFlowExample::correctFlow($status);
}
?>

2. パフォーマンス最適化のベストプラクティス

<?php
class OptimizedProcessHandling {
    
    public static function efficientNonBlockingWait() {
        echo "=== 効率的な非ブロッキング待機 ===\n";
        
        // 複数プロセスを起動
        $pids = [];
        for ($i = 1; $i <= 5; $i++) {
            $pid = pcntl_fork();
            if ($pid === 0) {
                sleep(rand(1, 3));
                exit(0);
            } elseif ($pid > 0) {
                $pids[$pid] = "Process-{$i}";
            }
        }
        
        $completed = [];
        $startTime = microtime(true);
        
        // 非ブロッキングで全プロセスの完了を待つ
        while (!empty($pids)) {
            $status = 0;
            // WNOHANG: ブロッキングしない
            $completedPid = pcntl_waitpid(-1, $status, WNOHANG);
            
            if ($completedPid > 0) {
                // プロセスが完了
                if (pcntl_wifexited($status)) {
                    $exitCode = pcntl_wexitstatus($status);
                    $completed[$completedPid] = $exitCode;
                    unset($pids[$completedPid]);
                    
                    echo "✓ {$pids[$completedPid]} が完了\n";
                }
                
            } elseif ($completedPid === 0) {
                // まだ完了していないプロセスがある
                // CPU使用を抑えるため少し待機
                usleep(100000); // 0.1秒
            } else {
                // エラーまたはプロセスなし
                break;
            }
        }
        
        $duration = microtime(true) - $startTime;
        echo "\n全プロセス完了: " . round($duration, 2) . "秒\n";
        echo "完了したプロセス数: " . count($completed) . "\n";
    }
}

OptimizedProcessHandling::efficientNonBlockingWait();
?>

まとめ

pcntl_wifexited関数は、PHPのマルチプロセッシング機能において、子プロセスの終了状態を正確に判定するために必須の関数です。

✨ 主要なメリット

  • 精密な判定: 終了タイプを正確に区別
  • エラー検出: 異常終了の早期発見
  • 制御フロー: 終了コードに基づいた処理分岐
  • 監視機能: リアルタイムなプロセス状態把握

🚀 実践的応用分野

  • バッチ処理: 複数タスクの並列実行
  • ワーカーシステム: マルチワーカーの健全性管理
  • パイプライン: 条件付きタスク実行
  • 監視システム: プロセスの継続的監視

💡 技術的価値

  • 堅牢性: すべての終了パターンに対応
  • 効率性: 非ブロッキング処理による高速化
  • 可靠性: 完全なプロセス制御
  • スケーラビリティ: 大規模並列処理対応

pcntl_wifexitedを活用することで、PHPで本格的かつ信頼性の高いマルチプロセッシングシステムを構築できるでしょう。

タイトルとURLをコピーしました