swoole / swoole-src

🚀 Coroutine-based concurrency library for PHP
https://www.swoole.com
Apache License 2.0
18.42k stars 3.16k forks source link

php的内部异常,在error_handle处理后,worker进程退出,不管是协程版本或非协程版本都是如此 #1524

Closed tsingsun closed 6 years ago

tsingsun commented 6 years ago

Please answer these questions before submitting your issue. Thanks!

  1. What did you do? If possible, provide a recipe for reproducing the error.
    
    <?php

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();



2. What did you expect to see?

进程不应退出,不应该中断代码的执行,后果也是严重的,比如协程处理中断了

3. What did you see instead?

/usr/local/Cellar/php71/7.1.13_24/bin/php -dxdebug.remote_enable=1 -dxdebug.remote_mode=req -dxdebug.remote_port=9000 -dxdebug.remote_host=127.0.0.1 /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php
PHP Fatal error:  require(): Failed opening required 'ab.php' (include_path='.:') in /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php on line 12
PHP Stack trace:
PHP   1. {main}() /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:0
PHP   2. swoole_http_server->start() /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:40
PHP   3. {closure:/Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:16-27}($request = *uninitialized*, $response = *uninitialized*) /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:40
PHP   4. testWeb($response = *uninitialized*) /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:18

Fatal error: require(): Failed opening required 'ab.php' (include_path='.:') in /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php on line 12

Call Stack:
    0.0387     358592   1. {main}() /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:0
    0.0463     362440   2. swoole_http_server->start() /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:40
    3.5838     367992   3. {closure:/Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:16-27}(???, ???) /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:40
    3.5839     367992   4. testWeb(???) /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php:18

[2018-03-17 10:43:15 *2497.0]   ERROR   zm_deactivate_swoole (ERROR 503): Fatal error: require(): Failed opening required 'ab.php' (include_path='.:') in /Users/tsingsun/workspace/ThirdTools/yii2-swoole/demo/web/swoole_ori_server.php on line 12.
[2018-03-17 10:43:15 $2496.0]   WARNING swManager_check_exit_status: worker#0 abnormal exit, status=255, signal=0

4. What version of Swoole are you using (`php --ri swoole`)?

swoole 1.10.1 2.1.1 ,2.0.12 

5. What is your machine environment used (including version of kernel & php & gcc) ? 

mac
php 7.1.8

6. If you are using ssl, what is your openssl version?
twose commented 6 years ago

好险, 作为php萌新, 差点被这个问题迷惑了. 这和swoole无关, 而且顺带require是有IO操作的.

Fatal error

请把你的代码贴到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的exit

比如在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);
tsingsun commented 6 years ago

很不幸, 这是swoole的缺陷,php本来有register_exception_handle+register_error_handle来处理各种类型的exception和error,但是swoole并不支持register_exception_handle,导致在处理环节中却了一环,并且这个error是无法被捕获,不可怕吗?由于协程的存在还放大了该问题,想像一下,在一次支付请求,由于某些不可捕获的error,导致进程中的请求全败了.

twose commented 6 years ago

@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。

twose commented 6 years ago

@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
tsingsun commented 6 years ago

@twose 从新看了php文档,你是对的,E_ERROR等异常会导致脚本中断,这样swoole能做的确实有限,但还是觉得进程退出的代价太大.