├── README.md ├── _config.yml ├── docker ├── Dockerfile.ext-event └── Dockerfile.ext-phtreads └── src ├── Pthreads ├── ThreadExample.php └── ThreadPoolExample.php ├── ReactPHP ├── .gitignore ├── ChatServerExample.php ├── TimerExample.php ├── composer.json └── composer.lock ├── Stream ├── NonBlockingFileRead.php └── Select.php ├── coroutine └── echo.php └── fork └── fork.php /README.md: -------------------------------------------------------------------------------- 1 | # PHP Concurrency 2 | 3 | * [Definitions](#definitions) 4 | * [Asynchronous code execution](#asynchronous-code-execution) 5 | * [Event loop extensions](#event-loop-extensions) 6 | * [Event loop frameworks](#event-loop-frameworks) 7 | * [ReactPHP](#reactphp) 8 | * [AMP](#amp) 9 | * [icicleio](#icicleio) - deprecated in favor of [Amp v2.0](https://amphp.org) 10 | * [Kraken](#kraken) 11 | * [Workerman](#workerman) 12 | * [Multithread code execution](#multithread-code-execution) 13 | * [Thread extensions](#thread-extensions) 14 | * [Coroutines](#coroutines) 15 | * [Distributed locking](#distributed-locking) 16 | * Inter-process communication 17 | * [PCNTL Signals](http://php.net/manual/en/book.pcntl.php) 18 | * [Socket](http://php.net/manual/en/book.sockets.php) 19 | * Shared memory 20 | * [Shmop](http://php.net/manual/en/book.shmop.php) 21 | * [Semaphore](http://php.net/manual/en/book.sem.php) - Semaphore, Shared Memory and IPC 22 | * [Sync](http://php.net/manual/en/book.sync.php) - Semaphore, Mutex, Shared Memory and IPC 23 | * Mutex, Semaphore 24 | * [Semaphore](http://php.net/manual/en/book.sem.php) - Semaphore, Shared Memory and IPC 25 | * [Sync](http://php.net/manual/en/book.sync.php) - Semaphore, Mutex, Shared Memory and IPC 26 | * [Named pipe](http://php.net/manual/ru/function.posix-mkfifo.php) 27 | * [Expect](http://php.net/manual/en/book.expect.php) - allows to interact with processes through PTY 28 | * [Process management](#process-management) 29 | * [Tools](#tools) 30 | * External program execution 31 | * [System program execution (PHP manual)](http://php.net/manual/en/book.exec.php) 32 | * [popen (PHP manual)](http://php.net/manual/ru/function.popen.php) 33 | * [POSIX](http://php.net/manual/en/book.posix.php) - process ids 34 | * [Stream](#stream) 35 | * [PHP sources](https://github.com/php) 36 | * [Repositories related to the PHP Language](https://github.com/phplang) 37 | * RFC 38 | * Fiber 39 | * [RFC](https://wiki.php.net/rfc/fiber) 40 | * [Source](http://fiberphp.org/) 41 | * [Articles](#articles) 42 | 43 | ## Definitions 44 | 45 | [Concurrency](https://en.wikipedia.org/wiki/Concurrency_(computer_science)): ability of different parts of a program to be executed out-of-order. 46 | 47 | [Parallelism](https://en.wikipedia.org/wiki/Task_parallelism): Form of [parallel computing](https://en.wikipedia.org/wiki/Parallel_computing) in which execution of processes are carried out concurrently across multiple processors in parallel computing environments. 48 | 49 | [Multitasking](https://en.wikipedia.org/wiki/Computer_multitasking): is the concurrent execution of multiple tasks over a certain period of time. There are two types of multitasking: 50 | * [Co-operative, non-preemptive](https://en.wikipedia.org/wiki/Cooperative_multitasking): is a style of computer multitasking when process voluntarily yield control and all programs must cooperate for the entire scheduling scheme to work. 51 | * Asynchronous code execution 52 | * Coroutines 53 | * [Preemptive](https://en.wikipedia.org/wiki/Preemption_(computing)#PREEMPTIVE): involves the use of an interrupt mechanism which suspends the currently executing process and invokes a scheduler to determine which process should execute next. Therefore, all processes will get some amount of CPU time at any given time. 54 | * Threads 55 | * Forks 56 | 57 | ## Asynchronous code execution 58 | 59 | ### Event loop extensions 60 | 61 | | Name | Extension | Source | Version | PHP version | 62 | | -----|-----------|--------|---------|-------------| 63 | | ext-libevent |[PHP Manual](http://php.net/manual/en/book.libevent.php) | [PECL](https://pecl.php.net/package/libevent), [git.php.net](http://git.php.net/?p=pecl/event/libevent.git) | 0.1.0 | >= 5.3.0, < 7.0.0 | 64 | | ext-event |[PHP Manual](http://php.net/manual/en/book.event.php) | [PECL](https://pecl.php.net/package/event), [Bitbucket](https://bitbucket.org/osmanov/pecl-event/src) | 2.3.0 | >= 5.4, >= 7.0 | 65 | | ext-libev |[GitHub manual](https://github.com/m4rw3r/php-libev/) | [GitHub](https://github.com/m4rw3r/php-libev) | | < 7.0.0 | 66 | | ext-ev |[PHP Manual](http://php.net/manual/en/book.ev.php) | [PECL](https://pecl.php.net/package/ev), [BitBucket](https://bitbucket.org/osmanov/pecl-ev/src) | | >= 5.4, > 7.0 | 67 | | eio |[PHP Manual](http://php.net/manual/en/intro.eio.php)|[PECL](http://pecl.php.net/package/eio), [GitHub](https://github.com/rosmanov/pecl-eio)||| 68 | | swoole |[GitHub manual](https://github.com/swoole/swoole-src)|[GitHub](https://github.com/swoole/swoole-src)||| 69 | | libuv |[GitHub](https://github.com/bwoebi/php-uv)|[PECL](https://pecl.php.net/package/uv), [GitHub](https://github.com/bwoebi/php-uv)||| 70 | | concurent-php/ext-async | [GitHub manual](https://github.com/concurrent-php/ext-async) | [GitHub](https://github.com/concurrent-php/ext-async) | - | nightly | 71 | | ext-fiber | | https://wiki.php.net/rfc/fibers, https://github.com/amphp/ext-fiber | | | 72 | 73 | Links: 74 | * [Benchmarking libevent against libev](http://libev.schmorp.de/bench.html) - libev faster 75 | 76 | #### ext-libevent 77 | 78 | Has build-in OpenSSL library, non-blocking IO, http, dns. 79 | 80 | ``` 81 | pecl install libevent-0.1.0 82 | ``` 83 | 84 | #### ext-event 85 | 86 | Event is a PECL extension providing interface to libevent C library. 87 | 88 | The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts. 89 | 90 | Dockerfile: https://github.com/sokil/php-concurrency-labs/blob/master/docker/Dockerfile.ext-event 91 | 92 | #### ext-libev 93 | 94 | Library tries to improve `libevent`. But this is only event library, instead of libevent giving non-blocking IO, http, etc. 95 | 96 | Install `libev` library: 97 | 98 | ``` 99 | sudo apt-get install libev-dev 100 | ``` 101 | 102 | Install php extension `ext-libev`. Clone https://github.com/m4rw3r/php-libev and build extension: 103 | 104 | ``` 105 | phpize 106 | ./configure --with-libev 107 | make 108 | make install 109 | ``` 110 | 111 | #### libuv 112 | 113 | * [Official libuv site](http://libuv.org) 114 | * [Libuv source](https://github.com/libuv/libuv) 115 | 116 | ### Event loop frameworks 117 | 118 | * [ReactPHP](#reactphp) 119 | * [AMP](#amp) 120 | * [icicleio](#icicleio) 121 | * [Kraken](#kraken) 122 | * [Workerman](#workerman) 123 | 124 | #### ReactPHP 125 | 126 | Source: https://reactphp.org 127 | 128 | Examples: https://github.com/sokil/php-concurrency-labs/tree/master/src/ReactPHP 129 | 130 | ``` 131 | cd src/ReactPHP 132 | Docker build -t php-event . 133 | docker run --rm -v `pwd`:/src php-event php /src/TimerExample.php 134 | ``` 135 | 136 | ##### Articles 137 | 138 | * [Super Speed Symfony - ReactPHP](https://gnugat.github.io/2016/04/13/super-speed-sf-react-php.html) 139 | 140 | #### AMP 141 | 142 | Site: https://amphp.org 143 | 144 | Source: https://github.com/amphp 145 | 146 | #### icicleio 147 | 148 | Icicle is now deprecated in favor of [Amp v2.0](https://amphp.org) 149 | 150 | Source: https://github.com/icicleio 151 | 152 | #### Kraken 153 | 154 | Source: http://kraken-php.com 155 | 156 | #### Workerman 157 | 158 | Source: https://github.com/walkor/Workerman 159 | 160 | ## Multithread code execution 161 | 162 | ### Thread extensions 163 | 164 | |Name|Source|Manual| 165 | |----|------|------| 166 | |Pthreads|[GitHub](https://github.com/krakjoe/pthreads),[PECL](http://pecl.php.net/package/pthreads)|[PHP Manual](http://php.net/manual/ru/book.pthreads.php)| 167 | |Pht|[GitHub](https://github.com/tpunt/pht)|[PHP Manual](http://php.net/manual/en/book.pht.php)| 168 | 169 | #### Pthreads 170 | 171 | Dockerfile: https://github.com/sokil/php-concurrency-labs/blob/master/docker/Dockerfile.ext-phtreads 172 | 173 | Examples: https://github.com/sokil/php-concurrency-labs/tree/master/src/Pthreads 174 | 175 | ## Coroutines 176 | 177 | Coroutines are computer-program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes. 178 | 179 | ### Articles 180 | 181 | * [Wikipedia](https://en.wikipedia.org/wiki/Coroutine) 182 | * [Cooperative multitasking using coroutines](https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html) 183 | * [Co-operative PHP Multitasking](https://medium.com/async-php/co-operative-php-multitasking-ce4ef52858a0) 184 | 185 | ## Distributed locking 186 | 187 | * [Distributed lock manager (Wiki)](https://en.wikipedia.org/wiki/Distributed_lock_manager) 188 | * [Distributed locks with Redis](https://redis.io/topics/distlock) 189 | * Consul 190 | * [CLI](https://www.consul.io/docs/commands/lock.html) 191 | * API 192 | * [Session](https://www.consul.io/api/session.html) 193 | * [Semaphore](https://www.consul.io/docs/guides/semaphore.html) 194 | * [KV Storage, aquiring](https://www.consul.io/api/kv.html) 195 | 196 | ## Process management 197 | 198 | ### Articles 199 | 200 | * [Bring High Performance Into Your PHP App (with ReactPHP)](http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html) 201 | 202 | ### Tools 203 | 204 | * PHP-PM 205 | * GitHub: https://github.com/php-pm/php-pm 206 | * Spatie 207 | * https://github.com/spatie/async 208 | 209 | ## Tools 210 | 211 | ### Stream 212 | 213 | PHP Manual: http://php.net/manual/ru/book.stream.php 214 | 215 | Examples: https://github.com/sokil/php-concurrency-labs/tree/master/src/Stream 216 | 217 | ## Articles 218 | 219 | * [The Reactive Manifesto](https://www.reactivemanifesto.org/) 220 | * [Process Control Extensions (PHP Manual)](http://php.net/manual/en/refs.fileprocess.process.php) 221 | * [How to use the Linux AIO feature](https://github.com/littledan/linux-aio) 222 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /docker/Dockerfile.ext-event: -------------------------------------------------------------------------------- 1 | FROM php:7.2.6-cli-alpine3.6 2 | 3 | RUN apk update && \ 4 | apk add --virtual .build-deps \ 5 | autoconf \ 6 | make \ 7 | gcc \ 8 | g++ \ 9 | libevent-dev \ 10 | openssl-dev && \ 11 | docker-php-ext-install sockets pcntl && \ 12 | pecl channel-update pecl.php.net && \ 13 | yes '' | pecl install -o event-2.3.0 && \ 14 | # ini name configured to load ext after sockets.so 15 | docker-php-ext-enable --ini-name=z-event.ini event.so && \ 16 | apk del .build-deps && \ 17 | apk add \ 18 | bash \ 19 | libevent \ 20 | openssl -------------------------------------------------------------------------------- /docker/Dockerfile.ext-phtreads: -------------------------------------------------------------------------------- 1 | FROM php:7.2.6-zts-alpine3.7 2 | 3 | # PECL extension currently not stable for PHP > 7.1 4 | 5 | RUN apk update && \ 6 | apk add --virtual .build-deps \ 7 | autoconf \ 8 | g++ \ 9 | make \ 10 | git \ 11 | openssh-client && \ 12 | git clone https://github.com/krakjoe/pthreads.git /pthreads && \ 13 | cd /pthreads && \ 14 | phpize && \ 15 | ./configure && \ 16 | make install && \ 17 | docker-php-ext-enable pthreads && \ 18 | rm -rf /pthreads && \ 19 | apk del .build-deps && \ 20 | apk add \ 21 | bash -------------------------------------------------------------------------------- /src/Pthreads/ThreadExample.php: -------------------------------------------------------------------------------- 1 | numbersToSum = $numbersToSum; 26 | } 27 | 28 | /** 29 | * Execute thread 30 | */ 31 | public function run() 32 | { 33 | $this->sum = array_sum((array)$this->numbersToSum); 34 | } 35 | 36 | /** 37 | * Get sum result 38 | * 39 | * @return int 40 | */ 41 | public function getSum(): int 42 | { 43 | return $this->sum; 44 | } 45 | }; 46 | 47 | $summariserThread = new SummariserThread(1, 2, 3, 4, 5, 6, 7); 48 | 49 | if (!$summariserThread->start()) { 50 | echo 'Thread not started'; 51 | } 52 | 53 | if (!$summariserThread->join()) { 54 | echo 'Thread not joined'; 55 | } 56 | 57 | echo $summariserThread->getSum() . PHP_EOL; -------------------------------------------------------------------------------- /src/Pthreads/ThreadPoolExample.php: -------------------------------------------------------------------------------- 1 | numbersToSum = $numbersToSum; 9 | } 10 | public function run() { 11 | sleep(10); 12 | $this->sum = array_sum((array)$this->numbersToSum); 13 | } 14 | public function getSum(): int { 15 | return $this->sum; 16 | } 17 | public function getNumbersToSum(): array { 18 | return (array)$this->numbersToSum; 19 | } 20 | }; 21 | 22 | $pool = new Pool(4, Worker::class); 23 | 24 | $data = [ 25 | [1, 2, 3], 26 | [4, 5, 6], 27 | [7, 8, 9], 28 | [10, 11, 12], 29 | [13, 14, 15], 30 | ]; 31 | 32 | /** @var SummariserThread[] $tasks */ 33 | $tasks = []; 34 | foreach ($data as $dataChunk) { 35 | $task = new SummariserThread(...$dataChunk); 36 | $pool->submit($task); 37 | 38 | $tasks[] = $task; 39 | } 40 | 41 | while ($pool->collect() > 0) { 42 | echo 'Waiting...' . PHP_EOL; 43 | } 44 | 45 | 46 | // shutdown will wait for current queue to be completed 47 | $pool->shutdown(); 48 | 49 | // get result 50 | foreach ($tasks as $task) { 51 | echo sprintf( 52 | "Get result: %s, %s\n", 53 | json_encode($task->getNumbersToSum()), 54 | $task->getSum() 55 | ); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/ReactPHP/.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /src/ReactPHP/ChatServerExample.php: -------------------------------------------------------------------------------- 1 | addPeriodicTimer(5, function () { 12 | $memory = memory_get_usage() / 1024; 13 | $formatted = number_format($memory, 3) . 'K'; 14 | echo "Current memory usage: {$formatted}\n"; 15 | }); 16 | 17 | // start server 18 | $serverSocket = stream_socket_server('tcp://' . SERVER_HOST . ':' . SERVER_PORT); 19 | stream_set_blocking( 20 | $serverSocket, 21 | false // NON-BLOCKING 22 | ); 23 | 24 | // accept request 25 | $clientConnectionList = []; 26 | 27 | $loop->addReadStream( 28 | $serverSocket, 29 | function ($serverSocket) use ( 30 | $loop, 31 | &$clientConnectionList 32 | ) { 33 | // establish connection 34 | $clientConnection = stream_socket_accept($serverSocket); 35 | $clientConnectionList[] = $clientConnection; 36 | 37 | echo 'Connection established' . PHP_EOL; 38 | fwrite($clientConnection, "Connection accepted\n"); 39 | 40 | // wait incoming message 41 | $loop->addReadStream( 42 | $clientConnection, 43 | function($clientConnection) use ( 44 | $loop, 45 | &$clientConnectionList 46 | ) { 47 | // get request 48 | $requestTime = date('d.m.Y H:i:s', time()); 49 | $requestString = stream_socket_recvfrom($clientConnection, 1024); 50 | echo "Request string: " . $requestString; 51 | 52 | // send to all 53 | foreach ($clientConnectionList as $clientConnectionListItemId => $clientConnectionListItem) { 54 | // send to socket 55 | $loop->addWriteStream( 56 | $clientConnectionListItem, 57 | function($clientConnectionListItem) use( 58 | $loop, 59 | $requestTime, 60 | $requestString, 61 | &$clientConnectionList, 62 | $clientConnectionListItemId 63 | ) { 64 | // send message to socket 65 | $bytesSent = @stream_socket_sendto( 66 | $clientConnectionListItem, 67 | sprintf('[%s] %s', $requestTime, $requestString) 68 | ); 69 | 70 | if ($bytesSent <= 0) { 71 | unset($clientConnectionList[$clientConnectionListItemId]); 72 | } 73 | 74 | // stop handling socket in loop 75 | $loop->removeWriteStream($clientConnectionListItem); 76 | } 77 | ); 78 | 79 | } 80 | } 81 | ); 82 | } 83 | ); 84 | 85 | // start loop 86 | echo 'Server started at ' . SERVER_HOST . ':' . SERVER_PORT . PHP_EOL; 87 | $loop->run(); 88 | 89 | -------------------------------------------------------------------------------- /src/ReactPHP/TimerExample.php: -------------------------------------------------------------------------------- 1 | futureTick(function() { 10 | echo 'TICK' . PHP_EOL; 11 | }); 12 | 13 | $loop->addPeriodicTimer(1, function () { 14 | for ($i = 0; $i < 4; $i++) { 15 | echo 'Timer 1' . PHP_EOL; 16 | sleep(1); # blocks loop 17 | } 18 | }); 19 | 20 | $loop->addPeriodicTimer(1, function () { 21 | for ($i = 0; $i < 4; $i++) { 22 | echo 'Timer 2' . PHP_EOL; 23 | sleep(1); # blocks loop 24 | } 25 | }); 26 | 27 | $loop->addSignal(SIGINT, function() { 28 | die('SIGNAL HANDLED' . PHP_EOL); 29 | }); 30 | 31 | $loop->run(); -------------------------------------------------------------------------------- /src/ReactPHP/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sokil/react-chat", 3 | "type": "project", 4 | "require": { 5 | "react/event-loop": "^0.5.2", 6 | "react/http": "^0.7.4", 7 | "react/stream": "^0.7.3" 8 | }, 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Dmytro Sokil", 13 | "email": "dmytro.sokil@gmail.com" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/ReactPHP/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "abdf13b38d0f6383b77743cda67eb663", 8 | "packages": [ 9 | { 10 | "name": "evenement/evenement", 11 | "version": "v3.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/igorw/evenement.git", 15 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 20 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^6.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-0": { 32 | "Evenement": "src" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Igor Wiedler", 42 | "email": "igor@wiedler.ch" 43 | } 44 | ], 45 | "description": "Événement is a very simple event dispatching library for PHP", 46 | "keywords": [ 47 | "event-dispatcher", 48 | "event-emitter" 49 | ], 50 | "time": "2017-07-23T21:35:13+00:00" 51 | }, 52 | { 53 | "name": "psr/http-message", 54 | "version": "1.0.1", 55 | "source": { 56 | "type": "git", 57 | "url": "https://github.com/php-fig/http-message.git", 58 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 59 | }, 60 | "dist": { 61 | "type": "zip", 62 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 63 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 64 | "shasum": "" 65 | }, 66 | "require": { 67 | "php": ">=5.3.0" 68 | }, 69 | "type": "library", 70 | "extra": { 71 | "branch-alias": { 72 | "dev-master": "1.0.x-dev" 73 | } 74 | }, 75 | "autoload": { 76 | "psr-4": { 77 | "Psr\\Http\\Message\\": "src/" 78 | } 79 | }, 80 | "notification-url": "https://packagist.org/downloads/", 81 | "license": [ 82 | "MIT" 83 | ], 84 | "authors": [ 85 | { 86 | "name": "PHP-FIG", 87 | "homepage": "http://www.php-fig.org/" 88 | } 89 | ], 90 | "description": "Common interface for HTTP messages", 91 | "homepage": "https://github.com/php-fig/http-message", 92 | "keywords": [ 93 | "http", 94 | "http-message", 95 | "psr", 96 | "psr-7", 97 | "request", 98 | "response" 99 | ], 100 | "time": "2016-08-06T14:39:51+00:00" 101 | }, 102 | { 103 | "name": "react/cache", 104 | "version": "v0.4.2", 105 | "source": { 106 | "type": "git", 107 | "url": "https://github.com/reactphp/cache.git", 108 | "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8" 109 | }, 110 | "dist": { 111 | "type": "zip", 112 | "url": "https://api.github.com/repos/reactphp/cache/zipball/75494f26b4ef089db9bf8c90b63c296246e099e8", 113 | "reference": "75494f26b4ef089db9bf8c90b63c296246e099e8", 114 | "shasum": "" 115 | }, 116 | "require": { 117 | "php": ">=5.3.0", 118 | "react/promise": "~2.0|~1.1" 119 | }, 120 | "require-dev": { 121 | "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" 122 | }, 123 | "type": "library", 124 | "autoload": { 125 | "psr-4": { 126 | "React\\Cache\\": "src/" 127 | } 128 | }, 129 | "notification-url": "https://packagist.org/downloads/", 130 | "license": [ 131 | "MIT" 132 | ], 133 | "description": "Async, Promise-based cache interface for ReactPHP", 134 | "keywords": [ 135 | "cache", 136 | "caching", 137 | "promise", 138 | "reactphp" 139 | ], 140 | "time": "2017-12-20T16:47:13+00:00" 141 | }, 142 | { 143 | "name": "react/dns", 144 | "version": "v0.4.13", 145 | "source": { 146 | "type": "git", 147 | "url": "https://github.com/reactphp/dns.git", 148 | "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4" 149 | }, 150 | "dist": { 151 | "type": "zip", 152 | "url": "https://api.github.com/repos/reactphp/dns/zipball/7d1e08c300fd7de600810883386ee5e2a64898f4", 153 | "reference": "7d1e08c300fd7de600810883386ee5e2a64898f4", 154 | "shasum": "" 155 | }, 156 | "require": { 157 | "php": ">=5.3.0", 158 | "react/cache": "~0.4.0|~0.3.0", 159 | "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", 160 | "react/promise": "^2.1 || ^1.2.1", 161 | "react/promise-timer": "^1.2", 162 | "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5" 163 | }, 164 | "require-dev": { 165 | "clue/block-react": "^1.2", 166 | "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" 167 | }, 168 | "type": "library", 169 | "autoload": { 170 | "psr-4": { 171 | "React\\Dns\\": "src" 172 | } 173 | }, 174 | "notification-url": "https://packagist.org/downloads/", 175 | "license": [ 176 | "MIT" 177 | ], 178 | "description": "Async DNS resolver for ReactPHP", 179 | "keywords": [ 180 | "async", 181 | "dns", 182 | "dns-resolver", 183 | "reactphp" 184 | ], 185 | "time": "2018-02-27T12:51:22+00:00" 186 | }, 187 | { 188 | "name": "react/event-loop", 189 | "version": "v0.5.2", 190 | "source": { 191 | "type": "git", 192 | "url": "https://github.com/reactphp/event-loop.git", 193 | "reference": "e94985d93c689c554265b01014f8c3064921ca27" 194 | }, 195 | "dist": { 196 | "type": "zip", 197 | "url": "https://api.github.com/repos/reactphp/event-loop/zipball/e94985d93c689c554265b01014f8c3064921ca27", 198 | "reference": "e94985d93c689c554265b01014f8c3064921ca27", 199 | "shasum": "" 200 | }, 201 | "require": { 202 | "php": ">=5.3.0" 203 | }, 204 | "require-dev": { 205 | "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" 206 | }, 207 | "suggest": { 208 | "ext-event": "~1.0 for ExtEventLoop", 209 | "ext-pcntl": "For signal handling support when using the StreamSelectLoop" 210 | }, 211 | "type": "library", 212 | "autoload": { 213 | "psr-4": { 214 | "React\\EventLoop\\": "src" 215 | } 216 | }, 217 | "notification-url": "https://packagist.org/downloads/", 218 | "license": [ 219 | "MIT" 220 | ], 221 | "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 222 | "keywords": [ 223 | "asynchronous", 224 | "event-loop" 225 | ], 226 | "time": "2018-04-24T11:23:06+00:00" 227 | }, 228 | { 229 | "name": "react/http", 230 | "version": "v0.7.4", 231 | "source": { 232 | "type": "git", 233 | "url": "https://github.com/reactphp/http.git", 234 | "reference": "6646135c01097b5316d2cb47bc12e541bf26efae" 235 | }, 236 | "dist": { 237 | "type": "zip", 238 | "url": "https://api.github.com/repos/reactphp/http/zipball/6646135c01097b5316d2cb47bc12e541bf26efae", 239 | "reference": "6646135c01097b5316d2cb47bc12e541bf26efae", 240 | "shasum": "" 241 | }, 242 | "require": { 243 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 244 | "php": ">=5.3.0", 245 | "react/promise": "^2.3 || ^1.2.1", 246 | "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", 247 | "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6", 248 | "ringcentral/psr7": "^1.2" 249 | }, 250 | "require-dev": { 251 | "clue/block-react": "^1.1", 252 | "phpunit/phpunit": "^4.8.10||^5.0", 253 | "react/promise-stream": "^0.1.1", 254 | "react/socket": "^1.0 || ^0.8 || ^0.7" 255 | }, 256 | "type": "library", 257 | "autoload": { 258 | "psr-4": { 259 | "React\\Http\\": "src" 260 | } 261 | }, 262 | "notification-url": "https://packagist.org/downloads/", 263 | "license": [ 264 | "MIT" 265 | ], 266 | "description": "Event-driven, streaming plaintext HTTP and secure HTTPS server for ReactPHP", 267 | "keywords": [ 268 | "event-driven", 269 | "http", 270 | "https", 271 | "reactphp", 272 | "server", 273 | "streaming" 274 | ], 275 | "time": "2017-08-16T15:24:39+00:00" 276 | }, 277 | { 278 | "name": "react/promise", 279 | "version": "v2.5.1", 280 | "source": { 281 | "type": "git", 282 | "url": "https://github.com/reactphp/promise.git", 283 | "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" 284 | }, 285 | "dist": { 286 | "type": "zip", 287 | "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", 288 | "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", 289 | "shasum": "" 290 | }, 291 | "require": { 292 | "php": ">=5.4.0" 293 | }, 294 | "require-dev": { 295 | "phpunit/phpunit": "~4.8" 296 | }, 297 | "type": "library", 298 | "autoload": { 299 | "psr-4": { 300 | "React\\Promise\\": "src/" 301 | }, 302 | "files": [ 303 | "src/functions_include.php" 304 | ] 305 | }, 306 | "notification-url": "https://packagist.org/downloads/", 307 | "license": [ 308 | "MIT" 309 | ], 310 | "authors": [ 311 | { 312 | "name": "Jan Sorgalla", 313 | "email": "jsorgalla@gmail.com" 314 | } 315 | ], 316 | "description": "A lightweight implementation of CommonJS Promises/A for PHP", 317 | "keywords": [ 318 | "promise", 319 | "promises" 320 | ], 321 | "time": "2017-03-25T12:08:31+00:00" 322 | }, 323 | { 324 | "name": "react/promise-timer", 325 | "version": "v1.3.0", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/reactphp/promise-timer.git", 329 | "reference": "604513e33feb96ffb0dfb3d25233d3f9f0dce356" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/604513e33feb96ffb0dfb3d25233d3f9f0dce356", 334 | "reference": "604513e33feb96ffb0dfb3d25233d3f9f0dce356", 335 | "shasum": "" 336 | }, 337 | "require": { 338 | "php": ">=5.3", 339 | "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", 340 | "react/promise": "~2.1|~1.2" 341 | }, 342 | "require-dev": { 343 | "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" 344 | }, 345 | "type": "library", 346 | "autoload": { 347 | "psr-4": { 348 | "React\\Promise\\Timer\\": "src/" 349 | }, 350 | "files": [ 351 | "src/functions.php" 352 | ] 353 | }, 354 | "notification-url": "https://packagist.org/downloads/", 355 | "license": [ 356 | "MIT" 357 | ], 358 | "authors": [ 359 | { 360 | "name": "Christian Lück", 361 | "email": "christian@lueck.tv" 362 | } 363 | ], 364 | "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", 365 | "homepage": "https://github.com/react/promise-timer", 366 | "keywords": [ 367 | "async", 368 | "event-loop", 369 | "promise", 370 | "reactphp", 371 | "timeout", 372 | "timer" 373 | ], 374 | "time": "2018-04-24T14:19:26+00:00" 375 | }, 376 | { 377 | "name": "react/socket", 378 | "version": "v0.8.11", 379 | "source": { 380 | "type": "git", 381 | "url": "https://github.com/reactphp/socket.git", 382 | "reference": "94e98bb4def7054454784b330e44f73b7f6c160f" 383 | }, 384 | "dist": { 385 | "type": "zip", 386 | "url": "https://api.github.com/repos/reactphp/socket/zipball/94e98bb4def7054454784b330e44f73b7f6c160f", 387 | "reference": "94e98bb4def7054454784b330e44f73b7f6c160f", 388 | "shasum": "" 389 | }, 390 | "require": { 391 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 392 | "php": ">=5.3.0", 393 | "react/dns": "^0.4.13", 394 | "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", 395 | "react/promise": "^2.1 || ^1.2", 396 | "react/promise-timer": "~1.0", 397 | "react/stream": "^1.0 || ^0.7.1" 398 | }, 399 | "require-dev": { 400 | "clue/block-react": "^1.2", 401 | "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" 402 | }, 403 | "type": "library", 404 | "autoload": { 405 | "psr-4": { 406 | "React\\Socket\\": "src" 407 | } 408 | }, 409 | "notification-url": "https://packagist.org/downloads/", 410 | "license": [ 411 | "MIT" 412 | ], 413 | "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", 414 | "keywords": [ 415 | "Connection", 416 | "Socket", 417 | "async", 418 | "reactphp", 419 | "stream" 420 | ], 421 | "time": "2018-04-24T14:07:53+00:00" 422 | }, 423 | { 424 | "name": "react/stream", 425 | "version": "v0.7.7", 426 | "source": { 427 | "type": "git", 428 | "url": "https://github.com/reactphp/stream.git", 429 | "reference": "10100896018fd847a257cd81143b8e1b7be08e40" 430 | }, 431 | "dist": { 432 | "type": "zip", 433 | "url": "https://api.github.com/repos/reactphp/stream/zipball/10100896018fd847a257cd81143b8e1b7be08e40", 434 | "reference": "10100896018fd847a257cd81143b8e1b7be08e40", 435 | "shasum": "" 436 | }, 437 | "require": { 438 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 439 | "php": ">=5.3.8", 440 | "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" 441 | }, 442 | "require-dev": { 443 | "clue/stream-filter": "~1.2", 444 | "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" 445 | }, 446 | "type": "library", 447 | "autoload": { 448 | "psr-4": { 449 | "React\\Stream\\": "src" 450 | } 451 | }, 452 | "notification-url": "https://packagist.org/downloads/", 453 | "license": [ 454 | "MIT" 455 | ], 456 | "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", 457 | "keywords": [ 458 | "event-driven", 459 | "io", 460 | "non-blocking", 461 | "pipe", 462 | "reactphp", 463 | "readable", 464 | "stream", 465 | "writable" 466 | ], 467 | "time": "2018-01-19T15:04:38+00:00" 468 | }, 469 | { 470 | "name": "ringcentral/psr7", 471 | "version": "1.2.2", 472 | "source": { 473 | "type": "git", 474 | "url": "https://github.com/ringcentral/psr7.git", 475 | "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c" 476 | }, 477 | "dist": { 478 | "type": "zip", 479 | "url": "https://api.github.com/repos/ringcentral/psr7/zipball/dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c", 480 | "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c", 481 | "shasum": "" 482 | }, 483 | "require": { 484 | "php": ">=5.3", 485 | "psr/http-message": "~1.0" 486 | }, 487 | "provide": { 488 | "psr/http-message-implementation": "1.0" 489 | }, 490 | "require-dev": { 491 | "phpunit/phpunit": "~4.0" 492 | }, 493 | "type": "library", 494 | "extra": { 495 | "branch-alias": { 496 | "dev-master": "1.0-dev" 497 | } 498 | }, 499 | "autoload": { 500 | "psr-4": { 501 | "RingCentral\\Psr7\\": "src/" 502 | }, 503 | "files": [ 504 | "src/functions_include.php" 505 | ] 506 | }, 507 | "notification-url": "https://packagist.org/downloads/", 508 | "license": [ 509 | "MIT" 510 | ], 511 | "authors": [ 512 | { 513 | "name": "Michael Dowling", 514 | "email": "mtdowling@gmail.com", 515 | "homepage": "https://github.com/mtdowling" 516 | } 517 | ], 518 | "description": "PSR-7 message implementation", 519 | "keywords": [ 520 | "http", 521 | "message", 522 | "stream", 523 | "uri" 524 | ], 525 | "time": "2018-01-15T21:00:49+00:00" 526 | } 527 | ], 528 | "packages-dev": [], 529 | "aliases": [], 530 | "minimum-stability": "stable", 531 | "stability-flags": [], 532 | "prefer-stable": false, 533 | "prefer-lowest": false, 534 | "platform": [], 535 | "platform-dev": [] 536 | } 537 | -------------------------------------------------------------------------------- /src/Stream/NonBlockingFileRead.php: -------------------------------------------------------------------------------- 1 | context = new Context($this); 35 | } 36 | 37 | /** 38 | * @param callable $tick 39 | */ 40 | public function addTick(callable $tick) 41 | { 42 | $this->ticks[] = Closure::bind($tick, $this->context); 43 | } 44 | 45 | /** 46 | * @param $readStream 47 | * @param callable $callback 48 | */ 49 | public function addReadStream($readStream, callable $callback) 50 | { 51 | $readStreamId = (int)$readStream; 52 | 53 | $this->readStreams[$readStreamId] = $readStream; 54 | $this->readCallbacks[$readStreamId] = $callback; 55 | } 56 | 57 | /** 58 | * @param $readStream 59 | */ 60 | public function removeReadStream($readStream) 61 | { 62 | $readStreamId = (int)$readStream; 63 | 64 | unset($this->readStreams[$readStreamId]); 65 | unset($this->readCallbacks[$readStreamId]); 66 | } 67 | 68 | /** 69 | * Loop iterator 70 | */ 71 | public function run() 72 | { 73 | while (true) { 74 | // handle ticks 75 | foreach ($this->ticks as $tick) { 76 | call_user_func($tick); 77 | } 78 | $this->ticks = []; 79 | 80 | // handle streams 81 | $readStreams = $this->readStreams; 82 | $writeStreams = null; 83 | $exceptStreams = null; 84 | 85 | $modifiedStreamsCount = stream_select( 86 | $readStreams, 87 | $writeStreams, 88 | $exceptStreams, 89 | 0 90 | ); 91 | 92 | // add new client connection to pool if exists 93 | if ($modifiedStreamsCount === false) { 94 | echo "File read interrupted\n"; 95 | } else if ($modifiedStreamsCount === 0) { 96 | echo "File not ready for reading\n"; 97 | } else { 98 | echo sprintf("Selected %d files, ready to read\n", $modifiedStreamsCount); 99 | 100 | foreach ($readStreams as $readStream) { 101 | $this->readCallbacks[(int)$readStream]($readStream); 102 | } 103 | } 104 | 105 | if (empty($this->ticks) && empty($this->readStreams)) { 106 | echo "No ticks and streams to handle. Exit event loop\n"; 107 | break; 108 | } 109 | 110 | sleep(1); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Context used to incapsulate loop from tick callbacks (user code) 117 | */ 118 | class Context 119 | { 120 | /** 121 | * @var Loop 122 | */ 123 | private $loop; 124 | 125 | /** 126 | * Context constructor. 127 | * @param Loop $loop 128 | */ 129 | public function __construct(Loop $loop) 130 | { 131 | $this->loop = $loop; 132 | } 133 | 134 | /** 135 | * @param string $className 136 | * @return mixed 137 | */ 138 | public function import(string $className) 139 | { 140 | return new $className($this->loop); 141 | } 142 | } 143 | 144 | /** 145 | * Non blocking file reader 146 | */ 147 | class FileReader 148 | { 149 | /** 150 | * @var Loop 151 | */ 152 | private $loop; 153 | 154 | /** 155 | * @var int 156 | */ 157 | private $bufferSize = 4000; 158 | 159 | /** 160 | * @param Loop $loop 161 | */ 162 | public function __construct(Loop $loop) 163 | { 164 | $this->loop = $loop; 165 | } 166 | 167 | /** 168 | * @param string $filename 169 | * @param callable $callback 170 | * 171 | * @throws \Exception 172 | */ 173 | public function read(string $filename, callable $callback) 174 | { 175 | $file = fopen($filename, 'r'); 176 | 177 | // file read is non blocking 178 | if (stream_set_blocking($file, 0) === false) { 179 | throw new \Exception('Error setting non blocking mode for stream'); 180 | } 181 | 182 | // make read operations are unbuffered 183 | if (stream_set_read_buffer($file, 0) !== 0) { 184 | throw new \Exception('Error disabling read buffer'); 185 | } 186 | 187 | $data = null; 188 | 189 | $this->loop->addReadStream( 190 | $file, 191 | function(&$file) use(&$data, $callback) { 192 | $dataChunk = stream_get_contents($file, $this->bufferSize); 193 | echo md5($dataChunk) . PHP_EOL; 194 | 195 | if ($dataChunk === '') { 196 | $this->loop->removeReadStream($file); 197 | $callback($data); 198 | } else { 199 | $data .= $dataChunk; 200 | } 201 | } 202 | ); 203 | } 204 | } 205 | 206 | $loop = new Loop(); 207 | 208 | /** 209 | * Entrypoint 210 | */ 211 | $loop->addTick(function() { 212 | echo 'Main loop start' . PHP_EOL; 213 | 214 | /** @var FileReader $fileReader */ 215 | $fileReader = $this->import(FileReader::class); 216 | 217 | $fileReader->read(__FILE__, function (string $data) { 218 | echo sprintf("1) File size is: %d bytes\n", strlen($data)); 219 | }); 220 | 221 | $fileReader->read(__FILE__, function (string $data) { 222 | echo sprintf("2) File size is: %d bytes\n", strlen($data)); 223 | }); 224 | 225 | echo 'Main loop end' . PHP_EOL; 226 | }); 227 | 228 | $loop->run(); 229 | 230 | /* 231 | This will output: 232 | 233 | Main loop start 234 | Main loop end 235 | Selected 2 files, ready to read 236 | 3db1fa34b5917b9820f5f275968effee 237 | 3db1fa34b5917b9820f5f275968effee 238 | Selected 2 files, ready to read 239 | 479766c5eca1cc87f5ea50d5e63a47ed 240 | 479766c5eca1cc87f5ea50d5e63a47ed 241 | Selected 2 files, ready to read 242 | d41d8cd98f00b204e9800998ecf8427e 243 | 1) File size is: 4860 bytes 244 | d41d8cd98f00b204e9800998ecf8427e 245 | 2) File size is: 4860 bytes 246 | No ticks and streams to handle. Exit event loop 247 | 248 | */ -------------------------------------------------------------------------------- /src/Stream/Select.php: -------------------------------------------------------------------------------- 1 | 0) { 48 | 49 | echo sprintf("Active client connections: %d\n", count($clientSockets)); 50 | 51 | // send date to client 52 | foreach ($clientSockets as $clientSocket) { 53 | fwrite($clientSocket, date('Y-m-d H:i:s.u') . PHP_EOL); 54 | } 55 | 56 | // wait for data from client 57 | $clientSocketReadStreams = $clientSockets; 58 | $clientSocketWriteStreams = []; 59 | $clientSocketExceptStreams = []; 60 | 61 | $modifiedClientSocketStreamNumber = stream_select( 62 | $clientSocketReadStreams, 63 | $clientSocketWriteStreams, 64 | $clientSocketExceptStreams, 65 | 0 66 | ); 67 | 68 | // show data from client if exists 69 | if ($modifiedClientSocketStreamNumber === false) { 70 | echo "Client connection interrupted\n"; 71 | } else if ($modifiedClientSocketStreamNumber === 0) { 72 | echo "No client streams modified\n"; 73 | } else { 74 | echo sprintf("\033[0;33mSelected %d client streams\033[0m\n", $modifiedClientSocketStreamNumber); 75 | 76 | if (!empty ($clientSocketReadStreams)) { 77 | foreach ($clientSocketReadStreams as $clientSocketReadStream) { 78 | echo sprintf( 79 | "Read from client: \033[0;32m%s\033[0m", 80 | fread($clientSocketReadStream, 1024) 81 | ); 82 | } 83 | } 84 | } 85 | } 86 | 87 | sleep(1); 88 | } 89 | -------------------------------------------------------------------------------- /src/coroutine/echo.php: -------------------------------------------------------------------------------- 1 | send('hello'); 19 | echo "After send hello" . PHP_EOL; 20 | 21 | echo "Before send world" . PHP_EOL; 22 | $coroutine->send('world'); 23 | echo "After send world"; 24 | 25 | /** 26 | Before coroutine create 27 | After coroutine create 28 | Before send hello 29 | Before first yield 30 | hello 31 | After first yield 32 | Before second yield 33 | After send hello 34 | Before send world 35 | world 36 | After second yield 37 | After send world 38 | */ 39 | -------------------------------------------------------------------------------- /src/fork/fork.php: -------------------------------------------------------------------------------- 1 |