Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload the Swoole/Process code inside its callback on Swoole\Server->reload() #5505

Closed
mohsin-devdksa opened this issue Oct 8, 2024 · 5 comments
Labels

Comments

@mohsin-devdksa
Copy link

mohsin-devdksa commented Oct 8, 2024

1. What did you do? If possible, provide a simple script for reproducing the error.
Hi, I have a use case where I want to reload a custom process (Swoole\Process). According to the documentation, reload() has no effect on custom user processes. Is there any way to ensure that the code in the custom process is also reloaded when calling reload()?

I attempted the following approach (which didn’t work):

a) Create the Swoole\Process inside the onWorkerStart event.
b) Before calling $server->reload(), kill the custom process.
c) Create a new Swoole\Process when the onWorkerStart event is triggered again.

// Create process in workerStart event when server starts or reload
$this->server->on('workerstart', function($server, $worker_id) {
    if ($worker_id == 1) {
        $server->proc = new Process(function ($process) {
            // Business logic code here
        },false, SOCK_DGRAM, true);

        $server->addProcess($server->proc);
    }
});

// Kill the process before reload
$this->server->on('message', function($webSocketServer, $frame) {
    // Before reload kill process
    if ($webSocketServer->worker_id == 1) {
        Process::kill($webSocketServer->proc->pid, SIGTERM);
    }

    $reloadStatus = $webSocketServer->reload();
    echo (($reloadStatus === true) ? PHP_EOL.'Code Reloaded'.PHP_EOL  :  PHP_EOL.'Code Not Reloaded').PHP_EOL;


    // Other business logic
});

However, I discovered that we cannot create a Swoole\Process inside the onWorkerStart event. So, I tried another approach where I created the Swoole\Process in the main process. The idea was to store the process PID into Swoole\Coroutine\Table and fetch it before reload() to kill the process. But, as you know, reload() only affects worker and task processes not the main process.
Additionally, when I create a new process and try to retrieve its PID, I don’t receive it.

$proc = new Process(function ($process) {
    // Business logic code here
},false, SOCK_DGRAM, true);

$id = $this->server->addProcess($proc);

echo PHP_EOL . 'Process PID: ' . $proc->pid;
echo PHP_EOL . 'Process ID: '. $proc->id;

My question is: is there any method or technique that allows us to reload the code changes inside a custom Swoole\Process, added through $server->addProcess($proc) when calling reload(), and how can I reliably retrieve the PID of a newly created custom process in the parent process just as we get its id from $server->addProcess() ?

2. What did you expect to see?
reload() function should also reload the code of the Swoole\Process

3. What did you see instead?
reload() function does not reload the code of Swoole\Process

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

Swoole => enabled
Author => Swoole Team <[[email protected]](mailto:[email protected])>
Version => 5.1.2
Built => Aug 15 2024 12:06:11
coroutine => enabled with thread context
debug => enabled
trace_log => enabled
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
json => enabled
curl-native => enabled
pcre => enabled
c-ares => 1.18.1
zlib => 1.2.11
brotli => E16777225/D16777225
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
async_redis => enabled
coroutine_pgsql => enabled
Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => On => On
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608

5. What is your machine environment used (show your uname -a & php -v & gcc -v) ?

  • PHP 8.3.12 (cli) (built: Sep 27 2024 03:53:05) (NTS)
  • gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1 22.04)
  • Linux directlv16-ThinkPad-E15-Gen-4 6.8.0-45-generic 45~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 11 15:25:05 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
@NathanFreeman
Copy link
Member

I will take a look later.

@NathanFreeman
Copy link
Member

NathanFreeman commented Oct 12, 2024

$proc = new Process(function ($process) {
    // Business logic code here
},false, SOCK_DGRAM, true);

$id = $this->server->addProcess($proc);

