├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── config.m4 ├── mrloop_arginfo.h ├── php_mrloop.c ├── php_mrloop.h ├── src ├── loop.c └── loop.h └── tests ├── 001.phpt ├── 002.phpt ├── 003.phpt ├── 008.phpt ├── 009.phpt ├── 010.phpt ├── 011.phpt ├── 012.phpt └── 013.phpt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ext-mrloop CI 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | jobs: 7 | build: 8 | runs-on: 'ubuntu-latest' 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | php: ['8.1', '8.2', '8.3', '8.4'] 13 | name: PHP ${{ matrix.php }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Install PHP 17 | uses: shivammathur/setup-php@master 18 | with: 19 | php-version: ${{ matrix.php }} 20 | extensions: posix, pcntl 21 | env: 22 | phpts: ts 23 | - name: Run tests 24 | run: | 25 | git clone https://github.com/markreedz/mrloop.git mrloop && \ 26 | git clone https://github.com/axboe/liburing.git liburing && cd liburing && make && sudo make install && \ 27 | cd ../ && phpize && ./configure --with-mrloop="$(pwd)/mrloop" && \ 28 | make && make test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.lo 2 | *.loT 3 | *.la 4 | *.dep 5 | *.in~ 6 | *.libs 7 | *acinclude.m4 8 | *aclocal.m4 9 | *autom4te.cache 10 | *build 11 | *config.guess 12 | *config.h 13 | *config.h.in 14 | *config.log 15 | *config.nice 16 | *config.status 17 | *config.sub 18 | *configure 19 | *configure.ac 20 | *configure.in 21 | *include 22 | *install-sh 23 | *libtool 24 | *ltmain.sh 25 | *Makefile 26 | *Makefile.fragments 27 | *Makefile.global 28 | *Makefile.objects 29 | *missing 30 | *mkinstalldirs 31 | *modules 32 | *php_test_results_*.txt 33 | *phpt.* 34 | *run-test-info.php 35 | *run-tests.php 36 | *tests/**/*.diff 37 | *tests/**/*.out 38 | *tests/**/*.exp 39 | *tests/**/*.log 40 | *tests/**/*.sh 41 | *tests/**/*.db 42 | *tests/**/*.mem 43 | *tmp-php.ini 44 | *TODO 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lochemem Bruno Michael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ext-mrloop 2 | 3 | A PHP port of an event loop designed to harness the powers of `io_uring`. 4 | 5 | ## Rationale 6 | 7 | PHP has, in recent years, seen an emergence of eventware built atop potent multiplexing functions like `epoll()`, `poll()`, and `select()`. In event loops like `libev`, `libuv`, and `libevent` are powerful abstractions of the aforelisted functions for interleaving I/O inside a single process. Eponymous PHP extensions have, in the years since the popularization of the non-blocking I/O paradigm, been created to avail users of the language of the many potencies of the aforestated libraries. `io_uring` is yet another async system call API, and mrloop is an event loop built atop its interface. The former, which the latter is written atop—presents an enhancive proposition: unlike the commonplace non-blocking I/O APIs, it is a unified evented I/O interface with an emphasis on efficient userspace-kernel communications. `io_uring` is, internally, a set of [ring buffers](https://en.wikipedia.org/wiki/Circular_buffer)—structures the operations on which are of an **O(1)** complexity—whose communication mechanism is such that a userspace-resident application places a file descriptor on a submission queue and subsequently retrieves matching output from a kernel-controlled completion queue. It confers performance benefits that stem from a small system call footprint and is thus useful to any PHP developer keen on evented I/O. 8 | 9 | ## Requirements 10 | 11 | - PHP 8.1 or newer 12 | - Linux Kernel 5.4.1 or newer 13 | - [mrloop](https://github.com/markreedz/mrloop) 14 | - [liburing](https://github.com/axboe/liburing) 15 | 16 | ## Installation 17 | 18 | It is important to have all the aforelisted requirements at the ready before attempting to install `ext-mrloop`. The directives in the snippet to follow should allow you to build the extension's shared object file (`mrloop.so`). 19 | 20 | ```sh 21 | $ git clone https://github.com/ace411/mrloop.git 22 | $ git clone https://github.com/ringphp/php-mrloop.git 23 | $ cd 24 | $ phpize && ./configure --with-mrloop= 25 | $ make && sudo make install 26 | ``` 27 | 28 | After successfully building the shared object, proceed to operationalize the extension by adding the line `extension=mrloop` to your `php.ini` file. If you prefer to perform the said operationalization via command line interface, the following should suffice. 29 | 30 | ```sh 31 | $ printf "\nextension=mrloop\n" >> "$(php-config --ini-path)/php.ini" 32 | ``` 33 | 34 | ## API Synopsis 35 | 36 | ```php 37 | 38 | namespace ringphp; 39 | 40 | class Mrloop 41 | { 42 | 43 | /* public methods */ 44 | public static init(): Mrloop 45 | public addReadStream( 46 | resource $stream, 47 | ?int $nbytes, 48 | ?int $vcount, 49 | ?int $offset, 50 | callable $callback, 51 | ): void 52 | public addWriteStream( 53 | resource $stream, 54 | string $contents, 55 | ?int $vcount, 56 | callable $callback, 57 | ): void 58 | public tcpServer( 59 | int $port, 60 | ?int $connections, 61 | ?int $nbytes, 62 | callable $callback, 63 | ): void 64 | public writev(int|resource $fd, string $message): void 65 | public addTimer(float $interval, callable $callback): void 66 | public addPeriodicTimer(float $interval, callable $callback): void 67 | public futureTick(callable $callback): void 68 | public addSignal(int $signal, callable $callback): void 69 | public run(): void 70 | public stop(): void 71 | } 72 | ``` 73 | 74 | - [`Mrloop::init`](#mrloopinit) 75 | - [`Mrloop::addReadStream`](#mrloopaddreadstream) 76 | - [`Mrloop::addWriteStream`](#mrloopaddwritestream) 77 | - [`Mrloop::tcpServer`](#mrlooptcpserver) 78 | - [`Mrloop::writev`](#mrloopwritev) 79 | - [`Mrloop::addTimer`](#mrloopaddtimer) 80 | - [`Mrloop::addPeriodicTimer`](#mrloopaddperiodictimer) 81 | - [`Mrloop::futureTick`](#mrloopfuturetick) 82 | - [`Mrloop::addSignal`](#mrloopaddsignal) 83 | - [`Mrloop::run`](#mrlooprun) 84 | - [`Mrloop::stop`](#mrloopstop) 85 | 86 | ### `Mrloop::init` 87 | 88 | ```php 89 | public static Mrloop::init(): Mrloop 90 | ``` 91 | 92 | Initializes the event loop. 93 | 94 | - Initializing the loop is one step in operationalizing it. A follow-up call to the `run()` function is required to start the loop. 95 | 96 | **Parameter(s)** 97 | 98 | None. 99 | 100 | **Return value(s)** 101 | 102 | The function returns a `Mrloop` object in which the event loop is subsumed. 103 | 104 | ```php 105 | if (!\extension_loaded('mrloop')) { 106 | echo "Please install ext-mrloop to continue\n"; 107 | 108 | exit(1); 109 | } 110 | 111 | use ringphp\Mrloop; 112 | 113 | $loop = Mrloop::init(); 114 | 115 | $loop->addTimer( 116 | 1.2, 117 | function () { 118 | echo "Hello, user\n"; 119 | }, 120 | ); 121 | 122 | $loop->run(); 123 | ``` 124 | 125 | The example above will produce output similar to that in the snippet to follow. 126 | 127 | ``` 128 | Hello, user 129 | 130 | ``` 131 | 132 | ### `Mrloop::addReadStream` 133 | 134 | ```php 135 | public Mrloop::addReadStream( 136 | resource $stream, 137 | ?int $nbytes, 138 | ?int $vcount, 139 | ?int $offset, 140 | callable $callback, 141 | ): void 142 | ``` 143 | 144 | Funnels file descriptor in readable stream into event loop and thence executes a non-blocking read operation. 145 | 146 | **Parameter(s)** 147 | 148 | - **stream** (resource) - A userspace-defined readable stream. 149 | > The file descriptor in the stream is internally given a non-blocking disposition. 150 | - **nbytes** (int|null) - The number of bytes to read. 151 | > Specifying `null` will condition the use of a 1KB buffer. 152 | - **vcount** (int|null) - The number of read vectors to use. 153 | > Specifying `null` will condition the use of `1` vector. 154 | > Any value north of `8` will likely result in an inefficient read. 155 | - **offset** (int|null) - The point at which to start the read operation. 156 | > Specifying `null` will condition the use of an offset of `0`. 157 | - **callback** (callable) - The binary function through which the file's contents and read result code are propagated. 158 | 159 | **Return value(s)** 160 | 161 | The function does not return anything. 162 | 163 | ```php 164 | use ringphp\MrLoop; 165 | 166 | $loop = Mrloop::init(); 167 | 168 | $loop->addReadStream( 169 | $fd = \fopen('/path/to/file', 'r'), 170 | null, 171 | null, 172 | null, 173 | function (string $contents, int $res) use ($fd) { 174 | if ($res === 0) { 175 | echo \sprintf("%s\n", $contents); 176 | } 177 | 178 | \fclose($fd); 179 | }, 180 | ); 181 | 182 | $loop->run(); 183 | ``` 184 | 185 | The example above will produce output similar to that in the snippet to follow. 186 | 187 | ``` 188 | File contents... 189 | 190 | ``` 191 | 192 | ### `Mrloop::addWriteStream` 193 | 194 | ```php 195 | public Mrloop::addWriteStream( 196 | resource $stream, 197 | string $contents, 198 | ?int $vcount, 199 | callable $callback, 200 | ): void 201 | ``` 202 | 203 | Funnels file descriptor in writable stream into event loop and thence executes a non-blocking write operation. 204 | 205 | **Parameter(s)** 206 | 207 | - **stream** (resource) - A userspace-defined writable stream. 208 | > The file descriptor in the stream is internally given a non-blocking disposition. 209 | - **contents** (string) - The contents to write to the file descriptor. 210 | - **vcount** (int|null) - The number of write vectors to use. 211 | > Specifying `null` will condition the use of `1` vector. 212 | > Any value north of `8` will likely result in an inefficient write. 213 | - **callback** (callable) - The unary function through which the number of written bytes is propagated. 214 | 215 | **Return value(s)** 216 | 217 | The function does not return anything. 218 | 219 | ```php 220 | use ringphp\Mrloop; 221 | 222 | $loop = MrLoop::init(); 223 | 224 | $file = '/path/to/file'; 225 | 226 | $loop->addWriteStream( 227 | $fd = \fopen($file, 'w'), 228 | "file contents...\n", 229 | null, 230 | function (int $nbytes) use ($fd, $file) { 231 | echo \sprintf("Wrote %d bytes to %s\n", $nbytes, $file); 232 | 233 | \fclose($fd); 234 | }, 235 | ); 236 | 237 | $loop->run(); 238 | ``` 239 | 240 | The example above will produce output similar to that in the snippet to follow. 241 | 242 | ``` 243 | Wrote 18 bytes to /path/to/file 244 | 245 | ``` 246 | 247 | ### `Mrloop::tcpServer` 248 | 249 | ```php 250 | public Mrloop::tcpServer( 251 | int $port, 252 | ?int $connections, 253 | ?int $nbytes, 254 | callable $callback, 255 | ): void 256 | ``` 257 | 258 | Instantiates a simple TCP server. 259 | 260 | **Parameter(s)** 261 | 262 | - **port** (int) - The port on which to listen for incoming connections. 263 | - **connections** (int|null) - The maximum number of connections to accept. 264 | > This parameter does not have any effect when a version of mrloop in which the `mr_tcp_server` function lacks the `max_conn` parameter is included in the compilation process. 265 | > Specifying `null` will condition the use of a `1024` connection threshold. 266 | - **nbytes** (int|null) - The maximum number of readable bytes for each connection. 267 | > This setting is akin to the `client_max_body_size` option in NGINX. 268 | > Specifying null will condition the use of an `8192` byte threshold. 269 | - **callback** (callable) - The binary function with which to define a response to a client. 270 | > Refer to the segment to follow for more information on the callback. 271 | - **Callback parameters** 272 | - **message** (string) - The message sent via client socket to the server. 273 | - **client** (iterable) - An array containing client socket information. 274 | - **client_addr** (string) - The client IP address. 275 | - **client_port** (integer) - The client socket port. 276 | - **client_fd** (integer) - The client socket file descriptor. 277 | 278 | **Return value(s)** 279 | 280 | The function does not return anything. 281 | 282 | ```php 283 | use ringphp\Mrloop; 284 | 285 | $loop = Mrloop::init(); 286 | 287 | $loop->tcpServer( 288 | 8080, 289 | null, 290 | null, 291 | function (string $message, iterable $client) { 292 | // print access log 293 | echo \sprintf( 294 | "%s %s:%d %s\n", 295 | ( 296 | (new \DateTimeImmutable()) 297 | ->format(\DateTimeImmutable::ATOM) 298 | ), 299 | $client['client_addr'], 300 | $client['client_port'], 301 | $message, 302 | ); 303 | 304 | return \strtoupper($message); 305 | }, 306 | ); 307 | 308 | $loop->run(); 309 | ``` 310 | 311 | The example above will produce output similar to that in the snippet to follow. 312 | 313 | ``` 314 | 2022-09-24T22:26:56+00:00 127.0.0.1:66521 foo 315 | 2022-09-24T22:26:59+00:00 127.0.0.1:67533 bar 316 | 317 | ``` 318 | 319 | ### `Mrloop::writev` 320 | 321 | ```php 322 | public Mrloop::writev(int|resource $fd, string $contents): void 323 | ``` 324 | 325 | Performs vectorized non-blocking write operation on a specified file descriptor. 326 | 327 | **Parameter(s)** 328 | 329 | - **fd** (integer|resource) - The file descriptor to write to. 330 | - **contents** (string) - The arbitrary contents to write. 331 | 332 | **Return value(s)** 333 | 334 | The parser will throw an exception in the event that an invalid file descriptor is encountered and will not return anything otherwise. 335 | 336 | ```php 337 | use ringphp\Mrloop; 338 | 339 | $loop = Mrloop::init(); 340 | 341 | $loop->tcpServer( 342 | 8080, 343 | null, 344 | null, 345 | function (string $message, iterable $client) use ($loop) { 346 | [ 347 | 'client_addr' => $addr, 348 | 'client_port' => $port, 349 | 'client_fd' => $fd, 350 | ] = $client; 351 | 352 | $loop->writev( 353 | $fd, 354 | \sprintf( 355 | "Hello, %s:%d\r\n", 356 | $addr, 357 | $port, 358 | ), 359 | ); 360 | }, 361 | ); 362 | 363 | echo "Listening on port 8080\n"; 364 | 365 | $loop->run(); 366 | ``` 367 | 368 | The example above will produce output similar to that in the snippet to follow. 369 | 370 | ``` 371 | Listening on port 8080 372 | 373 | ``` 374 | 375 | ### `Mrloop::addTimer` 376 | 377 | ```php 378 | public Mrloop::addTimer(float $interval, callable $callback): void 379 | ``` 380 | 381 | Executes a specified action after a specified amount of time. 382 | 383 | **Parameter(s)** 384 | 385 | - **interval** (float) - The amount of time (in seconds) to wait before executing a specified action. 386 | - **callback** (callable) - The function in which the specified action due for execution after the aforestated interval has elapsed is defined. 387 | 388 | **Return value(s)** 389 | 390 | The function does not return anything. 391 | 392 | ```php 393 | use ringphp\Mrloop; 394 | 395 | $loop = Mrloop::init(); 396 | 397 | $loop->addTimer( 398 | 2.0, 399 | function () { 400 | echo "Hello, user\n"; 401 | }, 402 | ); 403 | 404 | $loop->run(); 405 | ``` 406 | 407 | The example above will produce output similar to that in the snippet to follow. 408 | 409 | ``` 410 | Hello, user 411 | 412 | ``` 413 | 414 | ### `Mrloop::addPeriodicTimer` 415 | 416 | ```php 417 | public Mrloop::addPeriodicTimer(float $interval, callable $callback): void 418 | ``` 419 | 420 | Executes a specified action in perpetuity with each successive execution occurring after a specified time interval. 421 | 422 | **Parameter(s)** 423 | 424 | - **interval** (float) - The interval (in seconds) between successive executions of a specified action. 425 | - **callback** (callable) - The function in which the specified action due for periodical execution is defined. 426 | > A return value of `0` will cancel the timer. 427 | 428 | **Return value(s)** 429 | 430 | The function does not return anything. 431 | 432 | ```php 433 | use ringphp\Mrloop; 434 | 435 | $loop = Mrloop::init(); 436 | $tick = 0; 437 | 438 | $loop->addPeriodicTimer( 439 | 2.0, 440 | function () use ($loop, &$tick) { 441 | echo \sprintf("Tick: %d\n", ++$tick); 442 | 443 | if ($tick === 5) { 444 | $loop->stop(); // return 0; 445 | } 446 | }, 447 | ); 448 | 449 | $loop->run(); 450 | ``` 451 | 452 | The example above will produce output similar to that in the snippet to follow. 453 | 454 | ``` 455 | Tick: 1 456 | Tick: 2 457 | Tick: 3 458 | Tick: 4 459 | Tick: 5 460 | ``` 461 | 462 | ### `Mrloop::futureTick` 463 | 464 | ```php 465 | public Mrloop::futureTick(callable $callback): void 466 | ``` 467 | 468 | Schedules the execution of a specified action for the next event loop tick. 469 | 470 | **Parameter(s)** 471 | 472 | - **callback** (callable) - The function in which the action to be scheduled is defined. 473 | 474 | **Return value(s)** 475 | 476 | The function does not return anything. 477 | 478 | ```php 479 | use ringphp\Mrloop; 480 | 481 | $loop = Mrloop::init(); 482 | $tick = 0; 483 | 484 | $loop->futureTick( 485 | function () use (&$tick) { 486 | echo \sprintf("Tick: %d\n", ++$tick); 487 | }, 488 | ); 489 | 490 | $loop->futureTick( 491 | function () use (&$tick) { 492 | echo \sprintf("Tick: %d\n", ++$tick); 493 | }, 494 | ); 495 | 496 | $loop->run(); 497 | ``` 498 | 499 | The example above will produce output similar to that in the snippet to follow. 500 | 501 | ``` 502 | Tick: 1 503 | Tick: 2 504 | ``` 505 | 506 | ### `Mrloop::addSignal` 507 | 508 | ```php 509 | public Mrloop::addSignal(int $signal, callable $callback): void 510 | ``` 511 | 512 | Performs a specified action in the event that a specified signal is detected. 513 | 514 | **Parameter(s)** 515 | 516 | - **signal** (int) - The signal to listen for. 517 | > Only the signals `SIGINT`, `SIGTERM`, and `SIGHUP` are workable. 518 | - **callback** (callable) - The function in which the specified action due for execution upon detection of a specified signal is defined. 519 | 520 | **Return value(s)** 521 | 522 | The function does not return anything. 523 | 524 | ```php 525 | use ringphp\Mrloop; 526 | 527 | $loop = Mrloop::init(); 528 | 529 | $loop->addReadStream( 530 | $fd = \fopen('/path/to/file', 'r'), 531 | null, 532 | null, 533 | null, 534 | function (...$args) use ($fd) { 535 | [$contents] = $args; 536 | 537 | echo \sprintf("%s\n", $contents); 538 | 539 | \fclose($fd); 540 | }, 541 | ); 542 | 543 | // CTRL + C to trigger 544 | $loop->addSignal( 545 | SIGINT, 546 | function () use ($loop) { 547 | echo "Loop terminated with signal SIGINT\n"; 548 | }, 549 | ); 550 | 551 | $loop->run(); 552 | ``` 553 | 554 | The example above will produce output similar to that in the snippet to follow. 555 | 556 | ``` 557 | File contents... 558 | Loop terminated with signal SIGINT 559 | ``` 560 | 561 | ### `Mrloop::run` 562 | 563 | ```php 564 | public Mrloop::run(): void 565 | ``` 566 | 567 | Runs the event loop. 568 | 569 | - All code situated between the initialization of the loop and the run directive is funneled into the mrloop io_uring interface that is abstracted into the project. 570 | - Invoking `run()` is mandatory. 571 | 572 | > Please remember to minimize the use of expensive blocking calls in your code. 573 | 574 | **Parameter(s)** 575 | 576 | None. 577 | 578 | **Return value(s)** 579 | 580 | The function does not return anything. 581 | 582 | ### `Mrloop::stop` 583 | 584 | ```php 585 | public Mrloop::stop(): void 586 | ``` 587 | 588 | Stops the event loop. 589 | 590 | **Parameter(s)** 591 | 592 | None. 593 | 594 | **Return value(s)** 595 | 596 | The function does not return anything. 597 | 598 | ```php 599 | use ringphp\Mrloop; 600 | 601 | $loop = Mrloop::init(); 602 | 603 | $loop->addReadStream( 604 | $fd = \fopen('/path/to/file', 'r'), 605 | null, 606 | null, 607 | null, 608 | function ($contents, $res) use ($fd, $loop) { 609 | echo \sprintf("%s\n", $contents); 610 | 611 | \fclose($fd); 612 | 613 | $loop->stop(); 614 | }, 615 | ); 616 | 617 | $loop->run(); 618 | ``` 619 | 620 | The example above will produce output similar to that in the snippet to follow. 621 | 622 | ``` 623 | File contents... 624 | ``` 625 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl mrloop extension for PHP (c) 2024 Lochemem Bruno Michael 2 | 3 | dnl PHP_ARG_ENABLE([mrloop], 4 | dnl [for mrloop support], 5 | dnl [AS_HELP_STRING([--enable-mrloop], 6 | dnl [include mrloop support])]) 7 | 8 | PHP_ARG_WITH([mrloop], 9 | [for mrloop library], 10 | [AS_HELP_STRING([--with-mrloop], 11 | [specify path to mrloop library])], 12 | [no]) 13 | 14 | if test "$PHP_MRLOOP" != "no"; then 15 | dnl add PHP version check 16 | PHP_VERSION=$($PHP_CONFIG --vernum) 17 | AC_MSG_CHECKING([PHP version]) 18 | if test $PHP_VERSION -lt 80100; then 19 | AC_MSG_ERROR([ext-mrloop requires PHP 8.1+]) 20 | else 21 | AC_MSG_RESULT([ok]) 22 | fi 23 | 24 | HEADER_INSTALL_DIRS="/usr/local/lib /usr/lib" 25 | URING_OBJ="liburing.so" 26 | 27 | AC_MSG_CHECKING([for liburing object file]) 28 | for iter in $HEADER_INSTALL_DIRS; do 29 | if test -s "$iter/$URING_OBJ"; then 30 | URING_SO="$iter/$URING_OBJ" 31 | AC_MSG_RESULT(found $URING_SO) 32 | fi 33 | done 34 | 35 | if test -z "$URING_SO"; then 36 | AC_MSG_RESULT(liburing is not properly installed) 37 | AC_MSG_ERROR(Please install liburing) 38 | fi 39 | 40 | AC_MSG_CHECKING([for mrloop package]) 41 | if test -s "$PHP_MRLOOP/mrloop.c"; then 42 | AC_MSG_RESULT(found mrloop package) 43 | else 44 | AC_MSG_RESULT(mrloop is not downloaded) 45 | AC_MSG_ERROR(Please download mrloop) 46 | fi 47 | 48 | CFLAGS="-g -O3 -luring -I$PHP_MRLOOP/" 49 | AC_DEFINE(HAVE_MRLOOP, 1, [ Have mrloop support ]) 50 | 51 | PHP_NEW_EXTENSION(mrloop, php_mrloop.c, $ext_shared) 52 | fi 53 | -------------------------------------------------------------------------------- /mrloop_arginfo.h: -------------------------------------------------------------------------------- 1 | /* mrloop extension for PHP (c) 2024 Lochemem Bruno Michael */ 2 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_init, 0, 0, 0) 3 | ZEND_END_ARG_INFO() 4 | 5 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_run, 0, 0, 0) 6 | ZEND_END_ARG_INFO() 7 | 8 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_stop, 0, 0, 0) 9 | ZEND_END_ARG_INFO() 10 | 11 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addTimer, 0, 0, 2) 12 | ZEND_ARG_TYPE_INFO(0, interval, IS_DOUBLE, 0) 13 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 14 | ZEND_END_ARG_INFO() 15 | 16 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addPeriodicTimer, 0, 0, 2) 17 | ZEND_ARG_TYPE_INFO(0, interval, IS_DOUBLE, 0) 18 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 19 | ZEND_END_ARG_INFO() 20 | 21 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_tcpServer, 0, 0, 4) 22 | ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0) 23 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, connections, IS_LONG, 0, "null") 24 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nbytes, IS_LONG, 0, "null") 25 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 26 | ZEND_END_ARG_INFO() 27 | 28 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addSignal, 0, 0, 2) 29 | ZEND_ARG_TYPE_INFO(0, signal, IS_LONG, 0) 30 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 31 | ZEND_END_ARG_INFO() 32 | 33 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addReadStream, 0, 0, 5) 34 | ZEND_ARG_TYPE_INFO(0, stream, IS_RESOURCE, 0) 35 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nbytes, IS_LONG, 0, "null") 36 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, vcount, IS_LONG, 0, "null") 37 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "null") 38 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 39 | ZEND_END_ARG_INFO() 40 | 41 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addWriteStream, 0, 0, 4) 42 | ZEND_ARG_TYPE_INFO(0, stream, IS_RESOURCE, 0) 43 | ZEND_ARG_TYPE_INFO(0, contents, IS_STRING, 0) 44 | ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, vcount, IS_LONG, 0, "null") 45 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 46 | ZEND_END_ARG_INFO() 47 | 48 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_writev, 0, 0, 2) 49 | ZEND_ARG_TYPE_INFO(0, fd, IS_LONG | IS_RESOURCE, 0) 50 | ZEND_ARG_TYPE_INFO(0, contents, IS_STRING, 0) 51 | ZEND_END_ARG_INFO() 52 | 53 | ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_futureTick, 0, 0, 1) 54 | ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 55 | ZEND_END_ARG_INFO() 56 | 57 | ZEND_METHOD(Mrloop, init); 58 | ZEND_METHOD(Mrloop, stop); 59 | ZEND_METHOD(Mrloop, run); 60 | ZEND_METHOD(Mrloop, addTimer); 61 | ZEND_METHOD(Mrloop, addPeriodicTimer); 62 | ZEND_METHOD(Mrloop, tcpServer); 63 | ZEND_METHOD(Mrloop, addSignal); 64 | ZEND_METHOD(Mrloop, addReadStream); 65 | ZEND_METHOD(Mrloop, addWriteStream); 66 | ZEND_METHOD(Mrloop, writev); 67 | ZEND_METHOD(Mrloop, futureTick); 68 | 69 | static const zend_function_entry class_Mrloop_methods[] = { 70 | PHP_ME(Mrloop, init, arginfo_class_Mrloop_init, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) 71 | PHP_ME(Mrloop, stop, arginfo_class_Mrloop_stop, ZEND_ACC_PUBLIC) 72 | PHP_ME(Mrloop, run, arginfo_class_Mrloop_run, ZEND_ACC_PUBLIC) 73 | PHP_ME(Mrloop, addTimer, arginfo_class_Mrloop_addTimer, ZEND_ACC_PUBLIC) 74 | PHP_ME(Mrloop, addPeriodicTimer, arginfo_class_Mrloop_addPeriodicTimer, ZEND_ACC_PUBLIC) 75 | PHP_ME(Mrloop, tcpServer, arginfo_class_Mrloop_tcpServer, ZEND_ACC_PUBLIC) 76 | PHP_ME(Mrloop, addSignal, arginfo_class_Mrloop_addSignal, ZEND_ACC_PUBLIC) 77 | PHP_ME(Mrloop, addReadStream, arginfo_class_Mrloop_addReadStream, ZEND_ACC_PUBLIC) 78 | PHP_ME(Mrloop, addWriteStream, arginfo_class_Mrloop_addWriteStream, ZEND_ACC_PUBLIC) 79 | PHP_ME(Mrloop, writev, arginfo_class_Mrloop_writev, ZEND_ACC_PUBLIC) 80 | PHP_ME(Mrloop, futureTick, arginfo_class_Mrloop_futureTick, ZEND_ACC_PUBLIC) 81 | PHP_FE_END}; 82 | -------------------------------------------------------------------------------- /php_mrloop.c: -------------------------------------------------------------------------------- 1 | /* mrloop extension for PHP (c) 2024 Lochemem Bruno Michael */ 2 | #ifdef HAVE_CONFIG_H 3 | #include "config.h" 4 | #endif 5 | 6 | #include "src/loop.c" 7 | #include "php_mrloop.h" 8 | #include "mrloop_arginfo.h" 9 | 10 | /* {{{ proto Mrloop Mrloop::init() */ 11 | PHP_METHOD(Mrloop, init) 12 | { 13 | php_mrloop_create(INTERNAL_FUNCTION_PARAM_PASSTHRU); 14 | } 15 | /* }}} */ 16 | 17 | /* {{{ proto void Mrloop::run() */ 18 | PHP_METHOD(Mrloop, run) 19 | { 20 | php_mrloop_run(INTERNAL_FUNCTION_PARAM_PASSTHRU); 21 | } 22 | /* }}} */ 23 | 24 | /* {{{ proto void Mrloop::stop() */ 25 | PHP_METHOD(Mrloop, stop) 26 | { 27 | php_mrloop_stop(INTERNAL_FUNCTION_PARAM_PASSTHRU); 28 | } 29 | /* }}} */ 30 | 31 | /* {{{ proto void Mrloop::addTimer( float interval [, callable callback ] ) */ 32 | PHP_METHOD(Mrloop, addTimer) 33 | { 34 | php_mrloop_add_timer(INTERNAL_FUNCTION_PARAM_PASSTHRU); 35 | } 36 | /* }}} */ 37 | 38 | /* {{{ proto void Mrloop::addPeriodicTimer( float interval [, callable callback ] ) */ 39 | PHP_METHOD(Mrloop, addPeriodicTimer) 40 | { 41 | php_mrloop_add_periodic_timer(INTERNAL_FUNCTION_PARAM_PASSTHRU); 42 | } 43 | /* }}} */ 44 | 45 | /* {{{ proto void Mrloop::tcpServer( int port [, callable callback ] ) */ 46 | PHP_METHOD(Mrloop, tcpServer) 47 | { 48 | php_mrloop_tcp_server_listen(INTERNAL_FUNCTION_PARAM_PASSTHRU); 49 | } 50 | /* }}} */ 51 | 52 | /* {{{ proto void Mrloop::addSignal( int signal [, callable callback ] ) */ 53 | PHP_METHOD(Mrloop, addSignal) 54 | { 55 | php_mrloop_add_signal(INTERNAL_FUNCTION_PARAM_PASSTHRU); 56 | } 57 | /* }}} */ 58 | 59 | /* {{{ proto void Mrloop::addReadStream( resource stream [, ?int nbytes = null [, callable callback ]] ) */ 60 | PHP_METHOD(Mrloop, addReadStream) 61 | { 62 | php_mrloop_add_read_stream(INTERNAL_FUNCTION_PARAM_PASSTHRU); 63 | } 64 | /* }}} */ 65 | 66 | /* {{{ proto void Mrloop::addWriteStream( resource stream [, string contents [, callable callback ]] ) */ 67 | PHP_METHOD(Mrloop, addWriteStream) 68 | { 69 | php_mrloop_add_write_stream(INTERNAL_FUNCTION_PARAM_PASSTHRU); 70 | } 71 | /* }}} */ 72 | 73 | /* {{{ proto void Mrloop::writev( int fd [, string contents ] ) */ 74 | PHP_METHOD(Mrloop, writev) 75 | { 76 | php_mrloop_writev(INTERNAL_FUNCTION_PARAM_PASSTHRU); 77 | } 78 | /* }}} */ 79 | 80 | /* {{{ proto void Mrloop::futureTick( callable callback ) */ 81 | PHP_METHOD(Mrloop, futureTick) 82 | { 83 | php_mrloop_add_future_tick(INTERNAL_FUNCTION_PARAM_PASSTHRU); 84 | } 85 | /* }}} */ 86 | 87 | /* {{{ PHP_MINIT_FUNCTION */ 88 | PHP_MINIT_FUNCTION(mrloop) 89 | { 90 | zend_class_entry ce, exception_ce; 91 | 92 | INIT_NS_CLASS_ENTRY(ce, "ringphp", "Mrloop", class_Mrloop_methods); 93 | INIT_CLASS_ENTRY(exception_ce, "MrloopException", NULL); 94 | 95 | php_mrloop_ce = zend_register_internal_class(&ce); 96 | php_mrloop_ce->create_object = php_mrloop_create_object; 97 | 98 | memcpy(&php_mrloop_object_handlers, zend_get_std_object_handlers(), sizeof(php_mrloop_object_handlers)); 99 | php_mrloop_object_handlers.free_obj = php_mrloop_free_object; 100 | 101 | #ifdef HAVE_SPL 102 | php_mrloop_exception_ce = zend_register_internal_class_ex(&exception_ce, spl_ce_RuntimeException); 103 | #else 104 | php_mrloop_exception_ce = zend_register_internal_class_ex(&exception_ce, zend_exception_get_default()); 105 | #endif 106 | 107 | return SUCCESS; 108 | } 109 | /* }}} */ 110 | 111 | /* {{{ PHP_GINIT_FUNCTION */ 112 | static PHP_GINIT_FUNCTION(mrloop) 113 | { 114 | #if defined(ZTS) && defined(COMPILE_DL_MRLOOP) 115 | ZEND_TSRMLS_CACHE_UPDATE(); 116 | #endif 117 | 118 | memset(mrloop_globals, 0, sizeof(*mrloop_globals)); 119 | } 120 | /* }}} */ 121 | 122 | /* {{{ PHP_RINIT_FUNCTION */ 123 | PHP_RINIT_FUNCTION(mrloop) 124 | { 125 | #if defined(ZTS) && defined(COMPILE_DL_MRLOOP) 126 | ZEND_TSRMLS_CACHE_UPDATE(); 127 | #endif 128 | 129 | return SUCCESS; 130 | } 131 | /* }}} */ 132 | 133 | /* {{{ PHP_RSHUTDOWN_FUNCTION */ 134 | PHP_RSHUTDOWN_FUNCTION(mrloop) 135 | { 136 | if (MRLOOP_G(tcp_cb)) 137 | { 138 | efree(MRLOOP_G(tcp_cb)); 139 | } 140 | 141 | if (MRLOOP_G(sigc) > 0) 142 | { 143 | for (size_t idx = 0; idx < MRLOOP_G(sigc); idx++) 144 | { 145 | efree(MRLOOP_G(sig_cb)[idx]); 146 | } 147 | } 148 | 149 | return SUCCESS; 150 | } 151 | /* }}} */ 152 | 153 | /* {{{ PHP_MINFO_FUNCTION */ 154 | PHP_MINFO_FUNCTION(mrloop) 155 | { 156 | php_info_print_table_start(); 157 | php_info_print_table_header(2, "mrloop support", "enabled"); 158 | php_info_print_table_header(2, "mrloop version", MRLOOP_VERSION); 159 | php_info_print_table_header(2, "mrloop author", MRLOOP_AUTHOR); 160 | php_info_print_table_end(); 161 | } 162 | /* }}} */ 163 | 164 | zend_module_entry mrloop_module_entry = { 165 | STANDARD_MODULE_HEADER, 166 | "mrloop", /* extension name */ 167 | class_Mrloop_methods, /* zend_function_entry */ 168 | PHP_MINIT(mrloop), /* PHP_MINIT - module initialization */ 169 | NULL, /* PHP_MSHUTDOWN - module shutdown */ 170 | PHP_RINIT(mrloop), /* PHP_RINIT - request initialization */ 171 | PHP_RSHUTDOWN(mrloop), /* PHP_RSHUTDOWN - request shutdown */ 172 | PHP_MINFO(mrloop), /* PHP_MINFO - module information */ 173 | MRLOOP_VERSION, /* module version */ 174 | PHP_MODULE_GLOBALS(mrloop), /* PHP_MODULE_GLOBALS - module globals */ 175 | PHP_GINIT(mrloop), /* PHP_GINIT - globals initialization */ 176 | NULL, 177 | NULL, 178 | STANDARD_MODULE_PROPERTIES_EX}; 179 | 180 | #ifdef COMPILE_DL_MRLOOP 181 | #ifdef ZTS 182 | ZEND_TSRMLS_CACHE_DEFINE() 183 | #endif 184 | ZEND_GET_MODULE(mrloop) 185 | #endif 186 | -------------------------------------------------------------------------------- /php_mrloop.h: -------------------------------------------------------------------------------- 1 | /* mrloop extension for PHP (c) 2024 Lochemem Bruno Michael */ 2 | #ifndef PHP_MRLOOP_H 3 | #define PHP_MRLOOP_H 4 | 5 | extern zend_module_entry mrloop_module_entry; 6 | 7 | #define MRLOOP_VERSION "0.1.0" 8 | #define MRLOOP_AUTHOR "Lochemem Bruno Michael" 9 | 10 | #if defined(ZTS) && defined(COMPILE_DL_MRLOOP) 11 | ZEND_TSRMLS_CACHE_EXTERN() 12 | #endif 13 | 14 | #endif /* PHP_MRLOOP_H */ 15 | -------------------------------------------------------------------------------- /src/loop.c: -------------------------------------------------------------------------------- 1 | /* mrloop extension for PHP (c) 2024 Lochemem Bruno Michael */ 2 | #include "loop.h" 3 | 4 | static zend_object *php_mrloop_create_object(zend_class_entry *ce) 5 | { 6 | php_mrloop_t *obj = zend_object_alloc(sizeof(php_mrloop_t), php_mrloop_ce); 7 | zend_object_std_init(&obj->std, php_mrloop_ce); 8 | 9 | obj->std.handlers = &php_mrloop_object_handlers; 10 | obj->loop = NULL; 11 | 12 | return &obj->std; 13 | } 14 | static void php_mrloop_free_object(zend_object *obj) 15 | { 16 | php_mrloop_t *intern = php_mrloop_from_obj(obj); 17 | 18 | if (intern->loop) 19 | { 20 | mr_free(intern->loop); 21 | } 22 | 23 | zend_object_std_dtor(obj); 24 | efree(intern); 25 | } 26 | 27 | static void php_mrloop_signal_handler(const int sig) 28 | { 29 | exit(EXIT_SUCCESS); 30 | } 31 | static void php_mrloop_create(INTERNAL_FUNCTION_PARAMETERS) 32 | { 33 | php_mrloop_t *evloop; 34 | 35 | ZEND_PARSE_PARAMETERS_NONE(); 36 | 37 | object_init_ex(return_value, php_mrloop_ce); 38 | evloop = PHP_MRLOOP_OBJ(return_value); 39 | 40 | mr_loop_t *loop = mr_create_loop(php_mrloop_signal_handler); 41 | evloop->loop = loop; 42 | } 43 | static void php_mrloop_stop(INTERNAL_FUNCTION_PARAMETERS) 44 | { 45 | zval *obj; 46 | php_mrloop_t *this; 47 | 48 | obj = getThis(); 49 | 50 | ZEND_PARSE_PARAMETERS_NONE(); 51 | 52 | this = PHP_MRLOOP_OBJ(obj); 53 | 54 | mr_stop(this->loop); 55 | } 56 | static void php_mrloop_run(INTERNAL_FUNCTION_PARAMETERS) 57 | { 58 | zval *obj; 59 | php_mrloop_t *this; 60 | 61 | obj = getThis(); 62 | 63 | ZEND_PARSE_PARAMETERS_NONE(); 64 | 65 | this = PHP_MRLOOP_OBJ(obj); 66 | 67 | mr_run(this->loop); 68 | } 69 | 70 | static int php_mrloop_timer_cb(void *data) 71 | { 72 | php_mrloop_cb_t *cb = (php_mrloop_cb_t *)data; 73 | zval result; 74 | int type; 75 | 76 | cb->fci.retval = &result; 77 | cb->fci.param_count = 0; 78 | cb->fci.params = NULL; 79 | 80 | type = cb->signal; 81 | 82 | if (zend_call_function(&cb->fci, &cb->fci_cache) == FAILURE) 83 | { 84 | efree(cb); 85 | mr_stop((mr_loop_t *)cb->data); 86 | 87 | PHP_MRLOOP_THROW("There is an error in your callback"); 88 | zval_ptr_dtor(&result); 89 | 90 | return 0; 91 | } 92 | 93 | // add explicit timer cancellation to periodic timer 94 | if (type == PHP_MRLOOP_PERIODIC_TIMER && Z_TYPE(result) == IS_LONG && Z_LVAL(result) == 0) 95 | { 96 | zval_ptr_dtor(&result); 97 | efree(cb); 98 | 99 | return 0; 100 | } 101 | 102 | zval_ptr_dtor(&result); 103 | efree(cb); 104 | 105 | return type == PHP_MRLOOP_TIMER || type == PHP_MRLOOP_FUTURE_TICK ? 0 : 1; 106 | } 107 | static void php_mrloop_add_timer(INTERNAL_FUNCTION_PARAMETERS) 108 | { 109 | zval *obj; 110 | double interval; 111 | php_mrloop_t *this; 112 | php_mrloop_cb_t *cb; 113 | zend_fcall_info fci; 114 | zend_fcall_info_cache fci_cache; 115 | 116 | fci = empty_fcall_info; 117 | fci_cache = empty_fcall_info_cache; 118 | obj = getThis(); 119 | 120 | ZEND_PARSE_PARAMETERS_START(2, 2) 121 | Z_PARAM_DOUBLE(interval) 122 | Z_PARAM_FUNC(fci, fci_cache) 123 | ZEND_PARSE_PARAMETERS_END(); 124 | 125 | this = PHP_MRLOOP_OBJ(obj); 126 | cb = emalloc(sizeof(php_mrloop_cb_t)); 127 | PHP_CB_TO_MRLOOP_CB(cb, fci, fci_cache); 128 | 129 | cb->signal = PHP_MRLOOP_TIMER; 130 | cb->data = this->loop; 131 | 132 | mr_call_after(this->loop, php_mrloop_timer_cb, (interval * 1000), cb); 133 | 134 | return; 135 | } 136 | static void php_mrloop_add_periodic_timer(INTERNAL_FUNCTION_PARAMETERS) 137 | { 138 | zval *obj; 139 | double interval; 140 | php_mrloop_t *this; 141 | php_mrloop_cb_t *cb; 142 | zend_fcall_info fci; 143 | zend_fcall_info_cache fci_cache; 144 | 145 | fci = empty_fcall_info; 146 | fci_cache = empty_fcall_info_cache; 147 | obj = getThis(); 148 | 149 | ZEND_PARSE_PARAMETERS_START(2, 2) 150 | Z_PARAM_DOUBLE(interval) 151 | Z_PARAM_FUNC(fci, fci_cache) 152 | ZEND_PARSE_PARAMETERS_END(); 153 | 154 | this = PHP_MRLOOP_OBJ(obj); 155 | cb = emalloc(sizeof(php_mrloop_cb_t)); 156 | PHP_CB_TO_MRLOOP_CB(cb, fci, fci_cache); 157 | 158 | cb->signal = PHP_MRLOOP_PERIODIC_TIMER; 159 | cb->data = this->loop; 160 | 161 | mr_add_timer(this->loop, interval, php_mrloop_timer_cb, cb); 162 | 163 | return; 164 | } 165 | static void php_mrloop_add_future_tick(INTERNAL_FUNCTION_PARAMETERS) 166 | { 167 | zval *obj; 168 | php_mrloop_cb_t *cb; 169 | php_mrloop_t *this; 170 | zend_fcall_info fci; 171 | zend_fcall_info_cache fci_cache; 172 | 173 | fci = empty_fcall_info; 174 | fci_cache = empty_fcall_info_cache; 175 | obj = getThis(); 176 | 177 | ZEND_PARSE_PARAMETERS_START(1, 1) 178 | Z_PARAM_FUNC(fci, fci_cache) 179 | ZEND_PARSE_PARAMETERS_END(); 180 | 181 | this = PHP_MRLOOP_OBJ(obj); 182 | cb = emalloc(sizeof(php_mrloop_cb_t)); 183 | PHP_CB_TO_MRLOOP_CB(cb, fci, fci_cache); 184 | 185 | cb->signal = PHP_MRLOOP_FUTURE_TICK; 186 | cb->data = this->loop; 187 | 188 | mr_call_soon(this->loop, php_mrloop_timer_cb, cb); 189 | 190 | return; 191 | } 192 | 193 | static void php_mrloop_readv_cb(void *data, int res) 194 | { 195 | if (res < 0) 196 | { 197 | PHP_MRLOOP_THROW(strerror(-res)); 198 | } 199 | 200 | php_mrloop_cb_t *cb; 201 | php_iovec_t *iov; 202 | zval args[2], result; 203 | 204 | cb = (php_mrloop_cb_t *)data; 205 | iov = (php_iovec_t *)cb->data; 206 | 207 | char next[(size_t)iov->iov_len]; 208 | php_strncpy(next, iov->iov_base, iov->iov_len + 1); 209 | 210 | ZVAL_STRING(&args[0], next); 211 | ZVAL_LONG(&args[1], res); 212 | 213 | cb->fci.retval = &result; 214 | cb->fci.param_count = 2; 215 | cb->fci.params = args; 216 | 217 | if (zend_call_function(&cb->fci, &cb->fci_cache) == FAILURE) 218 | { 219 | PHP_MRLOOP_THROW("There is an error in your callback"); 220 | } 221 | 222 | zval_ptr_dtor(&result); 223 | efree(cb->data); 224 | efree(cb); 225 | 226 | return; 227 | } 228 | static void php_mrloop_writev_cb(void *data, int res) 229 | { 230 | if (res < 0) 231 | { 232 | PHP_MRLOOP_THROW(strerror(-res)); 233 | } 234 | 235 | php_mrloop_cb_t *cb = (php_mrloop_cb_t *)data; 236 | zval args[1], result; 237 | ZVAL_LONG(&args[0], res); 238 | 239 | cb->fci.retval = &result; 240 | cb->fci.param_count = 1; 241 | cb->fci.params = args; 242 | 243 | if (zend_call_function(&cb->fci, &cb->fci_cache) == FAILURE) 244 | { 245 | PHP_MRLOOP_THROW("There is an error in your callback"); 246 | } 247 | 248 | zval_ptr_dtor(&result); 249 | efree(cb->data); 250 | efree(cb); 251 | 252 | return; 253 | } 254 | 255 | static void *php_mrloop_tcp_client_setup(int fd, char **buffer, int *bsize) 256 | { 257 | php_mrloop_conn_t *conn; 258 | php_sockaddr_t addr; 259 | socklen_t socklen; 260 | char ip_str[INET_ADDRSTRLEN]; 261 | 262 | conn = emalloc(sizeof(php_mrloop_conn_t)); 263 | conn->fd = fd; 264 | conn->buffer = ecalloc(1, MRLOOP_G(tcp_buff_size)); 265 | *buffer = conn->buffer; 266 | *bsize = MRLOOP_G(tcp_buff_size); 267 | 268 | socklen = sizeof(php_sockaddr_t); 269 | 270 | if (getpeername(fd, (struct sockaddr *)&addr, &socklen) > -1) 271 | { 272 | inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN); 273 | 274 | conn->addr = ecalloc(1, INET_ADDRSTRLEN); 275 | php_strncpy(conn->addr, ip_str, INET_ADDRSTRLEN); 276 | 277 | conn->port = (size_t)addr.sin_port; 278 | } 279 | 280 | return (void *)conn; 281 | } 282 | static int php_mrloop_tcp_server_recv(void *conn, int fd, ssize_t nbytes, char *buffer) 283 | { 284 | php_mrloop_conn_t *client = (php_mrloop_conn_t *)conn; 285 | mr_loop_t *loop = (mr_loop_t *)MRLOOP_G(tcp_cb)->data; 286 | 287 | if (nbytes == 0) 288 | { 289 | mr_close(loop, client->fd); 290 | efree(client->addr); 291 | efree(client->buffer); 292 | efree(client); 293 | 294 | return 1; 295 | } 296 | 297 | zval args[2], result; 298 | ZVAL_STRING(&args[0], buffer); 299 | 300 | array_init(&args[1]); 301 | add_assoc_string(&args[1], "client_addr", (char *)client->addr); 302 | add_assoc_long(&args[1], "client_port", client->port); 303 | add_assoc_long(&args[1], "client_fd", dup(client->fd)); 304 | 305 | MRLOOP_G(tcp_cb)->fci.retval = &result; 306 | MRLOOP_G(tcp_cb)->fci.param_count = 2; 307 | MRLOOP_G(tcp_cb)->fci.params = args; 308 | 309 | if (zend_call_function(&MRLOOP_G(tcp_cb)->fci, &MRLOOP_G(tcp_cb)->fci_cache) == FAILURE) 310 | { 311 | PHP_MRLOOP_THROW("There is an error in your callback"); 312 | zval_ptr_dtor(&result); 313 | 314 | return 1; 315 | } 316 | 317 | if (Z_TYPE(result) == IS_STRING) 318 | { 319 | client->iov.iov_base = Z_STRVAL(result); 320 | client->iov.iov_len = Z_STRLEN(result); 321 | 322 | mr_writev(loop, client->fd, &(client->iov), 1); 323 | mr_flush(loop); 324 | } 325 | 326 | zval_ptr_dtor(&result); 327 | 328 | return 1; 329 | } 330 | static void php_mrloop_tcp_server_listen(INTERNAL_FUNCTION_PARAMETERS) 331 | { 332 | zval *obj; 333 | php_mrloop_t *this; 334 | zend_fcall_info fci; 335 | zend_fcall_info_cache fci_cache; 336 | zend_long port, max_conn, nbytes; 337 | bool max_conn_null, nbytes_null; 338 | size_t nconn, fnbytes; 339 | 340 | obj = getThis(); 341 | fci = empty_fcall_info; 342 | fci_cache = empty_fcall_info_cache; 343 | max_conn_null = true; 344 | nbytes_null = true; 345 | 346 | ZEND_PARSE_PARAMETERS_START(4, 4) 347 | Z_PARAM_LONG(port) 348 | Z_PARAM_LONG_OR_NULL(max_conn, max_conn_null) 349 | Z_PARAM_LONG_OR_NULL(nbytes, nbytes_null) 350 | Z_PARAM_FUNC(fci, fci_cache) 351 | ZEND_PARSE_PARAMETERS_END(); 352 | 353 | this = PHP_MRLOOP_OBJ(obj); 354 | 355 | fnbytes = (size_t)(nbytes_null == true ? DEFAULT_CONN_BUFF_LEN : nbytes); 356 | MRLOOP_G(tcp_buff_size) = fnbytes; 357 | 358 | MRLOOP_G(tcp_cb) = emalloc(sizeof(php_mrloop_cb_t)); 359 | PHP_CB_TO_MRLOOP_CB(MRLOOP_G(tcp_cb), fci, fci_cache); 360 | MRLOOP_G(tcp_cb)->data = this->loop; 361 | 362 | nconn = (size_t)(max_conn_null == true ? PHP_MRLOOP_MAX_TCP_CONNECTIONS : (max_conn == 0 ? PHP_MRLOOP_MAX_TCP_CONNECTIONS : max_conn)); 363 | 364 | #ifdef MRLOOP_H 365 | mr_tcp_server(this->loop, (int)port, nconn, php_mrloop_tcp_client_setup, php_mrloop_tcp_server_recv); 366 | #else 367 | mr_tcp_server(this->loop, (int)port, php_mrloop_tcp_client_setup, php_mrloop_tcp_server_recv); 368 | #endif 369 | 370 | return; 371 | } 372 | 373 | static void php_mrloop_signal_cb(int sig) 374 | { 375 | for (size_t idx = 0; idx < MRLOOP_G(sigc); idx++) 376 | { 377 | if (MRLOOP_G(sig_cb)[idx] == NULL) 378 | { 379 | break; 380 | } 381 | 382 | php_mrloop_cb_t *cb = MRLOOP_G(sig_cb)[idx]; 383 | 384 | if (cb->signal == sig) 385 | { 386 | zval result; 387 | 388 | cb->fci.retval = &result; 389 | cb->fci.param_count = 0; 390 | cb->fci.params = NULL; 391 | 392 | if (zend_call_function(&cb->fci, &cb->fci_cache) == FAILURE) 393 | { 394 | PHP_MRLOOP_THROW("There is an error in your callback"); 395 | } 396 | 397 | zval_ptr_dtor(&result); 398 | 399 | break; 400 | } 401 | } 402 | 403 | exit(EXIT_SUCCESS); 404 | } 405 | static void php_mrloop_add_signal(INTERNAL_FUNCTION_PARAMETERS) 406 | { 407 | zend_long php_signal; 408 | zend_fcall_info fci; 409 | zend_fcall_info_cache fci_cache; 410 | 411 | ZEND_PARSE_PARAMETERS_START(2, 2) 412 | Z_PARAM_LONG(php_signal) 413 | Z_PARAM_FUNC(fci, fci_cache) 414 | ZEND_PARSE_PARAMETERS_END(); 415 | 416 | MRLOOP_G(sigc)++; 417 | size_t next = MRLOOP_G(sigc) - 1; 418 | 419 | MRLOOP_G(sig_cb)[next] = emalloc(sizeof(php_mrloop_cb_t)); 420 | PHP_CB_TO_MRLOOP_CB(MRLOOP_G(sig_cb)[next], fci, fci_cache); 421 | MRLOOP_G(sig_cb)[next]->signal = (int)php_signal; 422 | 423 | signal(SIGINT, php_mrloop_signal_cb); 424 | signal(SIGHUP, php_mrloop_signal_cb); 425 | signal(SIGTERM, php_mrloop_signal_cb); 426 | 427 | return; 428 | } 429 | 430 | static void php_mrloop_add_read_stream(INTERNAL_FUNCTION_PARAMETERS) 431 | { 432 | zval *res, *obj; 433 | php_mrloop_t *this; 434 | php_mrloop_cb_t *cb; 435 | php_iovec_t *iov; 436 | zend_fcall_info fci; 437 | zend_fcall_info_cache fci_cache; 438 | zend_long nbytes, vcount, offset; 439 | bool nbytes_null, vcount_null, offset_null; 440 | int fd; // php_socket_t fd; 441 | php_stream *stream; 442 | size_t fnbytes, fvcount, foffset; 443 | 444 | obj = getThis(); 445 | nbytes_null = true; 446 | vcount_null = true; 447 | offset_null = true; 448 | fci = empty_fcall_info; 449 | fci_cache = empty_fcall_info_cache; 450 | fd = -1; 451 | 452 | ZEND_PARSE_PARAMETERS_START(5, 5) 453 | Z_PARAM_RESOURCE(res) 454 | Z_PARAM_LONG_OR_NULL(nbytes, nbytes_null) 455 | Z_PARAM_LONG_OR_NULL(vcount, vcount_null) 456 | Z_PARAM_LONG_OR_NULL(offset, offset_null) 457 | Z_PARAM_FUNC(fci, fci_cache) 458 | ZEND_PARSE_PARAMETERS_END(); 459 | 460 | this = PHP_MRLOOP_OBJ(obj); 461 | 462 | // convert resource to PHP stream 463 | PHP_STREAM_TO_FD(stream, res, fd); 464 | 465 | fnbytes = (size_t)(nbytes_null == true ? DEFAULT_STREAM_BUFF_LEN : nbytes); 466 | fvcount = (size_t)(vcount_null == true ? DEFAULT_VECTOR_COUNT : vcount); 467 | foffset = (size_t)(offset_null == true ? DEFAULT_READV_OFFSET : offset); 468 | 469 | iov = emalloc(sizeof(php_iovec_t)); 470 | iov->iov_base = ecalloc(1, fnbytes); 471 | iov->iov_len = fnbytes; 472 | 473 | cb = emalloc(sizeof(php_mrloop_cb_t)); 474 | PHP_CB_TO_MRLOOP_CB(cb, fci, fci_cache); 475 | 476 | cb->data = iov; 477 | 478 | mr_readvcb(this->loop, fd, iov, fvcount, foffset, cb, php_mrloop_readv_cb); 479 | mr_flush(this->loop); 480 | 481 | return; 482 | } 483 | static void php_mrloop_add_write_stream(INTERNAL_FUNCTION_PARAMETERS) 484 | { 485 | zval *res, *obj; 486 | zend_string *contents; 487 | php_mrloop_t *this; 488 | php_mrloop_cb_t *cb; 489 | php_iovec_t *iov; 490 | zend_fcall_info fci; 491 | zend_fcall_info_cache fci_cache; 492 | zend_long vcount; 493 | bool vcount_null; 494 | int fd; 495 | php_stream *stream; 496 | size_t nbytes, fvcount; 497 | 498 | obj = getThis(); 499 | fci = empty_fcall_info; 500 | fci_cache = empty_fcall_info_cache; 501 | fd = -1; 502 | vcount_null = true; 503 | 504 | ZEND_PARSE_PARAMETERS_START(4, 4) 505 | Z_PARAM_RESOURCE(res) 506 | Z_PARAM_STR(contents) 507 | Z_PARAM_LONG_OR_NULL(vcount, vcount_null) 508 | Z_PARAM_FUNC(fci, fci_cache) 509 | ZEND_PARSE_PARAMETERS_END(); 510 | 511 | this = PHP_MRLOOP_OBJ(obj); 512 | 513 | PHP_STREAM_TO_FD(stream, res, fd); 514 | 515 | nbytes = ZSTR_LEN(contents); 516 | iov = emalloc(sizeof(php_iovec_t)); 517 | iov->iov_base = ecalloc(1, nbytes); 518 | iov->iov_len = nbytes; 519 | 520 | php_strncpy(iov->iov_base, ZSTR_VAL(contents), nbytes + 1); 521 | 522 | cb = emalloc(sizeof(php_mrloop_cb_t)); 523 | PHP_CB_TO_MRLOOP_CB(cb, fci, fci_cache); 524 | 525 | cb->data = iov; 526 | 527 | fvcount = (size_t)(vcount_null == true ? DEFAULT_VECTOR_COUNT : vcount); 528 | 529 | mr_writevcb(this->loop, fd, iov, fvcount, cb, php_mrloop_writev_cb); 530 | mr_flush(this->loop); 531 | 532 | return; 533 | } 534 | static void php_mrloop_writev(INTERNAL_FUNCTION_PARAMETERS) 535 | { 536 | zend_string *contents; 537 | php_iovec_t iov; 538 | php_mrloop_t *this; 539 | zval *obj, *res; 540 | size_t nbytes; 541 | php_stream *stream; 542 | int fd; 543 | 544 | obj = getThis(); 545 | fd = -1; 546 | 547 | ZEND_PARSE_PARAMETERS_START(2, 2) 548 | Z_PARAM_ZVAL(res) 549 | Z_PARAM_STR(contents) 550 | ZEND_PARSE_PARAMETERS_END(); 551 | 552 | this = PHP_MRLOOP_OBJ(obj); 553 | 554 | if (Z_TYPE_P(res) == IS_RESOURCE) 555 | { 556 | PHP_STREAM_TO_FD(stream, res, fd); 557 | } 558 | else if (Z_TYPE_P(res) == IS_LONG) 559 | { 560 | fd = Z_LVAL_P(res); 561 | 562 | if (fcntl(fd, F_GETFD) < 0) 563 | { 564 | PHP_MRLOOP_THROW(strerror(errno)); 565 | mr_stop(this->loop); 566 | 567 | return; 568 | } 569 | } 570 | else 571 | { 572 | PHP_MRLOOP_THROW("Detected invalid file descriptor"); 573 | RETURN_NULL(); 574 | } 575 | 576 | nbytes = ZSTR_LEN(contents); 577 | iov.iov_base = ZSTR_VAL(contents); 578 | iov.iov_len = nbytes; 579 | 580 | mr_writev(this->loop, fd, &iov, 1); 581 | mr_flush(this->loop); 582 | } 583 | 584 | static size_t php_strncpy(char *dst, char *src, size_t nbytes) 585 | { 586 | const char *osrc = src; 587 | size_t nleft = nbytes; 588 | 589 | // copy as many bytes as will fit 590 | if (nleft != 0) 591 | { 592 | while (--nleft != 0) 593 | { 594 | if ((*dst++ = *src++) == '\0') 595 | break; 596 | } 597 | } 598 | 599 | // not enough room in dst, add null byte and traverse rest of src 600 | if (nleft == 0) 601 | { 602 | if (nbytes != 0) 603 | *dst = '\0'; 604 | while (*src++) 605 | ; 606 | } 607 | 608 | return (src - osrc - 1); 609 | } 610 | -------------------------------------------------------------------------------- /src/loop.h: -------------------------------------------------------------------------------- 1 | /* mrloop extension for PHP (c) 2024 Lochemem Bruno Michael */ 2 | #ifndef __LOOP_H__ 3 | #define __LOOP_H__ 4 | 5 | #include "ext/spl/spl_exceptions.h" 6 | #include "ext/standard/info.h" 7 | #include "ext/standard/php_array.h" 8 | #include "ext/standard/php_string.h" 9 | #include "ext/standard/php_var.h" 10 | #include "mrloop.c" 11 | #include "php.h" 12 | #include "php_network.h" 13 | #include "php_streams.h" 14 | #include "signal.h" 15 | #include "sys/file.h" 16 | #include "zend_exceptions.h" 17 | 18 | /* for compatibility with older PHP versions */ 19 | #ifndef ZEND_PARSE_PARAMETERS_NONE 20 | #define ZEND_PARSE_PARAMETERS_NONE() \ 21 | ZEND_PARSE_PARAMETERS_START(0, 0) \ 22 | ZEND_PARSE_PARAMETERS_END() 23 | #endif 24 | 25 | #define DEFAULT_STREAM_BUFF_LEN 1024 26 | #define DEFAULT_CONN_BUFF_LEN 8132 27 | #define DEFAULT_HTTP_HEADER_LIMIT 100 28 | #define DEFAULT_VECTOR_COUNT 1 29 | #define DEFAULT_READV_OFFSET 0 30 | #define PHP_MRLOOP_TIMER 1 31 | #define PHP_MRLOOP_PERIODIC_TIMER 2 32 | #define PHP_MRLOOP_FUTURE_TICK 3 33 | #define PHP_MRLOOP_MAX_TCP_CONNECTIONS 1024 34 | 35 | struct php_mrloop_t; 36 | struct php_mrloop_cb_t; 37 | struct php_mrloop_conn_t; 38 | typedef struct php_mrloop_t php_mrloop_t; 39 | typedef struct php_mrloop_cb_t php_mrloop_cb_t; 40 | typedef struct php_mrloop_conn_t php_mrloop_conn_t; 41 | 42 | typedef struct iovec php_iovec_t; 43 | typedef struct addrinfo php_addrinfo_t; 44 | typedef struct sockaddr_in php_sockaddr_t; 45 | typedef struct phr_header phr_header_t; 46 | typedef struct stat php_stat_t; 47 | 48 | /* userspace-bound event loop object */ 49 | struct php_mrloop_t 50 | { 51 | /* event loop instance */ 52 | mr_loop_t *loop; 53 | /* PHP object */ 54 | zend_object std; 55 | }; 56 | 57 | /* TCP client connection object */ 58 | struct php_mrloop_conn_t 59 | { 60 | /* client socket file descriptor */ 61 | int fd; 62 | /* client socket address */ 63 | char *addr; 64 | /* data sent over client socket */ 65 | char *buffer; 66 | /* client socket port */ 67 | size_t port; 68 | /* scatter-gather I/O primitives */ 69 | php_iovec_t iov; 70 | }; 71 | 72 | /* mrloop callback object */ 73 | struct php_mrloop_cb_t 74 | { 75 | /* PHP callback interface */ 76 | zend_fcall_info fci; 77 | /* PHP callback cache */ 78 | zend_fcall_info_cache fci_cache; 79 | /* arbitrary data relevant to callback */ 80 | void *data; 81 | /* ancillary numeric data (signal or otherwise) relevant to callback */ 82 | int signal; 83 | }; 84 | 85 | zend_object_handlers php_mrloop_object_handlers; 86 | 87 | static inline php_mrloop_t *php_mrloop_from_obj(zend_object *obj) 88 | { 89 | return (php_mrloop_t *)((char *)obj - XtOffsetOf(php_mrloop_t, std)); 90 | } 91 | 92 | #define PHP_MRLOOP_OBJ(zv) php_mrloop_from_obj(Z_OBJ_P(zv)); 93 | 94 | /* {{{ ZEND_BEGIN_MODULE_GLOBALS */ 95 | ZEND_BEGIN_MODULE_GLOBALS(mrloop) 96 | /* TCP server callback */ 97 | php_mrloop_cb_t *tcp_cb; 98 | /* signal callback */ 99 | php_mrloop_cb_t *sig_cb[3]; 100 | /* signal callback count */ 101 | size_t sigc; 102 | /* TCP buffer size */ 103 | size_t tcp_buff_size; 104 | ZEND_END_MODULE_GLOBALS(mrloop) 105 | /* }}} */ 106 | 107 | ZEND_DECLARE_MODULE_GLOBALS(mrloop) 108 | 109 | /* ext-mrloop globals accessor */ 110 | #ifdef ZTS 111 | #define MRLOOP_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(mrloop, v) 112 | #else 113 | #define MRLOOP_G(v) (mrloop_globals.v) 114 | #endif 115 | 116 | static PHP_GINIT_FUNCTION(mrloop); 117 | 118 | /* safe rendition of native C strncpy (adapted from https://github.com/ariadnavigo/strlcpy) */ 119 | static size_t php_strncpy(char *dst, char *src, size_t nbytes); 120 | 121 | /* creates mrloop object in PHP userspace */ 122 | static zend_object *php_mrloop_create_object(zend_class_entry *ce); 123 | /* frees PHP userspace-residing mrloop object */ 124 | static void php_mrloop_free_object(zend_object *obj); 125 | 126 | /* callback specified during creation of event loop */ 127 | static void php_mrloop_signal_handler(const int sig); 128 | /* creates Mrloop object in PHP userspace */ 129 | static void php_mrloop_create(INTERNAL_FUNCTION_PARAMETERS); 130 | /* explicitly stops event loop subsumed in Mrloop object */ 131 | static void php_mrloop_stop(INTERNAL_FUNCTION_PARAMETERS); 132 | /* runs event loop subsumed in Mrloop object */ 133 | static void php_mrloop_run(INTERNAL_FUNCTION_PARAMETERS); 134 | 135 | /* mrloop-bound callback specified during invocation of timer-related functions */ 136 | static int php_mrloop_timer_cb(void *data); 137 | /* executes a specified action after a specified amount of time */ 138 | static void php_mrloop_add_timer(INTERNAL_FUNCTION_PARAMETERS); 139 | /* executes a specified action in perpetuity with each successive execution occurring after a specified time interval */ 140 | static void php_mrloop_add_periodic_timer(INTERNAL_FUNCTION_PARAMETERS); 141 | /* schedules the execution of a specified action for the next event loop tick */ 142 | static void php_mrloop_add_future_tick(INTERNAL_FUNCTION_PARAMETERS); 143 | 144 | /* mrloop-bound callback specified during invocation of vectorized read function */ 145 | static void php_mrloop_readv_cb(void *data, int res); 146 | /* mrloop-bound callback specified during invocation of vectorized write function */ 147 | static void php_mrloop_writev_cb(void *data, int res); 148 | 149 | /* initializes client connection context for TCP server */ 150 | static void *php_mrloop_tcp_client_setup(int fd, char **buffer, int *bsize); 151 | /* processes incoming TCP connections and issues responses to clients */ 152 | static int php_mrloop_tcp_server_recv(void *conn, int fd, ssize_t nbytes, char *buffer); 153 | /* starts a TCP server */ 154 | static void php_mrloop_tcp_server_listen(INTERNAL_FUNCTION_PARAMETERS); 155 | 156 | /* mrloop-bound callback specified during invocation of signal handlers */ 157 | static void php_mrloop_signal_cb(int sig); 158 | /* executes specified action in the event that a specified signal is detected */ 159 | static void php_mrloop_add_signal(INTERNAL_FUNCTION_PARAMETERS); 160 | 161 | /* funnels file descriptor in readable stream into event loop and thence executes a non-blocking read operation */ 162 | static void php_mrloop_add_read_stream(INTERNAL_FUNCTION_PARAMETERS); 163 | /* funnels file descriptor in writable stream into event loop and thence executes a non-blocking write operation */ 164 | static void php_mrloop_add_write_stream(INTERNAL_FUNCTION_PARAMETERS); 165 | /* performs vectorized non-blocking write operation on a specified file descriptor */ 166 | static void php_mrloop_writev(INTERNAL_FUNCTION_PARAMETERS); 167 | 168 | zend_class_entry *php_mrloop_ce, *php_mrloop_exception_ce; 169 | 170 | #define PHP_MRLOOP_THROW(message) zend_throw_exception(php_mrloop_exception_ce, message, 0); 171 | 172 | /* wraps PHP function in mrloop callback-bound structure */ 173 | #define PHP_CB_TO_MRLOOP_CB(mrloop_cb, php_fci, php_fci_cache) \ 174 | memcpy(&mrloop_cb->fci, &php_fci, sizeof(zend_fcall_info)); \ 175 | memcpy(&mrloop_cb->fci_cache, &php_fci_cache, sizeof(zend_fcall_info_cache)); \ 176 | Z_TRY_ADDREF(mrloop_cb->fci.function_name); \ 177 | if (php_fci.object) \ 178 | { \ 179 | GC_ADDREF(mrloop_cb->fci.object); \ 180 | } 181 | 182 | /* extract file descriptor from PHP stream */ 183 | #define PHP_STREAM_TO_FD(fd_stream, fd_resource, fd) \ 184 | if ((fd_stream = (php_stream *)zend_fetch_resource_ex( \ 185 | fd_resource, NULL, php_file_le_stream()))) \ 186 | { \ 187 | if (php_stream_cast(fd_stream, \ 188 | PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, \ 189 | (void *)&fd, 1) == FAILURE || \ 190 | fd < 0) \ 191 | { \ 192 | PHP_MRLOOP_THROW("Passed resource without file descriptor"); \ 193 | RETURN_NULL(); \ 194 | } \ 195 | } \ 196 | if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) \ 197 | { \ 198 | close(fd); \ 199 | char *error = strerror(errno); \ 200 | PHP_MRLOOP_THROW(error); \ 201 | mr_stop(this->loop); \ 202 | } 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | init() creates instance of Mrloop 3 | --FILE-- 4 | 13 | --EXPECT-- 14 | bool(true) 15 | -------------------------------------------------------------------------------- /tests/002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | addReadStream() funnels file descriptor in readable stream into event loop and thence executes a non-blocking read operation 3 | --FILE-- 4 | addReadStream( 11 | $fd = \popen('pwd', 'r'), 12 | null, 13 | null, 14 | null, 15 | function (...$args) use ($fd, $loop) { 16 | [$message] = $args; 17 | 18 | var_dump( 19 | (bool) \preg_match( 20 | \sprintf( 21 | '/%s/ix', 22 | \preg_quote($message, '/'), 23 | ), 24 | __DIR__, 25 | ), 26 | ); 27 | 28 | \pclose($fd); 29 | 30 | $loop->stop(); 31 | }, 32 | ); 33 | 34 | $loop->run(); 35 | 36 | ?> 37 | --EXPECT-- 38 | bool(true) 39 | -------------------------------------------------------------------------------- /tests/003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | addWriteStream() funnels file descriptor in writable stream into event loop and thence executes a non-blocking write operation 3 | --FILE-- 4 | addWriteStream( 13 | $fd = \fopen($file, 'w'), 14 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 15 | null, 16 | function (int $nbytes) use ($fd, $file, $loop) { 17 | var_dump($nbytes); 18 | 19 | \fclose($fd); 20 | \unlink($file); 21 | 22 | $loop->stop(); 23 | }, 24 | ); 25 | 26 | $loop->run(); 27 | 28 | ?> 29 | --EXPECT-- 30 | int(56) 31 | -------------------------------------------------------------------------------- /tests/008.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | addSignal() performs a specified action in the event that a specified signal is detected 3 | --SKIPIF-- 4 | 16 | --FILE-- 17 | addTimer( 24 | 1.2, 25 | function () { 26 | var_dump('Tick'); 27 | 28 | \posix_kill( 29 | \posix_getpid(), 30 | (\defined('SIGINT') ? SIGINT : 2), 31 | ); 32 | }, 33 | ); 34 | 35 | $loop->addSignal( 36 | (\defined('SIGINT') ? SIGINT : 2), 37 | function () { 38 | echo "Terminated with SIGINT\n"; 39 | }, 40 | ); 41 | 42 | $loop->run(); 43 | 44 | ?> 45 | --EXPECT-- 46 | string(4) "Tick" 47 | Terminated with SIGINT 48 | -------------------------------------------------------------------------------- /tests/009.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | addTimer() executes a specified action after a specified amount of time 3 | --FILE-- 4 | addTimer( 11 | 1.5, 12 | function () use ($loop) { 13 | var_dump('Tick'); 14 | 15 | $loop->stop(); 16 | }, 17 | ); 18 | 19 | $loop->run(); 20 | 21 | ?> 22 | --EXPECT-- 23 | string(4) "Tick" 24 | -------------------------------------------------------------------------------- /tests/010.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | addPeriodicTimer() executes a specified action in perpetuity with each successive execution occurring after a specified time interval 3 | --FILE-- 4 | addPeriodicTimer( 13 | 1.2, 14 | function () use ($loop, &$tick) { 15 | var_dump(++$tick); 16 | 17 | if ($tick === 5) { 18 | $loop->stop(); 19 | } 20 | }, 21 | ); 22 | 23 | $loop->run(); 24 | 25 | ?> 26 | --EXPECT-- 27 | int(1) 28 | int(2) 29 | int(3) 30 | int(4) 31 | int(5) 32 | -------------------------------------------------------------------------------- /tests/011.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | writev() performs vectorized non-blocking write operation on file descriptor 3 | --FILE-- 4 | writev(1, "Hello, user"); 11 | $loop->stop(); 12 | 13 | $loop->run(); 14 | 15 | ?> 16 | --EXPECT-- 17 | Hello, user 18 | -------------------------------------------------------------------------------- /tests/012.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | writev() throws exception on detection of invalid file descriptor 3 | --FILE-- 4 | writev(987874, "Hello, user"); 12 | } catch (\Throwable $err) { 13 | $loop->writev(1, $err->getMessage()); 14 | } 15 | $loop->stop(); 16 | 17 | $loop->run(); 18 | 19 | ?> 20 | --EXPECT-- 21 | Bad file descriptor 22 | -------------------------------------------------------------------------------- /tests/013.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | futureTick() schedules the execution of a specified action for the next event loop tick 3 | --FILE-- 4 | futureTick( 13 | function () use (&$tick) { 14 | echo \sprintf("Tick: %d\n", ++$tick); 15 | }, 16 | ); 17 | 18 | $loop->futureTick( 19 | function () use ($loop, &$tick) { 20 | echo \sprintf("Tick: %d\n", ++$tick); 21 | $loop->stop(); 22 | }, 23 | ); 24 | 25 | $loop->run(); 26 | 27 | ?> 28 | --EXPECT-- 29 | Tick: 1 30 | Tick: 2 31 | --------------------------------------------------------------------------------