Context class is used to manage context in spool to prevent data disorder

Time:2021-6-10

In the previous article, we said: class static variables cannot be usedClass::$array/Global variablesglobal $_array/Global object properties$object->array/Other super global variables$GLOBALSAnd so on, to avoid data confusion.

That’s because the spool is resident in memory, and these global variables are shared. When a concurrent request is encountered, the content written by coroutine a may be changed by coroutine B because the coroutine is suspended or the scheduling of coroutine B is concurrent, resulting in inconsistent context content.

The solution is to join a collaborative process based onIDTo save the contextContextClass to isolate the context / global variables between different coroutines, and then clean up the context when the coroutine exits.

use Swoole\Coroutine;

class Context
{
    protected static $pool = [];

    //Get data based on the collaborative process' ID '
    static function get($key)
    {
        $cid = Coroutine::getCid();
        if ($cid < 0)
        {
            return null;
        }
        if(isset(self::$pool[$cid][$key])){
            return self::$pool[$cid][$key];
        }
        return null;
    }

    //Write data based on CO program 'ID'
    static function put($key, $item)
    {
        $cid = Coroutine::getCid();
        if ($cid > 0)
        {
            self::$pool[$cid][$key] = $item;
        }

    }

    //Delete data based on CO program 'ID'
    static function delete($key = null)
    {
        $cid = Coroutine::getCid();
        if ($cid > 0)
        {
            if($key){
                unset(self::$pool[$cid][$key]);
            }else{
                unset(self::$pool[$cid]);
            }
        }
    }
}

Use example:

$server = new Swoole\Http\Server('127.0.0.1', 9501);

$server->on('request', function ($request, $response) {
    if ($request->server['request_uri'] == '/a') {
        Context::put('name', 'a');
        co::sleep(1.0);
        echo Context::get('name');
        $response->end(Context::get('name'));
        //Clean up when exiting the coroutine
        Context::delete('name');
    } else {
        Context::put('name', 'b');
        $response->end();
        //Clean up when exiting the coroutine
        Context::delete();
    }
});
$server->start();

Simulate concurrent testing:

curl http://127.0.0.1:9501/a
curl http://127.0.0.1:9501/b