Native and transparent PHP coroutines and async/await without Swoole

Green threads for PHP, thanks to PHP 8.1 Fibers.

composer require moebius/coroutine

Final final update: 1.0.0-beta released

The API is stable, but there might still be bugs. It contains a small set of functions which can be used anywhere in your code base, and it SHOULD (but may - please report) not cause any side effects in existing code bases.

All these functions can be used anywhere in your application. When used, future coroutines will benefit.

  • M\go(callable): Coroutine launches a coroutine

  • M\await(Coroutine): mixed returns the value from the coroutine.

  • M\sleep(float): void pauses the coroutine and allows other coroutines to work.

  • M\unblock(resource): resource enables transparent context switching on blocking stream operations.

Final update: 0.0.5-alpha out I don’t think Reddit is the place to make announcements about this library, so I will make this final update to this post.

  • Automatic context switching between coroutines is supported for streams using protocol wrappers (file:// is currently the only one implemented)

  • http:// is probably very similar to file:// and I suspect implementing this one will make many other PHP functions asynchronous as well.

  • Some regression tests have been added to help avoid breaking things.

  • A small bug was fixed where a coroutine would not be allowed to complete when the process terminated.

UPDATE

Install it via composer:

```

composer require moebius/moebiusto try it out. `` and try it out. Remember it requires PHP 8.1. Never tested it on Windows.

``` <?php use M{go, await, sleep}}; // these functions should get you started

function typewrite(string $message) { for ($i = 0; $i < mb_strlen($message); $i++) { echo mb_substr($message, $i, 1); sleep(0.3); } }

go(typewrite(...), "H world!\n"); echo "ello"; ```

/UPDATE

PS: This is pure PHP 8.1 without any PECL modules...

I've been working on a small micro-framework to make it easier to work with asynchronous and parallel code, and I would love some feedback and perhaps somebody would like to get involved with this "experiment" - it is already proving useful to me.

Most importantly: I'm designing this to work with existing code. It is quite different from ReactPHP and Amphp.

Features so far:

  • M::go() for launching Fiber based coroutines (which return a Promise object).
  • M::await() for synchronously awaiting promises (very useful with the go() function).
  • "Invisible event loop" using register_shutdown_function(),

Work in progress:

  • Support for non-blocking IO using stream_select(). This is almost complete.
  • "Magically" making old blocking code asynchronous. My implementation seems to be working, but I'm not ready to reveal it yet.

Wishlist:

  • TCP server implementation which launches coroutines to handle connections.
  • HTTP server implementation that builds on top of the TCP server implementation.
  • Non-blocking database drivers (preferably compatible with PDO).
  • Resource pool implementation (to manage multiple concurrent database connections for example).

I will try to publish this later tonight. Working on documentation so that people can start trying it at once.

Resolving a promise: <?php function my_existing_synchronous_function() { // Any promise (with a `then(callable, callable)` method) $result = M::await($somePromise); }

Run something in parallel: ``` <?php // 5 seconds coroutine $workFor5Seconds = M::go(function() { for ($i = 0; $i < 5; $i++) { M::sleep(1); } });

// 2 seconds coroutine $workFor2Seconds = M::go(function() { for ($i = 0; $i < 2; $i++) { M::sleep(1); } });

// Busy coroutine with file IO $workForAWhile = M::go(function() { file_put_contents('/tmp/some-file.txt', 'Hello'); for ($i = 0; $i < 10000; $i++) { // Reading from the file system in a blocking manner // will allow other coroutines to make some progress. file_get_contents('/tmp/some-file.txt'); } unlink('/tmp/some-file.txt'); });

// Now wait for both co-routines to finish await($workFor2Seconds, $workFor5Seconds, $workForAWhile); echo "This took 5 seconds\n"; ```

I think this is surprisingly awesome, which is why I am posting one of my first posts here on reddit...