recoilphp / recoil

Asynchronous coroutines for PHP 7.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Database transactions and Recoil::suspend and resume

djsharman opened this issue · comments

Hi again,

I am currently trying to find a nice way of handling Async DB transactions using mysqli.

Currently I have some test code that works roughly along the lines of:

        $value = yield Recoil::suspend(function (Strand $strand) use($mysql ) {
            //

            $mysql->query('select * from test where num=3')->then(

                    function (Result $result) use ($strand) {
                        $ext_data = $result->all();
                        print 'Done DB Lookup' . PHP_EOL;
                        $strand->resume($ext_data);


                    },
                    function ($error) use ($strand) {
                        error_log($error);
                        print 'Error DB Lookup' . PHP_EOL;
                        $strand->resume(null);
                    }
                    );

        });
        print_r ($value);

print 'Resuming main strand.' . PHP_EOL;

});

$kernel->wait();

`

I am planning to have the $mysql connection object to be available to all classes using a static class somewhere. The interesting part here is that when the function suspends and waits for the DB to return some results, the loop can have processed some other calls (e.g. Ratchet messages for other users), and perhaps started another DB connection. So when the $strand resumes the whole application can be faced with a new DBConnection stored globally. Not a problem for normal queries, but when someone wants to encapsulate a few DB calls within a transaction having the same DB connection is essential. What would you suggest as the best way to store this connection and restore it on the resumption of the strand?

That was a bit meandering I hope you understand what I am getting at.

Regards,

Darren

In this case, I'd avoid having a global connection, and rather share a "connection pool" that maintains (or opens) mysql connections as necessary. When a new request comes in from the websocket, you can ask the pool for a connection (suspending the current strand until one becomes available). The pool would not "give out" the same connection to multiple strands at the same time. It would look something like this:

/**
 * Some imaginary method somewhere ...
 */
public function handleWebsocketRequest($request) {
    $mysql = yield $this->pool->acquireConnection();

    try { 
        // do work with $mysql
    }  finally  {
        yield $this->pool->releaseConnection($mysql); // return the connection to the pool for other strands to use
    }
}

Just as an aside, it looks like you're manually suspending and resuming the strand in order to chain onto a promise. Recoil supports thennables (any object with a then method) out of the box, so where you have:

$mysql->query('select * from test where num=3')->then(
    function (Result $result) use ($strand) {
        $ext_data = $result->all();
        print 'Done DB Lookup' . PHP_EOL;
        $strand->resume($ext_data);
    },
    function ($error) use ($strand) {
        error_log($error);
        print 'Error DB Lookup' . PHP_EOL;
        $strand->resume(null);
    }
);

You should be able to just write:

try {
    $ext_data = (yield $mysql->query('select * from test where num=3'))->all();
    print 'Done DB Lookup' . PHP_EOL;
} catch (Exception $e) {
    error_log($e->getMessage());
}

If the $error passed to the promise rejection handler is already an exception, it's thrown as-is; otherwise, it is wrapped in an Recoil\Exception\RejectedException.