2
头图

I believe that many users wanted an API to cancel the coroutine before, but it has not been added. Now it has been added in the v4.7 version:

For specific implementation, see: #4247 , #4249

New API & constants

Two new APIs have been added, namely

Co::cancel($cid): bool
Used to cancel a coroutine, but cannot initiate a cancel operation on the current coroutine

with

Co::isCanceled(): bool
Used to determine whether the current coroutine has been cancelled

Three new error codes have been added:

constantmeaning
SWOOLE_ERROR_CO_CANNOT_CANCELCoroutine cannot be cancelled
SWOOLE_ERROR_CO_NOT_EXISTSCoroutine does not exist
SWOOLE_ERROR_CO_CANCELEDThe coroutine has been cancelled

Description

This API is used to cancel another or event callback

Only the coroutine in the cancelable operation can be cancelled. When a coroutine is successfully cancelled, the context will be immediately switched to the corresponding coroutine

Try to cancel a coroutine that is in an uncancelable operation, 060bedd43b9b39 will return Co::cancel() succeeds, and true false ,

swoole_last_error() at this time, there may be two situations:

  1. SWOOLE_ERROR_CO_NOT_EXISTS does not exist 060bedd43b9b8f
  2. The coroutine is in an SWOOLE_ERROR_CO_CANNOT_CANCEL

You can use Co::isCanceled() to determine whether the current operation is manually canceled. If manual cancellation ends normally, it will return to true . If it fails, it will return to false

Currently, the cancellation of most of the coroutine APIs is basically supported, including:

  1. socket
  2. AsyncIO (fread, gethostbyname ...)
  3. sleep
  4. waitSignal
  5. wait/waitpid
  6. waitEvent
  7. Co::suspend/Co::yield
  8. channel
  9. native curl (SWOOLE_HOOK_NATIVE_CURL)

There are two uninterruptible scenarios

  1. A coroutine forced to be switched by the CPU interrupt scheduler
  2. During file lock operation
However, cancellation may be allowed in subsequent versions, so stay tuned

scenes to be used

The cancellation function based on the coroutine can be implemented on the user side:

  • Overtime circuit breaker based on coroutine granularity

In the previous version, suspended coroutines cannot be actively scheduled. Co::resume() Co::cancel() is that not only the manual Co::yield() coroutine can be cancelled, but all the coroutines that are allowed to be cancelled can be cancelled.

  • Better API design

Unlike traditional PHP APIs with similar functions, a large number of APIs in Swoole have added timeout parameters. Of course, there are also some that are difficult to add or inappropriate to add timeout parameters, such as file operation series functions. Now everything is possible, yes. Realize the timeout of any IO operation at the PHP layer without relying on the underlying API design

Example

Let's take a look at some sample codes to understand the usage of coroutine cancellation:

Cannot initiate cancellation of the current coroutine and non-existent coroutine

A coroutine is automatically created in the coroutine container, and Co::cancel() is called to cancel it. At this time, it cannot be cancelled. At the same time, there is only one coroutine in the coroutine container, and it is impossible to cancel a non-existent coroutine.

use Swoole\Coroutine;
use function Swoole\Coroutine\run;

run(function () {
    assert(Coroutine::cancel(Coroutine::getCid()) === false);
    assert(swoole_last_error() === SWOOLE_ERROR_CO_CANNOT_CANCEL);

    assert(Coroutine::cancel(999) === false);
    assert(swoole_last_error() === SWOOLE_ERROR_CO_NOT_EXISTS);
});

The following are three examples demonstrate in Co::suspend/Co::yield , AsyncIO and channel use sleep to forge timeout after cancellation

Co::suspend/Co::yield

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

run(function () {
    $cid = Coroutine::getCid();
    go(function () use ($cid) {
        System::sleep(0.002);
        assert(Coroutine::cancel($cid) === true);
    });
    $retval = Coroutine::suspend();
    echo "Done\n";
    assert($retval === false);
    assert(swoole_last_error() === SWOOLE_ERROR_CO_CANCELED);
});

AsyncIO

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

run(function () {
    $cid = Coroutine::getCid();
    Event::defer(function () use ($cid) {
        assert(Coroutine::cancel($cid) === true);
    });
    $retval = System::gethostbyname('www.baidu.com');
    echo "Done\n";
    assert($retval === false);
    assert(swoole_last_error() === SWOOLE_ERROR_AIO_CANCELED);
});

channel

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

run(function () {
    $chan = new Coroutine\Channel(1);
    $cid = Coroutine::getCid();
    go(function () use ($cid) {
        System::sleep(0.002);
        assert(Coroutine::cancel($cid) === true);
    });

    assert($chan->push("hello world [1]", 100) === true);
    assert(Coroutine::isCanceled() === false);
    assert($chan->errCode === SWOOLE_CHANNEL_OK);

    assert($chan->push("hello world [2]", 100) === false);
    assert(Coroutine::isCanceled() === true);
    assert($chan->errCode === SWOOLE_CHANNEL_CANCELED);

    echo "Done\n";
});

When external use Co::cancel() cancel the suspended state of a coroutine, the API called by the coroutine will immediately return to failure, and the program code will continue to execute downward.

By judging the return value and error code of the coroutine operation function/method, or using Co::isCanceled() judge whether it is cancelled.


沈唁
1.9k 声望1.2k 粉丝