Should an existing ReactPHP loop be reused? - php

Should a single ReactPHP loop be used for multiple purposes as shown below, or should a new loop be created and used for each purpose? If reused, the loop is obviously already running, so how does one ensure it is not inadvertently executed before completely configured such as in my process example (i.e. between $process->start($loop) and $process->stdout->on(...))?
<?php
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) use($loop) {
$connection->on('data', function ($data) use ($connection, $loop) {
$loop->addTimer(10, function() {
syslog(LOG_INFO, 'Do something upon non-repeating timer completion');
});
$process = new \React\ChildProcess\Process($cmd);
$process->start($loop);
$process->stdout->on('data', function ($chunk) {
syslog(LOG_INFO, 'Do something with $chunk');
});
});
});
$loop->run();

You should only use one loop and share it within your application. What I always do is first ensure everything is set up and then start the loop. You can do this with an event dispatcher like PSR-14 defines for example.

Related

PHP Swoole: how to make variables persistent between requests?

As a simple example, say I simply want to increment a counter when someone connects. The code I have is
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
$server = new Server("0.0.0.0", 9502);
$users = [];
$server->on("Start", function(Server $server){
echo "Swoole WebSocket Server is started at http://127.0.0.1:9502\n";
});
$server->on('Open', function(Server $server, Swoole\Http\Request $request){
global $count;
array_push($users, $request->fd);
var_dump($users);
});
Note: fd is simply the connection ID of the user (maybe short for file descriptor?), so I'm basically trying to get a simple array of all the users that have connected - the problem is though, this value isn't persistant between requests - so when the 2nd user connects, the array becomes empty again
Does anyone know of a way to fix this? I know I could use a database, but that seems very wasteful/inefficient when I want to create a realtime site - so that's why I want to store/access all the data within the php script, if possible...without using any external storage
Not tested but basicly do:
<?php
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\Table;
$server = new Server("0.0.0.0", 9502);
$table = new Table(1024);
$table->column('user_id', Swoole\Table::TYPE_STRING, 64);
$table->create();
$server->on("Start", function(Server $server){
echo "Swoole WebSocket Server is started at http://127.0.0.1:9502\n";
});
$server->on('Open', function(Server $server, Swoole\Http\Request $request) use ($table) {
$table->set($request->fd,['user_id'=>$request->fd]);
var_dump($table->count());
});
Read more about the possibilities here: openswoole.com/docs/modules/swoole-table

How do I use a Ratchet\Server\IoServer object after run executed?

I want to run a function that iterates through a generator class. The generator functions would run as long as the Ratchet connection is alive. All I need to do is to make this happen after the run method is executed:
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;
require dirname(__DIR__) . '/xxx/vendor/autoload.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8180,
'0.0.0.0'
);
$server->run();
This is the method I need to run in the server after it is started:
function generatorFunction()
{
$products = r\table("tableOne")->changes()->run($conn);
foreach ($products as $product) {
yield $product['new_val'];
}
}
Previously I was calling the function before $server->run() like this:
for ( $gen = generatorFunction(); $gen->valid(); $gen->next()) {
var_dump($gen->current());
}
$server->run();
But this doesn't allow the client to establish a connection to the Ratchet server. I suspect it never comes to $server->run() as the generator class is being iterated.
So now, I want to start the server first, then call this generator method so that it can keep listening to changes in rethinkdb.
How do I do that?
Let's start by example:
<?php
require 'vendor/autoload.php';
class Chat implements \Ratchet\MessageComponentInterface {
function onOpen(\Ratchet\ConnectionInterface $conn) { echo "connected.\n"; }
function onClose(\Ratchet\ConnectionInterface $conn) {}
function onError(\Ratchet\ConnectionInterface $conn, \Exception $e) {}
function onMessage(\Ratchet\ConnectionInterface $from, $msg) {}
}
$loop = \React\EventLoop\Factory::create(); // create EventLoop best for given environment
$socket = new \React\Socket\Server('0.0.0.0:8180', $loop); // make a new socket to listen to (don't forget to change 'address:port' string)
$server = new \Ratchet\Server\IoServer(
/* same things that go into IoServer::factory */
new \Ratchet\Http\HttpServer(
new \Ratchet\WebSocket\WsServer(
new Chat() // dummy chat to test things out
)
),
/* our socket and loop objects */
$socket,
$loop
);
$loop->addPeriodicTimer(1, function (\React\EventLoop\Timer\Timer $timer) {
echo "echo from timer!\n";
});
$server->run();
To achieve what you need you don't have to run the loop before or after the $server->run() but it needs to be run simultaneously.
For that you need to get deeper than Ratchet - to ReactPHP and its EventLoop. If you have access to the loop interface then adding a timer (that executes once) or a periodic timer (every nth second) is a piece of cake.

React PHP timeout always resolves

