swoole-src icon indicating copy to clipboard operation
swoole-src copied to clipboard

We need a way to timeout/kill a Swoole\Coroutine [enhancement]

Open ValiDrv opened this issue 4 years ago • 4 comments

Please answer these questions before submitting your issue. Thanks!

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

Coroutines are awesome, but we need a way to time them out and stop their processing, either manually after a WaitGroup, or automatically after a Barrier or maybe go(function() {..}, SWOOLE_COROUTINE_TIMEOUT => 1)

Example:

  • The code with [***] should not run if we somehow killed this coroutine before the end of the First IO operation.
  • If that First IO operation takes longer than the timeout, it's output should be discarded (since the rest of the the coroutine would not run).
  • If we timeout during some heavy PHP non IO code/coroutine, then that PHP code could run normally.
  • if we have some defer() code, then it should run before the coroutine is closed (timed out/killed, or normally)
  • I'm not sure if this can be done in the PHP code efficiently, but Swoole can keep track of these flags internally, and acting upon them when dealing with coroutines.
<?php declare(strict_types=1);

use Swoole\Coroutine\Barrier;
use Swoole\Coroutine\System;
use function Swoole\Coroutine\run;
use Swoole\Coroutine;

run(function () {
    $barrier = Barrier::make();

    $N = 4;
    $COIds = [];    # Keep track of running coroutines

    foreach (range(1, $N) as $i) {
        Coroutine::create(function () use ($barrier, $i, &$COIds) {
            echo "\n$i STARTED Cid:" . Co::getCid();
            $COIds[Co::getCid()] = true;          # Mark as started
            System::sleep($i - 0.5);              # First IO operation
            # [***] Do some heavy CPU processing
            System::sleep(0.4);                   # [***] Make more IO operations
            $COIds[Co::getCid()] = false;         # [***] Mark as finished
            echo "\n$i DONE Cid:" . Co::getCid(); # [***] 
        });
    }

    Barrier::wait($barrier, 2);     # Wait 2 sec for the the first two coroutines to finish.

    $COIds = array_filter($COIds);  # Trim out finished coroutines
    echo "\nNeed a way to kill these coroutines: " . implode(', ', array_keys($COIds));
});

2. What did you expect to see?

1 STARTED Cid:2
2 STARTED Cid:3
3 STARTED Cid:4
4 STARTED Cid:5
1 DONE Cid:2
2 DONE Cid:3
Need a way to kill these coroutines: 4, 5
real	0m1.933s
user	0m0.020s
sys	0m0.012s
  1. What did you see instead?
1 STARTED Cid:2
2 STARTED Cid:3
3 STARTED Cid:4
4 STARTED Cid:5
1 DONE Cid:2
2 DONE Cid:3
Need a way to kill these coroutines: 4, 5
3 DONE Cid:4
4 DONE Cid:5
real	0m3.933s
user	0m0.020s
sys	0m0.012s

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

swoole

Swoole => enabled
Author => Swoole Team <[email protected]>
Version => 4.5.9
Built => Dec  3 2020 14:16:55
coroutine => 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
zlib => 1.2.11
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

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

Linux b4dfe6918ddf 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 GNU/Linux

PHP 8.0.0 (cli) (built: Dec  1 2020 03:14:26) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0, Copyright (c), by Zend Technologies

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 8.3.0-6' --with-bugurl=file:///usr/share/doc/gcc-8/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-8 --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-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 8.3.0 (Debian 8.3.0-6) 

ValiDrv avatar Jan 04 '21 00:01 ValiDrv

Did you fill in your 2 and 3 incorrectly? I feel like it's the other way around

sy-records avatar Jan 04 '21 11:01 sy-records

Did you fill in your 2 and 3 incorrectly? I feel like it's the other way around

thank you :) had them the other way around

ValiDrv avatar Jan 04 '21 12:01 ValiDrv

I think what you need here is threads. The main difference between coroutines and threads is that with threads some external system (usually the OS) has the ability to take control away from your application. In coroutines you are controlling when you yield the control away.

Having an ability to interact with a running coroutine from external location (context switching or killing) will mean that you will loose all the advantages of using a coroutine.

Imagine situation where the coroutine is moving items between arrays:

$item = array_pop($array1);
$array2[] = $item;

If you switch in between those operations, or kill the coroutine at that point, then you will loose the item (it wont be in either array). That is the main problem of multithreading. With coroutines you manually specify when its safe to do a switch and when its safe to end your work, so you don't need to deal with multithreading/synchronization issues.

hubertnnn avatar Feb 12 '21 10:02 hubertnnn

Your moving items example if exactly why this is needed.

Say instead of array_pop you have some IO operation, and instead of pushing the data to some array, you yield it to another coroutine, and want to stop if X is received, or after exactly 30sec.

You want some way to "forget" the array_poping coroutine and all its variables after that wait group timeout or when X is found, so you can clear it's memory and so on.

The way it works without this feature is that you need this logic everywhere inside the array_poping coroutine.

Something like Coroutine:: shutdown($cid) that calls the Coroutine::defer() and ends $cid

ValiDrv avatar Feb 12 '21 12:02 ValiDrv