Closed tsingsun closed 6 years ago
好险, 作为php萌新, 差点被这个问题迷惑了. 这和swoole无关, 而且顺带require是有IO操作的.
请把你的代码贴到php-cli中跑一下, 你会得到一个Fatal error
.
然后请看PHP官网文档
以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
对于进程不应退出,不应该中断代码的执行,后果也是严重的,比如协程处理中断了
这段话我也有不同的意见, 我认为严重的错误应该尽快解决风险而不是抑制它, 严重的错误抛出阻止代码执行是非常有必要的(如set_error_handler会使得代码继续运行,这很危险), 真正需要捕获并抑制的应该是由开发者(库)定义的各种exception或trigger_error和一些(预料中的)可忽视错误(对后续执行无威胁的).
我宁愿让服务宕机也不会让错误代码一直跑下去
并且在php官方文档中也有详细解释 错误捕获自定义函数的用途
本函数可以用你自己定义的方式来处理运行中的错误, 例如,在应用程序中严重错误发生时,或者在特定条件下触发了一个错误(使用 trigger_error()),你需要对数据/文件做清理回收。 并且 同时注意,在需要时你有责任使用 die()。 如果错误处理程序返回了,脚本将会继续执行发生错误的后一行。
比如在swoole,中途跳出业务逻辑层,实现php的exit效果
try{
// 代码又臭又长, 不能用if块来分割影响美观
if (命中缓存提前输出了, 后续代码不再执行){
throw new \ExitException(); //我要退出啦
}
// 慢查询
// balabala
} catch (\ExitException $e){
// 出来啦
} catch (\Exception $e) {
// 向用户解释错误原因或者输出500
} finally {
$response->end();
}
然后, 这种级别的错误, 进程退出是合理且不可避免的, 如果你要做擦屁股处理, 需要用register_shutdown_function()搭配error_get_last() 处理, 送上简单的实现代码.
function serializeTrace($traces):string {
$default_trace = [
'file' => 'unknown',
'line' => 0,
'function' => 'unknown'
];
$r = '';
foreach ($traces as $i => $t) {
$t = $t + $default_trace;
$r .= "#$i {$t['file']}({$t['line']}): ";
if (isset($t['object']) and is_object($t['object'])) {
$r .= get_class($t['object']).'->';
}
$r .= "{$t['function']}()\n";
}
return $r;
}
$shutdown_error_handle = function () {
$error = error_get_last();
if (!empty($error)) {
$log = "Shutdown Error: [{$error['type']}] {$error['message']} ".
"in {$error['file']}:{$error['line']}\n".
"Stack trace:";
$log .= serializeTrace(debug_backtrace());
$this->errorLog($log);
}
};
register_shutdown_function($shutdown_error_handle);
很不幸, 这是swoole的缺陷,php本来有register_exception_handle+register_error_handle来处理各种类型的exception和error,但是swoole并不支持register_exception_handle,导致在处理环节中却了一环,并且这个error是无法被捕获,不可怕吗?由于协程的存在还放大了该问题,想像一下,在一次支付请求,由于某些不可捕获的error,导致进程中的请求全败了.
@tsingsun
swoole只是不能使用set_exception_handler
, 但任意exception都可以用try捕获.
因为你示例代码中贴出的require错误
并不是exception
而是一个严重error
, 就算你在php-cli环境下运行也会异常退出而不能被捕获.
而且我也贴了
以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。
@tsingsun 这是在普通的php环境下运行, 并没有任何捕获方法奏效, 只打印出了error_handle, 进程仍旧异常退出了后续代码也未执行.
<?php
set_error_handler(function () {
echo 'error_handle';
return true;
});
set_exception_handler(function () {
echo 'exception_handle';
return true;
});
try {
require 'ab.php';
} catch (Error $e) {
echo 'catch error';
} catch (Exception $e) {
echo 'catch exception';
} catch (Throwable $t) {
echo 'catch throwable';
} finally {
echo 'finally';
}
PHP Fatal error: require(): Failed opening required 'ab.php' (include_path='.:/usr/local/Cellar/php/7.2.3_3/share/pear') in /Users/twosee/Toast/test/error.php on line 21
error_handle
Fatal error: require(): Failed opening required 'ab.php' (include_path='.:/usr/local/Cellar/php/7.2.3_3/share/pear') in /Users/twosee/Toast/test/error.php on line 21
Process finished with exit code 255
@twose 从新看了php文档,你是对的,E_ERROR等异常会导致脚本中断,这样swoole能做的确实有限,但还是觉得进程退出的代价太大.
Please answer these questions before submitting your issue. Thanks!
set_error_handler('errorHandle'); $http = new swoole_http_server("127.0.0.1", 9501);
function testWeb(\Swoole\Http\Response $response) { $response->header("Content-Type", "text/html"); //内部异常 require 'ab.php'; //普通可捕获 //$a = $b; //throw new \Exception('aaa'); $response->end('hello'); }
$http->on("request", function (\Swoole\Http\Request $request, $response) { try{ testWeb($response); }catch (Exception $e){ echo 'e'; }catch (Throwable $t){ echo 't'; }finally{ echo 'f'; } });
function errorHandle($errno, $errstr, $errfile, $errline) { if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting, so let it fall // through to the standard PHP error handler return false; } / Don't execute PHP internal error handler / return true; }
$http->start();