The promise of asynchronous PHP
-
Upload
wim-godden -
Category
Technology
-
view
311 -
download
3
Transcript of The promise of asynchronous PHP
Who am I ?
Wim Godden (@wimgtr)
Founder of Cu.be Solutions (http://cu.be)
Open Source developer since 1997
Developer of OpenX, PHPCompatibility, PHPConsistent, ...
Speaker at Open Source conferences
Blocking I/O
Disk reading/writing
Network reading/writing
Communication with DB (with some exceptions)
Sending mail
...
Non-blocking = good
Work on multiple things at same time
Not entirely sequential anymore
How do you know something is finished ?
→ Events !
Callback hell
$one->do(function ($two) { $two->do(function ($three) { $three->do(function ($stillcounting) { $stillcounting->get(function() { throw new IQuitException(); }); }); });});
State of asynchronous PHP
Several built-in functions
Several libraries (using the built-in functions)
Facebook Hack
Pthreadsclass WebRequest extends Thread { public $url; public $response; public function __construct($url){ $this->url = $url; } public function run() { $this->response = file_get_contents($this->url); }}
$request = new WebRequest("http://cu.be");
if ($request->start()) { /* do some work here */ $a = array_fill(0, 10000000, 'test'); for ($i = 0; $i < count($a); $i++) {}
/* ensure we have data */ $request->join(); var_dump($request->response);}
pcntl_fork
Clones PHP process
Multiprocessing, not multithreading
No communication between processes
No Apache
popenchild.php
<?php/* Do some work */echo 'Output here';
main.php
<?php// open child process$child = popen('php child.php', 'r');
/* * Do some work, while already doing other * work in the child process. */
// get response from child (if any) as soon at it's ready:$response = stream_get_contents($child);
Warning : doesn't behave sam
e
on all operating systems !
curl_multi_select$ch1 = curl_init();$ch2 = curl_init();
curl_setopt($ch1, CURLOPT_URL, "http://www.google.com/");curl_setopt($ch2, CURLOPT_URL, "http://www.yahoo.com/");
$mh = curl_multi_init();curl_multi_add_handle($mh,$ch1);curl_multi_add_handle($mh,$ch2);
$active = null;do { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); usleep(1000);} while (curl_multi_select($mh) === -1);
while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); }}
curl_multi_remove_handle($mh, $ch1);curl_multi_remove_handle($mh, $ch2);curl_multi_close($mh);
Using Curl as async system
$c = curl_init();curl_setopt($c, CURLOPT_URL, 'http://www.long.process.com/calling-here?action=go&from=4&to=40000');curl_setopt($c, CURLOPT_FOLLOW_LOCATION, true);curl_setopt($c, CURLOPT_FRESH_CONNECT, true);curl_setopt($c, CURLOPT_TIMEOUT_MS, 1);curl_exec($c);curl_close($c);
// Code continues after 1ms timeout
Libevent, libev, libuv
Event handling libraries
PHP extensions
libevent = also used by Memcached
libev = not available on Windows
ReactPHP
Event-driven non-blocking I/O library
Written in PHP
Provides event-driven interface
Implements event loop
ReactPHP – a simple webserver
$loop = new React\EventLoop\Factory::create();$socket = new React\Socket\Server($loop);$http = new React\Http\Server($socket, $loop);$http->on('request', function ($request, $response) { $response->writeHead(200); $response->send("Hello world!\n");});$socket->listen(80);$loop->run();
ReactPHP – Deferred & Promise
Computation to be performed = Deferred(React\Promise\Deferred)
2 possible status :Resolved
Rejected
ReactPHP – Deferred & Promise
$deferred = new React\Promise\Deferred();$promise = $deferred->promise() ->then( function ($value) { // Resolved, use $value }, function ($reason) { // Rejected, show or log $reason }, function ($status) { // Progress changed, show or log $status } );
ReactPHP – Deferred & Promise
$deferred = new Someclass\Extends\Promise\Deferred();$promise = $deferred->promise() ->then( function ($value) { // Resolved, use $value }, function ($reason) { // Rejected, show or log $reason }, function ($status) { // Progress changed, show or log $status } );
Hostname lookup – the old way
$hostnames = explode(',', $_POST['hostnames']);$hostnames = FilterDangerousHostnames($hostnames);$success = array();foreach ($hostnames as $hostname) { $ip = gethostbyname($hostname); if ($ip != $hostname) { $success[] = “$hostname ($ip)”; }}echo 'Success resolving ' . implode(', ', $success);
Sequential→ 10 hostnames → 10 sequential lookups
DNS timeouts → delays
Hostname lookup – the async way$loop = React\EventLoop\Factory::create();$factory = new React\Dns\Resolver\Factory();$dns = $factory->create('8.8.8.8', $loop);
$hostnames = explode(',', $_POST['hostnames']);$hostnames = FilterDangerousHostnames($hostnames);$promises = array();foreach ($hostnames as $hostname) { $promises[] = $dns->resolve($hostname) ->then( function($ip) use ($hostname) { return "$hostname ($ip)"; }, function($error) { return ''; } );}\React\Promise\all($promises)->then( function($hostnames) { $hostnames = array_filter($hostnames, 'strlen'); echo 'Success in resolving ' . implode(', ', $hostnames) . "\n"; });$loop->run();
ReactPHP – Chaining then() statements
$promise = $deferred->promise() ->then( function ($a) { return $a * 2; } ) ->then( function ($b) { return $b * 2; } ) ->then( function ($c) { echo 'c is now ' . $c; } );$deferred->resolve(1); // Will output 'c is now 4'
ReactPHP – Chaining then() statements
$promise = $deferred->promise() ->then( function ($a) { if ($a > 5) { return $a; } else { throw new Exception('Too small'); } } ) ->then( null, function ($e) { echo "We got this exception : " . $e->getMessage(); } );$deferred->resolve(10); // Will output nothing$deferred->resolve(1); // Will output : We got this exception : Too small
ReactPHP – Promises vs Streams
Promises→ Very useful
→ But : limited to simple return values
Streams→ Much more powerful
→ Also somewhat more complex
ReactPHP - Streams
Either :Readable
Writable
Both
Example :Through stream = filter
Limited only by your imagination !
$loop = React\EventLoop\Factory::create();
$source = new React\Stream\Stream(fopen('source.txt', 'r'), $loop);$filter = new MyLib\Stream\AlnumFilter();$dest = new React\Stream\Stream(fopen('dest.txt', 'w'), $loop);
$source->pipe($filter)->pipe($dest);
$loop->run();
$loop = React\EventLoop\Factory::create();$socket = new React\Socket\Server($loop);$clients = new SplObjectStorage();$i = 0;$socket->on('connection', function($connection) use($clients, &$i) { $connection->id = ++$i; $connection->write('Enter your nickname: '); $connection->on('data', function($message) use($clients, $connection) { if (empty($connection->nickName)) { $connection->nickName = $message; } else { foreach ($clients as $client) { if ($client->id == $connection->id) { continue; } $client->write( sprintf( '<%s> %s', $connection->nickName, $message ) ); } } }); $clients->attach($connection);});$socket->listen(1337);$loop->run();
Some golden rules & warnings
Golden rule #1 : asynchronous != faster code
Golden rule #2 : don't assume your code will remain as fast
Golden rule #3 : if you don't need a response, don't wait for one
Warning : async does not guarantee execution order !