The promise of asynchronous PHP

46
The promise of asynchronous PHP Wim Godden Cu.be Solutions @wimgtr

Transcript of The promise of asynchronous PHP

The promise of asynchronous PHP

Wim GoddenCu.be Solutions

@wimgtr

Who am I ?

Wim Godden (@wimgtr)

Where I'm from

Where I'm from

Where I'm from

Where I'm from

Where I'm from

Where I'm from

My town

My town

Belgium – the traffic

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

Who are you ?

Developers ?

Ever worked with asynchronous PHP libraries ?

Node.JS ?

Synchronous processing

Asynchronous processing

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 !

Events

Start

Progress update

End (successfully)

Failed

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

Pthreads

PECL extension

Multithreading

Requires zts (thread-safe)

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 - structure

Event Loop

Stream

Socket

HTTP

→ stream_select() / libevent / libev

ReactPHP - structure

Event Loop

Stream

Socket

HTTPClient DNSWHOIS HTTPClient

WebsocketSOCKS IRC

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 } );

ReactPHP – Promises example

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 !

Questions ?

Questions ?

Thanks !

@wimgtr

[email protected]

Please provide some feedback : http://joind.in/14264