swoole / rfc

Swoole 提案
116 stars 3 forks source link

RFC-1016: 协程中断功能 #40

Closed twose closed 3 weeks ago

twose commented 5 years ago

协程中断

需要了解的概念

以上三个功能都是基于协程中断特性的, 只有处于cancelable(可取消)的操作中的协程才能被取消/置异常/杀死, 当你成功中断一个协程时, 上下文环境将会立即切换到对应协程中

尝试取消/置异常/杀死一个处于不可取消操作中的协程, 将会返回false, 此时调用swoole_last_error()返回值为SWOOLE_ERROR_CO_NONCANCELABLE_OPERATION

尝试取消/置异常/杀死一个不存在的协程, 将会返回false, 此时调用swoole_last_error()返回值为SWOOLE_ERROR_CO_NOT_EXIST

尝试置异常一个协程成功后, 协程内抛出的异常的错误码$e->getCode值为SWOOLE_ERROR_CO_INTERRUPTED_BY_EXCEPTION, 并且异常将可以通过$e->getOriginCid(), $e->getOriginFile(), $e->getOriginFile(), $e->getOriginTrace() 来获取是哪个协程的哪个位置将当前协程置异常的

例子

取消操作:

Co::cancel(go(function () {
    var_dump(Co::sleep(1));
    var_dump(Co::isCancelled());
}));

如果sleep操作被取消, 将返回剩余未sleep的秒数, 为float型, 可以通过Co::isCancelled来判断当前操作是否是被手动取消的, 如sleep操作正常结束, 将返回true, 如失败, 将返回false

float(1)
bool(true)

指定协程抛出异常

Co::throw(go(function () {
    try {
        Co::yield();
    } catch (Co\Exception $e) {
        var_dump($e);
    }
}));

将会输出异常对象, 此操作和在Co::yield()之后马上抛出异常等价, 但是这个异常会额外包含来源的信息

杀死协程

Co::kill(go(function () {
    Co::yield();
    echo "never here\n";
}));

不会有任何输出

值得注意的是, 即使是不在协程内(全局空间), 也可以执行这几个中断操作

更多相关示例代码及测试请见: https://github.com/swoole/swoole-src/tree/interrupt/tests/swoole_coroutine/interrupt

协程中抛出异常未捕获将不再会导致进程退出

协程中产生普通的未捕获异常(Exception)不再会导致进程退出, 而是导致协程退出, 并产生一条协程异常退出的错误日志, 如:

Warning: [Coroutine#2] Uncaught Exception: whoops in %s/tests/swoole_coroutine/exception/server.php:21
Stack trace:
#0 {main}
  thrown in %s/tests/swoole_coroutine/exception/server.php on line 21

产生致命错误时(Error)进程将无法继续正常运行, 仍会导致进程退出

mlsjla commented 5 years ago

Co::shutdown 命名好坑,建议

8923052 commented 5 years ago

中断,有用,支持。

twose commented 5 years ago

Co::shutdown 命名好坑,建议

  • Co::kill

采纳

qxhy123 commented 5 years ago

能不能提供更多中断的使用场景?

twose commented 5 years ago

@qxhy123 能不能提供更多中断的使用场景?

这个问题很适合在这里解答

异步安全重启+平滑退出的更优解

在现有方案中, 异步安全重启+平滑退出都是有最大超时时间的, 如果服务长时间没有顺利完成手头已有任务, 进程会被直接强制退出, 可能会造成一些未知的问题. 但是有协程中断功能后, 可以在超时后先尝试取消所有协程, 所有IO会按照失败分支逻辑运行, 完成使命(如服务器能够正确地返回超时错误, 当前服务不可用等), 而不是由于进程挂掉而导致请求丢失/连接被强制切断

基于协程粒度的超时熔断/异常熔断

如题, 原先已挂起的协程是不可主动调度的

更加灵活的封装

框架层一定会非常需要这样层次的API来进行控制

更好的API设计

和传统PHP的类似功能的API不同的是, swoole中大量的API增加了timeout参数, 当然也有部分难以添加或者说不合适添加timeout参数的(会很奇怪, 设计上也有困难), 比如文件操作系列函数, 现在一切都有了可能, 我们可以在PHP层实现任意IO操作的超时, 而无需依赖于底层的API设计

Debug

延伸这个特性, 我们甚至可以期望用PHP代码实现强大的Debug工具, 如进入一个挂起的协程进行一些DEBUG调试, 然后再将它挂起

lmafwo commented 5 years ago

中断,有用,支持。

Ederth commented 5 years ago

支持

tanbinghao commented 5 years ago

支持

qinghuiying commented 5 years ago

支持

qinghuiying commented 5 years ago

支持

Yurunsoft commented 5 years ago

有用,可以更好管理协程

xuanyanwow commented 5 years ago

有用

RunsTp commented 5 years ago

支持 非常重要的功能

YunionVan commented 5 years ago

哇,超时熔断,期待!!!

no-serve-people commented 5 years ago

支持支持,,,,我看已经有个pr了

lackoxygen commented 5 years ago

非常需要

HeKunTong commented 5 years ago

非常需要

Melonyed commented 5 years ago

支持支持 需要此功能

SkyWblack commented 5 years ago

i do

caohao-go commented 5 years ago

Moln commented 5 years ago

关于 Co::cancel 所表达的仅 sleep , 对于其它IO协程 会发生什么

Co::cancel(go(function () {
  $result = file_get_contents('...');  // 这里会发生什么
}))
Moln commented 5 years ago

需不需要退出功能呢?

@twose

function Co::exit() {
   Co::kill(Co::getCid());
}
function do_something() {
   static $i = 0;

   $i++;
   echo $i;
   if ($i == 5) {
       Co::exit();
   }
}

go(function () {
  while(true) {
   do_something();
   Co::sleep(1);
  }
});
// 输出 12345
tw2066 commented 2 years ago

支持

HeKunTong commented 2 years ago

您的邮件已收到!

HeKunTong commented 3 weeks ago

您的邮件已收到!