Skip to content

Server-reload() does not terminate Swoole Timers, gracefully #361

@fakharksa

Description

@fakharksa
  1. What did you do? If possible, provide a simple script for reproducing the error.

Terminology used in Code below:

  • class prefix sw stands for swoole\ and osw for openswoole. so, swTimer is Swoole\Timer.
  • $this->swoole_ext == 1 evaluates to true means "swoole" extension is enabled, otherwise "openswoole" extension is enabled.

I create this Swoole's timer from inside onMessage Event and in the same onMEssage, i also calll reload(), as below:

                if ($frame->data == 'reload-code') {
                    if ($this->swoole_ext == 1) {
                        echo PHP_EOL.'In Reload-Code: Clearing All Swoole-based Timers'.PHP_EOL;
                        swTimer::clearAll();
                    } else {
                        echo PHP_EOL.'In Reload-Code: Clearing All OpenSwoole-based Timers'.$fd.PHP_EOL;
                        oswTimer::clearAll();
                    }
//                    self::$fds = null;
//                    unset($frame);
                    echo "Reloading Code Changes (by Reloading All Workers)".PHP_EOL;
                    $webSocketServer->reload();
                } else {
                    include_once __DIR__ . '/Controllers/WebSocketController.php';

                    global $app_type_database_driven;
                    if ($app_type_database_driven) {
                        $sw_websocket_controller = new WebSocketController($webSocketServer, $frame, $this->dbConnectionPools[$webSocketServer->worker_id]);
                    } else {
                        $sw_websocket_controller = new WebSocketController($webSocketServer, $frame);
                    }

                    $timerTime = $_ENV['SWOOLE_TIMER_TIME1'];
                    if ($this->swoole_ext == 1) {
                        self::$fds[$frame->fd][] = swTimer::tick($timerTime, $respond, $webSocketServer, $frame, $sw_websocket_controller);
                    } else {
                        self::$fds[$frame->fd][] = oswTimer::tick($timerTime, $respond, $webSocketServer, $frame, $sw_websocket_controller);
                    }
                } 

I use WebSocket's push() function from inside Swoole's Timer's callback which pushes data, and is defined as below:

        $respond = function($timerId, $webSocketServer, $frame, $sw_websocket_controller) {
            if (isset($frame->fd) && isset(self::$fds[$frame->fd])) { // if the user / fd is connected then push else clear timer.
                if ($frame->data) { // when a new message arrives from connected client with some data in it
                    $bl_response = $sw_websocket_controller->handle();
                    $frame->data = false;
                } else {
                    $bl_response = 1;
                }

                $webSocketServer->push($frame->fd,
                    json_encode($bl_response),
                    WEBSOCKET_OPCODE_TEXT,
                    SWOOLE_WEBSOCKET_FLAG_FIN); // SWOOLE_WEBSOCKET_FLAG_FIN OR OpenSwoole\WebSocket\Server::WEBSOCKET_FLAG_FIN

            } else {
                echo "Inside Event's Callback: Clearing Timer ".$timerId.PHP_EOL;
                if ($this->swoole_ext == 1) {
                    swTimer::clear($timerId);
                } else {
                    oswTimer::clear($timerId);
                }
            }
        };

Other related Code:

  • Below is my onClose:
        $this->server->on('close', function($server, $fd, $reactorId) {
            echo PHP_EOL."client {$fd} closed in ReactorId:{$reactorId}".PHP_EOL;

            if ($this->swoole_ext == 1) {
                if (isset(self::$fds[$fd])) {
                    echo PHP_EOL.'On Close: Clearing Swoole-based Timers for Connection-'.$fd.PHP_EOL;
                    $fd_timers = self::$fds[$fd];
                    foreach ($fd_timers as $fd_timer){
                        if (swTimer::exists($fd_timer)) {
                            echo PHP_EOL."In Connection-Close: clearing timer: ".$fd_timer.PHP_EOL;
                            swTimer::clear($fd_timer);
                        }
                    }
                }
            } else {
                if (isset(self::$fds[$fd])) {
                    echo PHP_EOL.'On Close: Clearing OpenSwoole-based Timers for Connection-'.$fd.PHP_EOL;
                    $fd_timers = self::$fds[$fd];
                    foreach ($fd_timers as $fd_timer){
                        if (oswTimer::exists($fd_timer)) {
                            echo PHP_EOL."In Connection-Close: clearing timer: ".$fd_timer.PHP_EOL;
                            oswTimer::clear($fd_timer);
                        }
                    }
                }
            }
            unset(self::$fds[$fd]);
        });
  • Below is my onDisconnect:
        $this->server->on('disconnect', function(Server $server, int $fd) {
            echo "connection disconnect: {$fd}\n";
            if ($this->swoole_ext == 1) {
                if (isset(self::$fds[$fd])) {
                    echo PHP_EOL.'On Disconnect: Clearing Swoole-based Timers for Connection-'.$fd.PHP_EOL;
                    $fd_timers = self::$fds[$fd];
                    foreach ($fd_timers as $fd_timer){
                        if (swTimer::exists($fd_timer)) {
                            echo PHP_EOL."In Disconnect: clearing timer: ".$fd_timer.PHP_EOL;
                            swTimer::clear($fd_timer);
                        }
                    }
                }
            } else {
                if (isset(self::$fds[$fd])) {
                    echo PHP_EOL.'On Disconnect: Clearing OpenSwoole-based Timers for Connection-'.$fd.PHP_EOL;
                    $fd_timers = self::$fds[$fd];
                    foreach ($fd_timers as $fd_timer){
                        if (oswTimer::exists($fd_timer)) {
                            echo PHP_EOL."In Disconnect: clearing timer: ".$fd_timer.PHP_EOL;
                            oswTimer::clear($fd_timer);
                        }
                    }
                }
            }
            unset(self::$fds[$fd]);
        });

