PHP 8 compatibility
test code
- composer require workerman/workerman
- Fill code into test.php
- php test.php start
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
global $worker;
$worker = new Worker();
Worker::$pidFile = __DIR__.'/work.pid';
$worker->onWorkerStart = function () {
global $worker, $sec;
$sec = 0.5;
\Workerman\Lib\Timer::add($sec, function () {
exit();
}, true);
};
// 运行worker
Worker::runAll();
Execute the above code at php8
PHP 8.0.11 (cli) (built: Sep 23 2021 20:15:09) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.11, Copyright (c) Zend Technologies
with Zend OPcache v8.0.11, Copyright (c), by Zend Technologies
bash-5.1# php test.php start
Workerman[test.php] start in DEBUG mode
------------------------------------- WORKERMAN --------------------------------------
Workerman version:4.0.19 PHP version:8.0.11
-------------------------------------- WORKERS ---------------------------------------
proto user worker listen processes status
tcp root none none 1 [OK]
--------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
Warning: EventBase::loop(): Failed to invoke event callback in /app/vendor/workerman/workerman/Events/Event.php on line 195
Warning: EventBase::loop(): Failed to invoke event callback in /app/vendor/workerman/workerman/Events/Event.php on line 195
Warning: EventBase::loop(): Failed to invoke event callback in /app/vendor/workerman/workerman/Events/Event.php on line 195
Execute the above code at php7
PHP 7.4.14 (cli) (built: Jan 16 2021 00:10:32) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.14, Copyright (c), by Zend Technologies
bash-5.1# php test.php start
Workerman[test.php] start in DEBUG mode
------------------------------------- WORKERMAN --------------------------------------
Workerman version:4.0.19 PHP version:7.4.14
-------------------------------------- WORKERS ---------------------------------------
proto user worker listen processes status
tcp root none none 1 [OK]
--------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
Worker[39] process terminated
Worker[40] process terminated
Worker[41] process terminated
Worker[42] process terminated
Comments (16)
-
repo owner -
-
HI Ruslan Osmanov.
I also encountered this problem when using php8 (php8.0.11/8.1.0RC2, event 3.0.6).
Here is a minimal reproducible example.
<?php ini_set('display_errors', 'on'); error_reporting(E_ALL); $base = new EventBase(); $client = stream_socket_client('tcp://example.com:80'); $event = new Event($base, $client, Event::READ|Event::PERSIST, function($client){ exit("EXIT HERE\n\n"); // The same result for throws Error or Exception which codes may like this // $num = 1/0; }, $client); $event->add(); fwrite($client, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); $base->loop();
The results for PHP7 and PHP5.
$ php test.php EXIT HERE
The results for PHP8.
$ php test.php EXIT HERE PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13 PHP Warning: EventBase::loop(): Failed to invoke event callback in /home/test.php on line 13
For php8, an infinite loop will display errors and the CPU consumption is so high.
I understand that it's a bad idea to call
exit()
or throw exceptions in event loop callback. However, many frameworks use event extensions as base, such as workerman reactphp amphp etc. These frameworks have many users who don't know much about the event extension. They may callexit()
or fail to catch some exceptions or errors in the their business code, which will lead to an infinite loop and unavailable services.
-
repo owner Hi @popo,
Thank you for the minimal example! Well, pecl-event has never handled neither
exit()
norexceptions
in any special way. The changes we see in PHP 8 come from PHP core, and we, apparently, have to deal with them.One way to solve the issue is to delete (or stop) the event if the callback invocation fails. This would result in the following output for your test case:
EXIT HERE Warning: EventBase::loop(): Failed to invoke event callback. Deleting event. in Standard input code on line 12
However, just deleting events from the loop may not be sufficient because exceptions such as
DivisionByZeroError
would not be bubbled up to the caller ofEvent::loop
. Then we might also throwEventException
so that the caller is able to catch it.Please share your thoughts.
-
I think it's a good idea to be consistent with the default behavior of PHP. That is, when
exit()
is called, destroy EventLoop and exit the process. When an exception or error is not caught, throw them, then destroy the EventLoop and exit the process. I think this is also the result that most developers can accept and anticipate in advance.Another idea is to throw an EventException when
exit()
is called or an error/exception is generated to inform the EventLoop caller of what happened and let him decide what to do.
-
repo owner - changed status to resolved
Fixed issue
#69: event callback call failure was not handled→ <<cset e448f03666dc>>
-
repo owner @popo, @程巍, I’ve tried to fix this issue in the
master
branch. Please check it out.You can either build the extension manually, or use the Docker and Docker Compose files, e.g.
cat test.php | docker-compose run php8test ./run.sh
If everything is okay, I’ll publish a new beta release after a while.
-
repo owner It would be great if you could test it with PHP 7 as well, because I’ve applied the same logic to the PHP 7 version for consistency.
-
The results for php8
-
The results for php7
It works fine for php7.4.24. But
cat test.php | docker-compose run php7test ./run.sh
` has an error. -
repo owner @popo, Thank you.
It works fine for php7.4.24. But
cat test.php | docker-compose run php7test ./run.sh
` has an error.The assertion error occurs because of the fact that the event stream argument has to be put into non-blocking mode. We could do this in the extension implicitly, but I think it is the user’s responsibility. (See
stream_set_blocking()
function.)So, apart from that, do you think that the new behavior works correctly?
-
So, apart from that, do you think that the new behavior works correctly?
Yes 👍
-
repo owner Okay, then I’ll publish a new PECL package soon.
-
repo owner Uploaded version 3.0.7RC1 (beta).
-
Test 69-issue-event-cb-failure_php8.phpt will get stuck in testing when build with master php source (05023a281ddb62186fa47f51192ea51ba10f3a9b)
Using 87d8edb7c15accf4ecd0a88c0ddec3c1165115d5 at archlinux with libevent 2.1.12
The warning is missing when stuck.
Is this the same problem or shall I create another issue?
-
repo owner @dixyes , I don’t know but let’s create another issue.
- Log in to comment
@程巍 , I don’t know what happens there yet, but
1) it is a bad idea to
exit()
from an event callback because the other event objects (including the loops) have no opportunity to gracefully handle the shutdown. Yes, destructors are still called, but what if the other callbacks are being executed at the moment you callexit()
? There is also no guarantee of any specific order of__destruct
calls. It would be better to keep track of some global flag, probably send an "exit" signal to all events, wait for graceful termination for some time, then try to stop them forcefully.2) Are you forking processes after creation of the loop? I think you might not want to have a copy of the loop in the child processes. Anyway, the purpose of
exit()
is an abrupt process termination, and I don’t see why would anybody want to call it from the event callback directly.Anyway, the main problem is the need to debug the Workerman app in order to understand how are you using the event extension, and I don’t have a lot of time, sorry. If you could create a minimal reproducible example, things could be solved much faster.