[PHP]pcntl_strerror関数とは?エラーメッセージを取得する方法を徹底解説

PHP

こんにちは!今回はPHPのプロセス制御におけるエラー処理に欠かせないpcntl_strerror関数について詳しく解説します。エラー番号だけでは分かりにくいPCNTLエラーを、人間が読める形式で取得したい方は必見です!

pcntl_strerror関数とは?

pcntl_strerror()は、エラー番号(errno)を受け取り、対応するエラーメッセージ文字列を返す関数です。PCNTLプロセス制御関数で発生したエラーを、開発者やユーザーが理解しやすい形式で表示するために使用します。

基本構文

pcntl_strerror(int $error_code): string
  • $error_code: エラー番号(errno)
  • 戻り値: エラーメッセージ文字列

pcntl_get_last_errorとの関係

pcntl_strerror()は、通常pcntl_get_last_error()と組み合わせて使用します:

// エラー番号を取得
$errno = pcntl_get_last_error();

// エラーメッセージに変換
$errmsg = pcntl_strerror($errno);

echo "エラー: [{$errno}] {$errmsg}\n";

処理の流れ

[PCNTL関数実行] → [エラー発生] → [pcntl_get_last_error()] → [番号取得]
                                          ↓

[pcntl_strerror()]

↓ [メッセージ取得]

主要なエラーコードとメッセージ

エラー番号定数名メッセージ例意味
1EPERMOperation not permitted操作が許可されていない
3ESRCHNo such processプロセスが存在しない
4EINTRInterrupted system callシステムコールが中断された
10ECHILDNo child processes子プロセスが存在しない
11EAGAINResource temporarily unavailableリソースが一時的に利用不可
12ENOMEMCannot allocate memoryメモリを割り当てられない
22EINVALInvalid argument無効な引数

実践的なコード例

基本的な使い方

<?php
// フォークを試みる
$pid = pcntl_fork();

if ($pid === -1) {
    // エラー発生
    $errno = pcntl_get_last_error();
    $errmsg = pcntl_strerror($errno);
    
    echo "フォークに失敗しました\n";
    echo "エラー番号: {$errno}\n";
    echo "エラー内容: {$errmsg}\n";
    exit(1);
    
} elseif ($pid) {
    // 親プロセス
    echo "子プロセスを生成しました: PID={$pid}\n";
    pcntl_wait($status);
    
} else {
    // 子プロセス
    echo "子プロセス実行中\n";
    exit(0);
}
?>

エラーハンドリングを強化した関数

<?php
function safe_pcntl_fork() {
    $pid = pcntl_fork();
    
    if ($pid === -1) {
        $errno = pcntl_get_last_error();
        $errmsg = pcntl_strerror($errno);
        
        // 詳細なエラーログ
        error_log(sprintf(
            "Fork failed: errno=%d, error='%s', time=%s",
            $errno,
            $errmsg,
            date('Y-m-d H:i:s')
        ));
        
        throw new RuntimeException(
            "プロセスのフォークに失敗: {$errmsg} (errno: {$errno})"
        );
    }
    
    return $pid;
}

