Php的swoole和grpc扩展一起使用时kill不能杀死swoole进程的问题 Grpc的fork模式问题
发现swoole的一个服务,用普通的kill总是杀不死,最后还是留一个进程在那里非要用 kill -9 才能结束掉。 看了一下,进程是卡死在 grpc shutdown的信号量上面了。
#0 0x00007f533454d48c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f53301ada52 in gpr_cv_wait () from /lib64/libgrpc.so.11
#2 0x00007f533015745b in ?? () from /lib64/libgrpc.so.11
#3 0x00007f533018e060 in grpc_shutdown_internal_locked() () from /lib64/libgrpc.so.11
#4 0x00007f533018e34c in grpc_shutdown_blocking () from /lib64/libgrpc.so.11
#5 0x00007f53304f2665 in zm_shutdown_grpc () from grpc.so
#6 0x00000000007011db in module_destructor ()
#7 0x00000000006fb86c in module_destructor_zval ()
#8 0x000000000070bbd4 in zend_hash_graceful_reverse_destroy ()
#9 0x00000000006fc6a3 in zend_shutdown ()
#10 0x000000000069f21a in php_module_shutdown ()
#11 0x0000000000469ac4 in main ()
https://github.com/grpc/grpc/issues/18833 这个理由提到 要改php.ini模式启动fork模式的支持。
grpc.enable_fork_support = 1
grpc.poll_strategy = epoll1
但这么改之后,启动的时候就死锁了,
# pstack 7009
#0 0x00007f48bc31e48c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f48b701ba52 in gpr_cv_wait () from /lib64/libgrpc.so.11
#2 0x00007f48b6fc545b in ?? () from /lib64/libgrpc.so.11
#3 0x00007f48b6ffc060 in grpc_shutdown_internal_locked() () from /lib64/libgrpc.so.11
#4 0x00007f48b6ffc34c in grpc_shutdown_blocking () from /lib64/libgrpc.so.11
#5 0x00007f48b7360841 in postfork_child () from grpc.so
#6 0x00007f48bc639b10 in __run_fork_handlers () from /lib64/libc.so.6
#7 0x00007f48bc5f838c in fork () from /lib64/libc.so.6
#8 0x00007f48b812530d in swoole_fork () from swoole.so
#9 0x00007f48b815897f in swManager_start(swServer*) () from swoole.so
#10 0x00007f48b8160e83 in swFactoryProcess_start(swFactory*) () from swoole.so
#11 0x00007f48b815cc0b in swServer_start(swServer*) () from swoole.so
#12 0x00007f48b81cedd0 in zim_swoole_server_start(_zend_execute_data*, _zval_struct*) () from swoole.so
#13 0x000000000077d7c8 in execute_ex ()
#14 0x000000000077e323 in zend_execute ()
#15 0x00000000006fcb24 in zend_execute_scripts ()
#16 0x000000000069f520 in php_execute_script ()
#17 0x00000000007803b3 in do_cli ()
#18 0x0000000000469a68 in main ()
还是卡在 postfork_child 后面的 grpc_shutdown_blocking函数上面,也就是这个fork模式grpc的工作有些问题。
搜索了一下,这个grpc的fork模式确实很多问题: https://github.com/swoole/swoole-src/issues/2604 gRPC 跨进程使用引发的问题 https://zhuanlan.zhihu.com/p/136619485 https://github.com/grpc/grpc/blob/master/doc/fork_support.md https://github.com/grpc/grpc/issues/15334 https://github.com/grpc/grpc//src/php/README.md https://github.com/grpc/grpc/blob/master/src/php/README.md https://github.com/grpc/grpc/issues/13412 https://github.com/grpc/grpc/issues/20250 https://github.com/grpc/grpc/pull/22774
因为swoole本身就是fork子进程的,所以刚好跟grpc有冲突吗。这个看代码,像是grpc fork后,有些条件变量状态不对了,shutdown流程死锁了。看堆栈看出来是 grpc_shutdown_internal_locked 调用的哪个子函数调用这个gpr_cv_wait。
简单的把grpc_shutdown_blocking函数给注释掉了,是不会卡死了,但不知道有没有什么负面作用。看python那边postfork的逻辑好像也没有shutdown的代码,只是删除channel避免 子进程里面使用父进程的channel吧。不知道这么改grpc在子进程里面还能正常工作不,还有待测试。这个正确的做法是找到这个信号量然后看grpc的代码逻辑来修复,但工作量太大了。
void postfork_child() {
TSRMLS_FETCH();
// loop through persistent list and destroy all underlying grpc_channel objs
destroy_grpc_channels();
release_persistent_locks();
// clean all channels in the persistent list
php_grpc_clean_persistent_list(TSRMLS_C);
// clear completion queue
grpc_php_shutdown_completion_queue(TSRMLS_C);
// clean-up grpc_core
/*
grpc_shutdown_blocking();
if (grpc_is_initialized() > 0) {
zend_throw_exception(spl_ce_UnexpectedValueException,
"Oops, failed to shutdown gRPC Core after fork()",
1 TSRMLS_CC);
}
*/
// restart grpc_core
grpc_init();
grpc_php_init_completion_queue(TSRMLS_C);
}
20202-08-21补充: 有人开了一个帖子了,应该是类似的问题 https://github.com/grpc/grpc/issues/23833
20202-09-07: 另外一个感觉更好的解决方法是,最开始php.ini 里面不要加载grpc.so和扩展和配置grpc.enable_fork_support ,在swoole的worker进程启动后再使用的php的dl函数动态加载grpc.so extension, 这样测试也没有出现死锁的情况。
function onWorkerStart($server, $worker_id)
{
if (!extension_loaded('grpc')) {
if (dl('grpc.so')) {
error_log("load grpc.so successfully");
include_once(dirname(__FILE__)."/grpc_header.php");
} else {
error_log("failed to load grpc.so");
}
} else {
error_log("grpc.so has been loaded");
}
$server->tick(300000, function ($id) {
G::$serv->task(TaskType::TIMER_TICK);
});
}
另外swoole的定时器,不用使用在全局的地方使用 swoole_timer_tick() 这个函数来设置定时器,这个是使用alarm信号来实现的,应该用 $server->tick 的这个epoll的timer实现的。 不然也会出现 kill -15 主进程, 主进程没有处理信号退出的情况。
2020-10-10补充,看上去 官方在 https://github.com/grpc/grpc/pull/24364/commits/8e9e895ffc530a9db678e932226d9cd8cd6436a5 这个commit 修复了这个问题。就把src/php/ext/grpc/php_grpc.c文件里面的调用grpc_shutdown_blocking 函数全部改为grpc_shutdown函数。
Let's remove it because grpc_shutdown now can execute synchronously if it's possible. grpc_shutdown_blocking can be harmful when it's executed under the event thread, which can lead a deadlock.