swoole / swoole-src

🚀 Coroutine-based concurrency library for PHP
https://www.swoole.com
Apache License 2.0
18.44k stars 3.16k forks source link

How to stop starting new worker when including another PHP file #5093

Closed charlesgetup closed 1 year ago

charlesgetup commented 1 year ago

Please answer these questions before submitting your issue.

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

I am using Swoole v5.0.2 and plates php template. When the (view) template renders, sometimes Swoole starts a new worker in the middle of the render process. And the template could not finish rendering when new worker comes in. A 502 error is finally returned by the server. See the render function below. The new worker gets started when running this line of code, include func_get_arg(0);.

public function render(array $data = array())
{
    $this->data($data);
    $path = ($this->engine->getResolveTemplatePath())($this->name);

    try {
        $level = ob_get_level();
        ob_start();

        (function() {
            extract($this->data);
            include func_get_arg(0);
        })($path);

        $content = ob_get_clean();

        if (isset($this->layoutName)) {
            $layout = $this->engine->make($this->layoutName);
            $layout->sections = array_merge($this->sections, array('content' => $content));
            $content = $layout->render($this->layoutData);
        }

        return $content;
    } catch (Throwable $e) {
        while (ob_get_level() > $level) {
            ob_end_clean();
        }

        throw $e;
    }
}
  1. What did you expect to see?

I don't want the new worker to be started when running include func_get_arg(0);.

  1. What did you see instead?

A new worket has been started when running include func_get_arg(0);.

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

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 5.0.2
Built => May 17 2023 05:44:40
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 3.0.8 7 Feb 2023
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
zlib => 1.2.13
brotli => E16777225/D16777225
mutex_timedlock => enabled
pthread_barrier => enabled
mysqlnd => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608
  1. What is your machine environment used (show your uname -a & php -v & gcc -v) ?

I am using this Alpine Linux docker image, FROM php:8.1.18-alpine3.17 AS base.

# uname -a
Linux 0b3f4b18effe 4.19.76-linuxkit #1 SMP Tue May 26 11:42:35 UTC 2020 x86_64 Linux
# php -v
PHP 8.1.18 (cli) (built: Apr 14 2023 18:38:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.18, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.18, Copyright (c), by Zend Technologies
# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-alpine-linux-musl/12.2.1/lto-wrapper
Target: x86_64-alpine-linux-musl
Configured with: /home/buildozer/aports/main/gcc/src/gcc-12-20220924/configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl --target=x86_64-alpine-linux-musl --enable-checking=release --disable-fixed-point --disable-libstdcxx-pch --disable-multilib --disable-nls --disable-werror --disable-symvers --enable-__cxa_atexit --enable-default-pie --enable-default-ssp --enable-languages=c,c++,d,objc,go,fortran,ada --disable-libssp --disable-libsanitizer --enable-shared --enable-threads --enable-tls --with-bugurl=https://gitlab.alpinelinux.org/alpine/aports/-/issues --with-system-zlib --with-linker-hash-style=gnu --with-pkgversion='Alpine 12.2.1_git20220924-r4'
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4)

Any suggestion is appreciated. Thanks.

charlesgetup commented 1 year ago

The function argument, func_get_arg(0), in include func_get_arg(0); is a file path. For example, /var/www/localhost/htdocs/app/core/view/backend_user/list.php.

NathanFreeman commented 1 year ago

try to use include_once or require_once rather than include and require .

charlesgetup commented 1 year ago

try to use include_once or require_once rather than include and require .

@NathanFreeman Thank you for your quick answer.

The included file is view template and it will be used to display dynamic data. For example, when using pagination, the same template file will be included in every page display. So I could not use include_once or require_once.

NathanFreeman commented 1 year ago

502 error code means there is a problem with upstream server , check if there are any log outputs.

NathanFreeman commented 1 year ago

Swoole will initialize all working processes over before receiving request.

charlesgetup commented 1 year ago

@NathanFreeman

I have added some error log to help me debug. Here is the render function with debug code.

public function render(array $data = array())
{
    $this->data($data);
    $path = ($this->engine->getResolveTemplatePath())($this->name);

    try {
        $level = ob_get_level();

        error_log('!!! ' .$level .' - ' .print_r($data, true));

        ob_start();

        (function() {
            extract($this->data);

            error_log('include: ' .func_get_arg(0));

            include func_get_arg(0);
        })($path);

        $content = ob_get_clean();

        error_log("$level - done");

        if (isset($this->layoutName)) {
            $layout = $this->engine->make($this->layoutName);
            $layout->sections = array_merge($this->sections, array('content' => $content));
            $content = $layout->render($this->layoutData);
        }

        return $content;
    } catch (Throwable $e) {
        while (ob_get_level() > $level) {
            ob_end_clean();
        }

        throw $e;
    }
}

I have tested this using a web page with pagination which displays the users in a table. When no new worker has been started, I will see the following debug output in error log.

!!! 1 - Array
(
    [title] => List User
    [totalUserAmount] => 10
    [users] => Array
        (
            [0] => ...
         )
    ... ...
)
include: /var/www/localhost/htdocs/app/core/view/backend_user/list.php
1 - done

But if new worker has been started, I got the following output.

!!! 1 - Array
(
    [title] => List User
    [totalUserAmount] => 10
    [users] => Array
        (
            [0] => ...
         )
    ... ...
)
include: /var/www/localhost/htdocs/app/core/view/backend_user/list.php
Worker #3 started

And I won't see 1 - done output. The process is stuck there until timeout and a 502 error is sent.

Do we have any flag which can keep the job done by one worker? I tried to use Swoole\Lock in Server's onRequest method, but the task worker still got started. And I am also not sure that this is the right direction to solve my issue or not.

Please advise. Thank you so much.

charlesgetup commented 1 year ago

@NathanFreeman I have to go now. Thank you so much for your help and I will come back tomorrow to check updates.

Any help is appreciated. Thank you.

charlesgetup commented 1 year ago

After a couple of days debug, I've finally found the issue.

The issue is about the include function, but not the one in plates, it is the nested include in my template partials. So this is not a Swoole issue. I will close this issue now.

During the debug, I found that I missed one super important Swoole event in my server setup. I think this may help someone else. So I will leave it here. And that is the one points me the right way.

$this->httpServer->on("**WorkerError**", \Closure::fromCallable(array($this, 'onServerWorkerErrorEventHandler')));

My outputs: Worker error: ID=3, pid=5374, exit_code=255. With the exit code, I realised that the issue is not caused by Swoole, it is my bad.