こんにちは!今回は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()]
↓ [メッセージ取得]
主要なエラーコードとメッセージ
エラー番号 | 定数名 | メッセージ例 | 意味 |
---|---|---|---|
1 | EPERM | Operation not permitted | 操作が許可されていない |
3 | ESRCH | No such process | プロセスが存在しない |
4 | EINTR | Interrupted system call | システムコールが中断された |
10 | ECHILD | No child processes | 子プロセスが存在しない |
11 | EAGAIN | Resource temporarily unavailable | リソースが一時的に利用不可 |
12 | ENOMEM | Cannot allocate memory | メモリを割り当てられない |
22 | EINVAL | Invalid 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関数のエラー対策完全ガイド