Below are my Before/After Reload events:

As you see i tried clearing timers here but that also did not help so i commented the code.

        $this->server->on('BeforeReload', function($server)
        {
            echo "Test Statement: Before Reload". PHP_EOL;
            dump(self::$fds);
//            var_dump(get_included_files());
//            if ($this->swoole_ext == 1) {
//                if (swTimer::clearAll()) {
//                    echo PHP_EOL."Before Reload: Cleared All Swoole-based Timers".PHP_EOL;
//                } else {
//                    echo PHP_EOL."Before Reload: Could not clear Swoole-based Timers".PHP_EOL;
//                }
//            } else {
//                if (oswTimer::clearAll()) {
//                    echo PHP_EOL."Before Reload: Cleared All OpenSwoole-based Timers".PHP_EOL;
//                } else {
//                    echo PHP_EOL."Before Reload: Could not clear OpenSwoole-based Timers".PHP_EOL;
//                }
//            }
        });

        $this->server->on('AfterReload', function($server)
        {
            echo PHP_EOL."Test Statement: After Reload". PHP_EOL;
            dump(self::$fds);
//            var_dump(get_included_files());
//            if ($this->swoole_ext == 1) {
//                if (swTimer::clearAll()) {
//                    echo PHP_EOL."AfterReload: Cleared All Swoole-based Timers".PHP_EOL;
//                } else {
//                    echo PHP_EOL."AfterReload: Could not clear Swoole-based Timers".PHP_EOL;
//                }
//            } else {
//                if (oswTimer::clearAll()) {
//                    echo PHP_EOL."AfterReload: Cleared All OpenSwoole-based Timers".PHP_EOL;
//                } else {
//                    echo PHP_EOL."AfterReload: Could not clear OpenSwoole-based Timers".PHP_EOL;
//                }
//            }
        });
  1. What did you expect to see?

No PHP Warning from WebSocketServer->push()

  1. What did you see instead?

Inside Timers, i use push() which continues to send data to an $fd, even after Timer has been cleared. This issue occurs only if i cause $webSocketServer->reload() to be executed from other terminal.

So i get this PHP Warning, repeatedly:
PHP Warning: Swoole\WebSocket\Server::push(): session#1 does not exists in /var/www/html/swoole-serv/sw_service.php on line 425
PHP Warning: Swoole\WebSocket\Server::push(): session#1 does not exists in /var/www/html/swoole-serv/sw_service.php on line 425
PHP Warning: Swoole\WebSocket\Server::push(): session#1 does not exists in /var/www/html/swoole-serv/sw_service.php on line 425
PHP Warning: Swoole\WebSocket\Server::push(): session#1 does not exists in /var/www/html/swoole-serv/sw_service.php on line 425

  1. What version of OpenSwoole are you using (show your php --ri openswoole)?
openswoole

Open Swoole => enabled
Author => Open Swoole Group <[email protected]>
Version => 22.1.2
Built => May 19 2024 22:56:05
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 3.0.2 15 Mar 2022
dtls => enabled
http2 => enabled
hook-curl => enabled
zlib => 1.2.11
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
postgresql => enabled

Directive => Local Value => Master Value
openswoole.enable_coroutine => On => On
openswoole.enable_preemptive_scheduler => On => On
openswoole.display_errors => On => On
openswoole.unixsock_buffer_size => 8388608 => 8388608

  1. What is your machine environment used (show your uname -a & php -v & gcc -v) ?
Linux HP-Laptop 5.17.5-76051705-generic #202204271406165150484020.04~63e51bd-Ubuntu SMP PREEMPT Wed Ma x86_64 x86_64 x86_64 GNU/Linux

PHP 8.3.8 (cli) (built: Jun 6 2024 16:58:27) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.8, Copyright (c) Zend Technologies
with Zend OPcache v8.3.8, Copyright (c), by Zend Technologies

COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1

Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.4.0-1ubuntu122.04' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-XeT9lY/gcc-11-11.4.0/debian/tmp-gcn/usr --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu122.04)

You can also try the following OpenSwoole support channels:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions