swoole / swoole-src

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

[question] unexpected coroutine behaviour #3436

Closed idealogica closed 3 years ago

idealogica commented 4 years ago

Hello dear Swoole developers,

  1. What did you do? If possible, provide a simple script for reproducing the error.
        go(function () {
            $nextTime = time() + 5;
            while (1) {
                if (time() >= $nextTime) {
                    $nextTime = time() + 5;
                    go(function () {
                        dump('before timeout');
                        \Co\System::sleep(2);
                        dump('after timeout');
                    });
                }
                $this->client->recv();
            }
        });

It outputs only 'before timeout', it doesn't output 'after timeout' Server::after() behaves the same

  1. What did you expect to see?

I expected it outputs both 'before timeout' and 'after timeout' strings

  1. What did you see instead?

It outputs only 'before timeout'

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

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 4.4.16
Built => Apr 20 2020 09:03:11
coroutine => enabled
debug => enabled
trace_log => enabled
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 1.1.1d  10 Sep 2019
http2 => enabled
pcre => enabled
mutex_timedlock => enabled
pthread_barrier => enabled
futex => 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 (including version of kernel & php & gcc) ?
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:        18.04
Codename:       bionic
Kernel: Linux 4.15.0-88-generic x86_64
PHP 7.1.33-12+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Feb 23 2020 07:22:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.1.33-12+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

Thank you for the help!

idealogica commented 4 years ago

However, I noticed that the following code outputs both 'before timeout' and 'after timeout'

        go(function () {
            $nextTime = time() + 5;
            while (1) {
                if (time() >= $nextTime) {
                    $nextTime = time() + 5;
                    go(function () {
                        dump('before timeout');
                        \Co\System::sleep(2);
                        dump('after timeout');
                    });
                }
                $this->client->recv();
                \Co\System::sleep(0.1);
            }
        });

Would you please explain what's happening? Can I call recv() in the loop without sleeping?

jasonterando commented 4 years ago

Newbie here, so hopefully this isn't bogus advice, but I ran into a similar issue, check out WaitGroup. Also, I think you have to surround the whole thing by \Co\Run (as opposed to Go). Maybe something like this?

// Psuedocode
\Co\run(function() {
   $wg = new Swoole\Coroutine\WaitGroup();
    for($loop = 1; $loop <= 5; $loop++) {
       go(function() {
          try {
             $wg->add();
             Co::sleep($loop);
             echo "Done $loop\n";
          } catch($e) {
             echo "Error: " . $e->getMessage();
         } finally {
             $wg->done();
         }
       });
   }
   $wg->wait(10000); // wait for max 10 sec timeout
});

https://www.swoole.co.uk/docs/modules/swoole-coroutine-waitgroup

idealogica commented 4 years ago

Thank you for the answer, but I'm not sure I understand it. I don't need to sync coroutines. I just need \Co\System::sleep(2); to finish sleeping every 7 seconds so it outputs "after timeout" string without placing additional \Co\System::sleep(0.1); call in the loop. Also when I call Co\run() it shows the warning Swoole\Coroutine\Scheduler::start(): eventLoop has already been created. unable to start Swoole\Coroutine\Scheduler in @swoole-src/library/alias_ns.php on line 17

matyhtf commented 4 years ago
$this->client->recv();

Is it blocking io, or is there an infinite loop ?

idealogica commented 4 years ago

It's blocking this coroutine:

                    go(function () {
                        dump('before timeout');
                        \Co\System::sleep(2);
                        dump('after timeout');
                    });

on this line: \Co\System::sleep(2); Infinite loop is not a problem.

jamie-beck commented 3 years ago

I'm experiencing the same behaviour where the co::sleep(1) never returns just like @idealogica

` php --ri swoole

swoole

Swoole => enabled Author => Swoole Team team@swoole.com Version => 4.5.8 Built => Nov 21 2020 08:19:29 coroutine => enabled trace_log => enabled epoll => enabled eventfd => enabled signalfd => enabled cpu_affinity => enabled spinlock => enabled rwlock => enabled sockets => enabled openssl => OpenSSL 1.1.1c FIPS 28 May 2019 http2 => enabled json => enabled pcre => enabled zlib => 1.2.11 brotli => E16777222/D16777222 mutex_timedlock => enabled pthread_barrier => enabled futex => 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 `

sy-records commented 3 years ago
\Co::set(['enable_preemptive_scheduler' => true]);
\Co\run(function () {
    $nextTime = time() + 5;
    while (1) {
        if (time() >= $nextTime) {
            $nextTime = time() + 5;
            go(function () {
                var_dump('before timeout');
                \Co\System::sleep(1);
                var_dump('after timeout');
            });
        }
    }
});
jamie-beck commented 3 years ago

@sy-records Thank you, adding the following did fix my issue. \Co::set(['enable_preemptive_scheduler' => true]);

Is this a bug that will be fixed or an undocumented feature that will be documented :-)

Thanks again. I never would have known to do that.

sy-records commented 3 years ago

This is an option added from v4.4.0.

Added a new Coroutine Preemptive Scheduler to prevent the Coroutine from taking too long CPU time to cause other Coroutine to starve.

idealogica commented 3 years ago

Thank you so much for the solution. I was also confused with this, and didn't know that there is a special setting to fix thus issue

idealogica commented 3 years ago

@sy-records Is there any enable_preemptive_scheduler drawbacks? Why isn't it enabled by default? I tried to enable it on production and it looks like it consumed all memory on the servers...

sy-records commented 3 years ago

You may need to check the logic of your code.

idealogica commented 3 years ago

True, the memory consumption caused by incorrect socket_buffer_size option value. enable_preemptive_scheduler works perfectly under load. Thank you for your help. I guess this can be closed now.