I was playing around with React and wanted to try to get a working timeout function. Following (sort of) the examples and Unit tests from https://github.com/reactphp/promise-timer#timeout I came up with:
use React\Promise\Timer;
$promise = uncertainOperation();
$loop = \React\EventLoop\Factory::create();
Timer\timeout($promise, 1, $loop)->then(
function ($value) {
var_dump($value);
}
,
function ($error) {
var_dump($error);
}
);
$loop->run();
function uncertainOperation() {
return new React\Promise\Promise(
function ($resolve) {
for($i = 0; $i < 30000000; $i++) { }
$resolve("Done");
}
);
}
But this always resolves with "Done" no matter how low I set the time in Timer\timeout. What am I missing?
The issue with your code is that it blocks. And it synchronously resolves the promise. It does nowhere return to the event loop driver, so that it could schedule the timeout watcher.
Try changing your code to use a timeout as simulation of e.g. a network timeout.
function uncertainOperation($loop) {
return new React\Promise\Promise(
function ($resolve) use ($loop) {
$loop->addTimer(5, function () {
$resolve("Done");
});
}
);
}
$loop->run();
Unfortunately, you have to pass around the loop in React.

voryx thruway multiple publish

I need to publish messages from php script, I can publish a single message fine. But now I need to publish different messages in loop, can't find proper way how to do it, here is what I tried:
$counter = 0;
$closure = function (\Thruway\ClientSession $session) use ($connection, &$counter) {
//$counter will be always 5
$session->publish('com.example.hello', ['Hello, world from PHP!!! '.$counter], [], ["acknowledge" => true])->then(
function () use ($connection) {
$connection->close(); //You must close the connection or this will hang
echo "Publish Acknowledged!\n";
},
function ($error) {
// publish failed
echo "Publish Error {$error}\n";
}
);
};
while($counter<5){
$connection->on('open', $closure);
$counter++;
}
$connection->open();
Here I want to publish $counter value to subscribers but the value is always 5, 1.Is there a way that I open connection before loop and then in loop I publish messages
2.How to access to $session->publish() from loop ?
Thanks!
There are a couple different ways to accomplish this. Most simply:
$client = new \Thruway\Peer\Client('realm1');
$client->setAttemptRetry(false);
$client->addTransportProvider(new \Thruway\Transport\PawlTransportProvider('ws://127.0.0.1:9090'));
$client->on('open', function (\Thruway\ClientSession $clientSession) {
for ($i = 0; $i < 5; $i++) {
$clientSession->publish('com.example.hello', ['Hello #' . $i]);
}
$clientSession->close();
});
$client->start();
There is nothing wrong with making many short connections to the router. If you are running in a daemon process though, it would probably make more sense to setup something that just uses the same client connection and then use the react loop to manage the loop instead of while(1):
$loop = \React\EventLoop\Factory::create();
$client = new \Thruway\Peer\Client('realm1', $loop);
$client->addTransportProvider(new \Thruway\Transport\PawlTransportProvider('ws://127.0.0.1:9090'));
$loop->addPeriodicTimer(0.5, function () use ($client) {
// The other stuff you want to do every half second goes here
$session = $client->getSession();
if ($session && ($session->getState() == \Thruway\ClientSession::STATE_UP)) {
$session->publish('com.example.hello', ['Hello again']);
}
});
$client->start();
Notice that the $loop is now being passed into the client constructor and also that I got rid of the line disabling automatic reconnect (so if there are network issues, your script will reconnect).

Return synchronously when a React/Promise is resolved

I need to return from a function call once a React/Promise has been resolved. The basic idea is to fake a synchronous call from an ansynchronous one. This means that the outer function must return a value once a promise has been resolved or rejected.
This is to create a driver for RedBeanPHP using React/Mysql. I am aware that this will likely lead to CPU starvation in the React event loop.
My initial idea was to use a generator then call yield inside a \React\Promise\Deferred::then callback.
function synchronous()
{
$result = asynchronous();
}
function asynchronous()
{
$deferred = new \React\Promise\Deferred;
$sleep = function() use ($deferred)
{
sleep(5);
$deferred->resolve(true);
};
$deferred->then(function($ret) {
yield $ret;
});
$sleep();
}
The PHP generator class, AFAICT, is only directly constructable by the PHP engine itself. The then callback would need to directly invoke send on the generator of the asynchronous function for this to work.
PHP lacks both continuations as well as generator delegation, which would make it possible to call yield from inside a nested callback, making this entirely impossible to achieve for the moment.
ReactPhp offers the async tools package which has an await function.
Code can then become:
function synchronous()
{
$result = \React\Async\await(asynchronous());
}
function asynchronous()
{
$deferred = new \React\Promise\Deferred;
$sleep = function() use ($deferred)
{
sleep(5);
$deferred->resolve(true);
};
$sleep();
return $deferred->promise();
}

Categories