$this->server->on('message', function($webSocketServer, $frame) use ($proc) {
    // Before reload kill process
    if ($webSocketServer->worker_id == 1) {
        Process::kill($proc->pid, SIGTERM);
    }

    $reloadStatus = $webSocketServer->reload();
    echo (($reloadStatus === true) ? PHP_EOL.'Code Reloaded'.PHP_EOL  :  PHP_EOL.'Code Not Reloaded').PHP_EOL;


    // Other business logic
});

You need to add the user processes before executing $server->start; using addProcess after the server has started will have no effect.

@devDeveloperMohsin
Copy link

Thanks, @NathanFreeman , for your help
We found a workaround to reload code inside a custom User Process in Swoole, which is similar to your suggestion. Here’s the breakdown of how it works:

When the Swoole server is running, it creates user processes with new PIDs (Process IDs). We leverage this behavior to reload user processes by utilizing the BeforeReload event.

During this event, we terminate the existing user processes, which forces Swoole to create new ones with fresh PIDs. As a result, any updated code is applied to the new processes.

Important: Inside the process, if we write the business logic directly, it won't be reloaded. Instead, we must include the business logic from a separate file inside the process’s handler function. This ensures the logic gets reloaded when the new process is created.


1. Killing Processes in BeforeReload Event:

In the BeforeReload event, we kill the current user processes so that Swoole can recreate them with new PIDs. We achieve this by iterating over the processes, killing them, and removing their PID files.

<?php
    $this->server->on('BeforeReload', function($server) {
        // Iterate through the custom processes and terminate them
        foreach($this->customProcessCallbacks as $key => $callback) {
            $processPidFile = __DIR__ . '/process_pids/' . $key . '.pid';

            // Check if the PID file exists and kill the process
            if (file_exists($processPidFile)) {
                $pid = shell_exec('cat ' . $processPidFile);
                Process::kill((int) $pid, SIGTERM); // Terminate the process// Remove the PID file
                unlink($processPidFile);
            }
        }
    });
?>

2. Creating the Process and Handling Business Logic:

Here, we create the custom process and add it to the server. The key trick is to include the business logic file inside the handle() function. This ensures that when Swoole reloads the process, the new code will be picked up.

<?php
	$processCallback = new ProcessCallback();
	// Add the process callback to an array that holds custom processes
	$this->customProcessCallbacks['test_process'] = [$processCallback, 'handle'];

	// Create the process and add it to the server
	$testProcess = new Process($this->customProcessCallbacks['test_process'], false, SOCK_DGRAM, true);
	$this->server->addProcess($testProcess);

	$this->server->start(); // Start the server
?>

3. Process Callback Class:

In the ProcessCallback class, we use the include statement to import the business logic from external files. This way, when Swoole creates a new process, the updated logic will be loaded.

<?php

use Swoole\Timer;

class ProcessCallback
{
    public function handle()
    {
        // Include the main business logic
        include('path/to/business-logic-file.php');

        // Set a timer to execute business logic periodically
        Timer::tick(2000, function () {
            include('path/to/timer-business-logic-file.php');
        });
    }
}
?>

How It Works:

1. Killing and Recreating Processes:

In the BeforeReload event, we kill the existing user processes.
When Swoole reloads, it automatically creates new processes, each with a new PID.

2. Reloading Business Logic:

To ensure the latest business logic is applied, we use include inside the handle() function of the process. This will load the business logic from external files, ensuring it gets reloaded when a new process is created.

@LIngMax
Copy link

LIngMax commented Oct 17, 2024

if(function_exists('opcache_reset'))opcache_reset();
if(function_exists('apc_clear_cache'))apc_clear_cache();

@matyhtf
Copy link
Member

matyhtf commented Oct 17, 2024

You can send a signal to a custom process within the beforeReload event.

In the custom process, use pcntl_signal to listen for signals, and exit the process upon receiving the signal.

After the custom process exits, the underlying system will restart a new instance of the custom process.

@matyhtf matyhtf closed this as completed Oct 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants