5

Hi I'm looking for a way to implement a coroutine in a php file. The idea is that I have long processes that need to be able to yield for potentially hours or days. So other php files will be calling functions in the same file as the coroutine to update something, then call a function like $coroutine.process() that causes the coroutine to continue from its last yield. This is to avoid having to use a large state machine.

I'm thinking that the coroutine php file will not actually be running when it's idle, but that when given processing time, will enter from the top and use something like a switch or goto to restart from the previous yield. Then when it reaches the next yield, the file will save its current state somewhere (like a session or database) and then exit.

Has anyone heard of this, or a metaphor similar to this? Bonus points for aggregating and managing multiple coroutines under one collection somehow, perhaps with support for a thread-like join so that flow continues in one place when they finish (a bit like Go).

UPDATE: php 5.5.0 has added support for generators and coroutines:

https://github.com/php/php-src/blob/php-5.5.0/NEWS

https://wiki.php.net/rfc/generators

I have not tried it yet, so perhaps someone can suggest a barebones example. I'm trying to convert a state machine into a coroutine. So for example a switch command inside of a for loop (whose flow is difficult to follow, and error prone as more states are added) converted to a cooperative thread where each decision point is easy to see in an orderly, linear flow that pauses for state changes at the yield keyword.

A concrete example of this is, imagine you are writing an elevator controller. Instead of determining whether to read the state of the buttons based on the elevator's state (STATE_RISING, STATE_LOWERING, STATE_WAITING, etc), you write one loop with sub-loops that run while the elevator is in each state. So while it's rising, it won't lower, and it won't read any buttons except the emergency button. This may not seem like a big deal, but in a complex state machine like a chat server, it can become almost impossible to update a state machine without introducing subtle bugs. Whereas the cooperative thread (coroutine) version has a plainly visibly flow that's easier to debug.

Zack Morris
  • 4,727
  • 2
  • 55
  • 83

3 Answers3

3

The Swoole Coroutine library provides go like coroutines for PHP. Each coroutine adds only 8K of ram per process. It provides a coroutine API with the basic functions expected (such as yield and resume), coro utilities such a coroutine iterator, as well as higher level coroutine builtins such as filesystem functions and networking (socket clients and servers, redis client and server, MySQL client, etc).

A second element to your question is the ability to have long lived coroutines - this likely isn't a good idea unless you are saving the state of the coro in a session and allowing the coro to end/close. Otherwise the request will have to live as long as the coroutine. If the service is being hosted by a long lived PHP script the scenario is easier and the coroutine will simply live until it is allowed to / forced to close.

Swoole performs comparibly to Node.js and Go based services, and is used in multiple production services that regularly host 500K+ TCP connections. It is a little known gem for PHP, largely because it is developed in China and most support and documentation is limited to Chinese speakers, although a small handful of individuals strive to help individuals that speak other languages.

One nice point for Swoole is that it's PHP classes wrap an expansive C/C++ api designed from to start to allow all of it's features to be used without PHP. The same source can easily be compiled as both a PHP extension and/or a standard library for both *NIX systems and Windows.

JSON
  • 1,819
  • 20
  • 27
2

PHP does not support coroutines.

I would write a PHP extension with setcontext(), of course assuming you are targeting Unix platforms.

Here a StackOverflow question about getting started with PHP extensions: Getting Started with PHP Extension-Development.

Why setcontext()? It is a little known fact that setcontext() can be used for coroutines. Just swap the context when calling another coroutine.

Community
  • 1
  • 1
nalply
  • 26,770
  • 15
  • 78
  • 101
  • 1
    That's making the bold assumption that jumping around between contexts will be perfectly safe for the PHP interpreter. It is, in all probability, not. –  Oct 17 '12 at 17:37
  • Ya I'd prefer not to dig too deeply under the hood. I'm thinking it might work more like a resumable process, where the script isn't actually running, but can save and resume somehow. I'm wondering if maybe I can encode the logic somehow in another script maybe (python/javascript/even xml?) and call it from php. – Zack Morris Oct 19 '12 at 16:29
  • That's a good idea to explore. Python has coroutines via generators. But I think this way is also difficult but in a different way. But when re-reading your question, I feel something emerging in my mind. I need to think very carefully about it and write some tests. I think there could be - maybe - a rather simple way. – nalply Oct 19 '12 at 17:46
  • you can add clojure or go (all of them support light threads) – dgierejkiewicz Jan 23 '17 at 08:27
  • No, you would be switching the context within zend engine itself. The API for properly doing it is in [zend_execute.h](https://github.com/php/php-src/blob/master/Zend/zend_execute.h). An example of how it is done can be seen [here](https://github.com/swoole/swoole-src/blob/6c847bdac238d2146c5aa3da13d04dc5b04988af/swoole_coroutine.cc#L190) in the 'php_coro_create' function used by the swoole coroutine library for PHP. It's pretty heavy to say the least. – JSON Nov 30 '18 at 05:05
0

I am writing a second answer because there seems to be a different approach to PHP coroutines.

With Comet HTTP responses are long-lived. Small <script> chunks are sent from time to time and the JavaScript is executed by the browser as they arrive. The response can pause for a long time waiting for an event. 2001 I wrote a small hobby chat server in Java exploiting this technique. I was abroad for half a year and was homesick and used this to chat with my parents and my friends at home.

The chat server showed me that it is possible that a HTTP request triggers other HTTP responses. This is somewhat like a coroutine. All the HTTP responses are waiting for an event and if the event applies for a response, it takes up processing and then goes sleeping again, after having triggered some other response.

You need a medium over which the PHP "processes" communicate with each other. A simple medium are files, but I think a database would be a better fit. My old chat server used a log file. Chat messages were appended to the log file and all chat processes were continually reading from the end of the log file in an endless loop. PHP supports sockets for direct communication, but this needs a different setup.

To get started, I propose these two functions:

function get_message() {
    # Check medium. Return a message; or NULL if there are no messages waiting.
}

function send_message($message) {
    # Write a message to the medium.
}

Your coroutines loop like this:

while (1) {
    sleep(1); // go easy on the CPU
    $message = get_message();
    if ($message === NULL) continue;

    # Your coroutine is now active. Act on the message.
    # You can send send messages to other coroutines.
    # You also can send <script> chunks to the browser, like this:
    echo '<script type="text/javascript">';
    echo '// Your JavaScript code';
    echo '</script>';
    flush();

    # Yield
}    

To yield use continue, because it restarts the while (1) loop waiting for messages. The coroutine also yields at the end of the loop.

You can give your coroutines IDs and/or devise a subscription model in which some coroutines listen to some messages but not all.

Edit:

Sadly PHP and Apache are not a very good fit for a scalable solution. Even if most of the time the coroutines don't do anything, they hog memory as processes, and Apache starts trashing memory if there are too many of them, maybe for a few thousand coroutines. Java is not very much better, but since my chat server was private, I didn't experience performance problems. There never were more than 10 users accessing it simultaneously.

Ningx, Node.js or Erlang have this solved in a better way.

nalply
  • 26,770
  • 15
  • 78
  • 101
  • Ya true, I also tried writing a chat server in php/mysql back in 2007 and ended up saving the state in database tables. It got tremendously complicated though to implement the decision points, where say, a user wants to send a message to multiple recipients or invite multiple people who go through a check-in process of some kind to join the chatroom. It got to be such a mess that I also began exploring erlang, thinking that maybe I could incorporate some of the state recognition concepts into php to turn a stream of events into concrete states. – Zack Morris Oct 20 '12 at 16:39
  • Yes this can get complicated. It is possible to abstract away some part of it with the `get_message()` and `send_message()` functions. – nalply Oct 20 '12 at 16:50