// 使用例
try {
    $pid = safe_pcntl_fork();
    
    if ($pid > 0) {
        echo "親プロセス: 子PID={$pid}\n";
        pcntl_wait($status);
    } else {
        echo "子プロセス実行中\n";
        sleep(2);
        exit(0);
    }
    
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

waitpidのエラー処理

<?php
class ProcessWaiter {
    public function waitForChild($pid) {
        echo "子プロセス(PID:{$pid})の終了を待機中...\n";
        
        $result = pcntl_waitpid($pid, $status);
        
        if ($result === -1) {
            // エラー発生
            $errno = pcntl_get_last_error();
            $errmsg = pcntl_strerror($errno);
            
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
            echo "エラー情報:\n";
            echo "  関数: pcntl_waitpid\n";
            echo "  対象PID: {$pid}\n";
            echo "  エラー番号: {$errno}\n";
            echo "  エラー内容: {$errmsg}\n";
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
            
            // エラー種別に応じた対応
            switch ($errno) {
                case ECHILD:
                    echo "→ 子プロセスが存在しません\n";
                    break;
                case EINVAL:
                    echo "→ 無効な引数が指定されました\n";
                    break;
                case EINTR:
                    echo "→ システムコールが中断されました\n";
                    break;
                default:
                    echo "→ 不明なエラーです\n";
            }
            
            return false;
        }
        
        echo "子プロセスが正常に終了しました\n";
        return true;
    }
}

// テスト
$pid = pcntl_fork();

if ($pid > 0) {
    // 親プロセス
    $waiter = new ProcessWaiter();
    $waiter->waitForChild($pid);
    
    // 存在しないPIDで試す(エラーが発生)
    echo "\n存在しないPIDで試します:\n";
    $waiter->waitForChild(99999);
    
} elseif ($pid === 0) {
    // 子プロセス
    sleep(1);
    exit(0);
}
?>

シグナル処理のエラー対応

<?php
class SignalHandler {
    private $handlers = [];
    
    public function register($signal, $callback) {
        $result = pcntl_signal($signal, $callback);
        
        if (!$result) {
            $errno = pcntl_get_last_error();
            $errmsg = pcntl_strerror($errno);
            
            throw new RuntimeException(
                "シグナルハンドラの登録に失敗: {$errmsg} (signal: {$signal})"
            );
        }
        
        $this->handlers[$signal] = true;
        echo "✓ シグナル{$signal}のハンドラを登録しました\n";
        return true;
    }
    
    public function dispatch() {
        $result = pcntl_signal_dispatch();
        
        if (!$result) {
            $errno = pcntl_get_last_error();
            $errmsg = pcntl_strerror($errno);
            
            error_log("Signal dispatch failed: {$errmsg}");
            return false;
        }
        
        return true;
    }
}

try {
    $handler = new SignalHandler();
    
    $handler->register(SIGTERM, function() {
        echo "SIGTERMを受信しました\n";
        exit(0);
    });
    
    $handler->register(SIGINT, function() {
        echo "SIGINTを受信しました\n";
        exit(0);
    });
    
    echo "シグナル待機中...\n";
    
    while (true) {
        sleep(1);
        $handler->dispatch();
    }
    
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

エラーログ記録システム

<?php
class PCNTLErrorLogger {
    private $log_file;
    
    public function __construct($log_file = 'pcntl_errors.log') {
        $this->log_file = $log_file;
    }
    
    public function logError($function_name, $context = []) {
        $errno = pcntl_get_last_error();
        
        if ($errno === 0) {
            return; // エラーなし
        }
        
        $errmsg = pcntl_strerror($errno);
        
        $log_entry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'function' => $function_name,
            'errno' => $errno,
            'error' => $errmsg,
            'pid' => getmypid(),
            'context' => $context
        ];
        
        $log_line = json_encode($log_entry, JSON_UNESCAPED_UNICODE) . "\n";
        file_put_contents($this->log_file, $log_line, FILE_APPEND);
        
        return $log_entry;
    }
    
    public function displayError($error_info) {
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
        echo "PCNTLエラー発生\n";
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
        echo "時刻: {$error_info['timestamp']}\n";
        echo "関数: {$error_info['function']}\n";
        echo "エラー番号: {$error_info['errno']}\n";
        echo "エラー内容: {$error_info['error']}\n";
        echo "プロセスID: {$error_info['pid']}\n";
        
        if (!empty($error_info['context'])) {
            echo "コンテキスト:\n";
            foreach ($error_info['context'] as $key => $value) {
                echo "  {$key}: {$value}\n";
            }
        }
        
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
    }
}

// 使用例
$logger = new PCNTLErrorLogger();

// フォークを試みる
$pid = pcntl_fork();

if ($pid === -1) {
    $error_info = $logger->logError('pcntl_fork', [
        'attempt' => 1,
        'memory_usage' => memory_get_usage()
    ]);
    
    $logger->displayError($error_info);
    exit(1);
}

if ($pid > 0) {
    // 親プロセス
    $result = pcntl_waitpid($pid, $status);
    
    if ($result === -1) {
        $error_info = $logger->logError('pcntl_waitpid', [
            'target_pid' => $pid
        ]);
        
        $logger->displayError($error_info);
    }
    
} else {
    // 子プロセス
    exit(0);
}
?>

全てのエラーコードを表示

<?php
echo "PCNTLエラーコード一覧\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";

$error_codes = [
    1 => 'EPERM',
    2 => 'ENOENT',
    3 => 'ESRCH',
    4 => 'EINTR',
    5 => 'EIO',
    7 => 'E2BIG',
    8 => 'ENOEXEC',
    9 => 'EBADF',
    10 => 'ECHILD',
    11 => 'EAGAIN',
    12 => 'ENOMEM',
    13 => 'EACCES',
    14 => 'EFAULT',
    22 => 'EINVAL',
    24 => 'EMFILE',
    32 => 'EPIPE',
];

foreach ($error_codes as $errno => $const_name) {
    $errmsg = pcntl_strerror($errno);
    printf("%3d %-10s %s\n", $errno, $const_name, $errmsg);
}

echo "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
?>

デバッグヘルパー関数

<?php
class PCNTLDebugHelper {
    /**
     * PCNTL関数の実行をラップしてエラー情報を表示
     */
    public static function executeWithErrorInfo($function_name, callable $callback) {
        echo "実行: {$function_name}\n";
        
        $result = $callback();
        
        // エラーチェック
        $errno = pcntl_get_last_error();
        
        if ($errno !== 0) {
            $errmsg = pcntl_strerror($errno);
            
            echo "  ✗ エラー発生\n";
            echo "    番号: {$errno}\n";
            echo "    内容: {$errmsg}\n";
            
            return ['success' => false, 'result' => $result, 'errno' => $errno, 'error' => $errmsg];
        } else {
            echo "  ✓ 成功\n";
            return ['success' => true, 'result' => $result];
        }
    }
    
    /**
     * エラーメッセージを分かりやすく表示
     */
    public static function explainError($errno) {
        $errmsg = pcntl_strerror($errno);
        
        $explanations = [
            EPERM => "権限がありません。root権限が必要な操作かもしれません。",
            ESRCH => "指定したプロセスが見つかりません。既に終了している可能性があります。",
            ECHILD => "子プロセスが存在しません。既に回収済みか、そもそも作成されていません。",
            EAGAIN => "リソースが一時的に利用できません。しばらく待ってから再試行してください。",
            ENOMEM => "メモリが不足しています。メモリリークがないか確認してください。",
            EINVAL => "無効な引数が渡されました。引数の値を確認してください。",
        ];
        
        echo "エラー番号: {$errno}\n";
        echo "エラー内容: {$errmsg}\n";
        
        if (isset($explanations[$errno])) {
            echo "説明: {$explanations[$errno]}\n";
        }
    }
}

// 使用例
echo "=== デバッグヘルパーの使用例 ===\n\n";

$result = PCNTLDebugHelper::executeWithErrorInfo('pcntl_fork', function() {
    return pcntl_fork();
});

if (!$result['success']) {
    echo "\n詳細な説明:\n";
    PCNTLDebugHelper::explainError($result['errno']);
} else {
    $pid = $result['result'];
    
    if ($pid > 0) {
        echo "子プロセスPID: {$pid}\n";
        pcntl_wait($status);
    } else {
        exit(0);
    }
}
?>

エラーハンドリングのベストプラクティス

<?php
class ProcessManager {
    private function forkWithRetry($max_retries = 3) {
        for ($attempt = 1; $attempt <= $max_retries; $attempt++) {
            echo "フォーク試行 {$attempt}/{$max_retries}\n";
            
            $pid = pcntl_fork();
            
            if ($pid === -1) {
                // エラー発生
                $errno = pcntl_get_last_error();
                $errmsg = pcntl_strerror($errno);
                
                echo "  ✗ 失敗: {$errmsg}\n";
                
                // リトライ可能なエラーか判定
                if ($errno === EAGAIN && $attempt < $max_retries) {
                    echo "  → リソース不足。1秒待機してリトライします\n";
                    sleep(1);
                    continue;
                    
                } elseif ($errno === ENOMEM && $attempt < $max_retries) {
                    echo "  → メモリ不足。GC実行後にリトライします\n";
                    gc_collect_cycles();
                    sleep(1);
                    continue;
                    
                } else {
                    // リトライ不可能なエラー
                    throw new RuntimeException(
                        "フォークに失敗しました: {$errmsg} (errno: {$errno})"
                    );
                }
            }
            
            // 成功
            echo "  ✓ 成功\n";
            return $pid;
        }
        
        throw new RuntimeException("最大リトライ回数に達しました");
    }
    
    public function createWorker() {
        try {
            $pid = $this->forkWithRetry();
            
            if ($pid > 0) {
                echo "ワーカープロセス作成: PID={$pid}\n";
                return $pid;
            } else {
                // 子プロセス
                echo "ワーカー実行中\n";
                sleep(2);
                exit(0);
            }
            
        } catch (RuntimeException $e) {
            echo "エラー: " . $e->getMessage() . "\n";
            return false;
        }
    }
}

$manager = new ProcessManager();
$pid = $manager->createWorker();

if ($pid !== false) {
    pcntl_wait($status);
    echo "ワーカー終了\n";
}
?>

重要なポイントと注意事項

💡 エラーメッセージは英語

pcntl_strerror()が返すメッセージは英語です:

$errmsg = pcntl_strerror(ECHILD);
echo $errmsg; // "No child processes"

日本語メッセージが必要な場合は、独自のマッピングを作成しましょう。

🔍 エラー番号0の扱い

エラーがない場合、pcntl_get_last_error()は0を返します:

$errno = pcntl_get_last_error();

if ($errno === 0) {
    echo "エラーなし\n";
} else {
    echo "エラー: " . pcntl_strerror($errno) . "\n";
}

⚠️ 環境依存性

エラーメッセージはOSに依存します:

// Linux
pcntl_strerror(EAGAIN); // "Resource temporarily unavailable"

// 別の環境では表現が異なる可能性

ベストプラクティス

1. 常にエラー番号と一緒に表示

// ✅ 良い例:番号とメッセージの両方
$errno = pcntl_get_last_error();
$errmsg = pcntl_strerror($errno);
echo "エラー [{$errno}]: {$errmsg}\n";

// ❌ 悪い例:メッセージだけ
echo "エラー: " . pcntl_strerror(pcntl_get_last_error()) . "\n";

2. ログには構造化データを使う

// ✅ JSON形式でログ
error_log(json_encode([
    'errno' => $errno,
    'error' => pcntl_strerror($errno),
    'function' => 'pcntl_fork',
    'time' => time()
]));

3. ユーザーフレンドリーなメッセージを追加

$errno = pcntl_get_last_error();
$errmsg = pcntl_strerror($errno);

// システムメッセージだけでなく、分かりやすい説明を追加
echo "プロセスの作成に失敗しました。\n";
echo "技術詳細: {$errmsg} (エラー番号: {$errno})\n";
echo "対処法: システムリソースを確認してください。\n";

まとめ

pcntl_strerror()は、PCNTLエラーを人間が理解できる形式で取得する必須関数です。重要なポイント:

  • ✅ エラー番号を読みやすいメッセージに変換
  • ✅ pcntl_get_last_error()と併用
  • ✅ デバッグとエラーログに不可欠
  • ✅ エラー番号と一緒に表示する
  • ✅ ユーザーフレンドリーな説明を追加

適切なエラーハンドリングは、堅牢なプロセス制御プログラムの基礎です。pcntl_strerror()を活用して、分かりやすいエラー処理を実装しましょう!


関連記事

  • pcntl_get_last_error()でエラー番号を取得
  • PHPでエラーハンドリングを実装する方法
  • PCNTL関数のエラー対策完全ガイド
タイトルとURLをコピーしました