go-php
go-php copied to clipboard
Long running PHP script
How would you handle a long running PHP script? How would we get "sigterm" notifications to allow the PHP script to close gracefully?
Generally speaking, calling context.Destroy()
or engine.Destroy()
will gracefully shut down PHP and call any defined __destruct
handlers for instantiated classes. So, for a PHP script like this:
<?php
class Test {
public function __construct() {
echo 'Constructing Test' . PHP_EOL;
}
public function __destruct() {
echo 'Destroying Test' . PHP_EOL;
}
public function run() {
while (true) {
echo $i++ . PHP_EOL;
sleep(1);
}
}
}
$t = new Test;
$t->run();
And a Go program like this:
package main
import (
"fmt"
"github.com/deuill/go-php"
"os"
"os/signal"
)
func main() {
engine, _ := php.New()
context, _ := engine.NewContext()
context.Output = os.Stdout
var stop = make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, os.Kill)
go func() {
fmt.Println("Executing script...")
context.Exec("index.php")
}()
<-stop
fmt.Println("\rDestroying engine...")
engine.Destroy()
}
You'd get output like this:
Executing script...
Instantiating Test
1
2
3
4
Destroying engine...
Destroying Test
It is not possible to bind variables into running scripts (that is, running context.Bind
does not affect current instances of context.Exec
or context.Eval
). However, it may be interesting to have channels exposed as custom resources, with an accompanying API for handling these.
Hey @deuill thanks for the response!
I like the use of a class deconstructor to control the shutting down, I hadn't thought of that. In additional tests, it doesn't seem like you can create multiple instances of a script, even if you share the context without a segfault / memory corruption. Do you have any examples of that? These would just be isolated scripts with nothing in common.
Correct, running concurrent scripts in the same context is treacherous, even if the two share no global scope (class names, function names, global/file-level variables etc).
There is no real way to avoid this without using PHP with ZTS, which isn't currently supported here (I "temporarily" removed ZTS support during the move to PHP7, as it was significantly refactored between PHP5 and PHP7). I'd be glad to help if you or anyone else wanted to go down the rabbit-hole of integrating ZTS back in, however most of the things I've used go-php
for are limited to one execution thread (as stupid and limiting that may seem).
Yeah I'd be interested in helping. I have a use case where I would prefer to keep mpst of the code in Go but run a library in PHP. I need to be able to run many instances isolated.
Where do we start? Whats required to get ZTS support working?
While the way PHP handles thread-safety has been cleaned up between PHP 5.x and 7.x (starting with this commit), the principle is the same: configuring PHP with --enable-maintainer-zts
will define the ZTS
macro, which is checked during compilation.
Documentation on PHP internals is miserable, and the code isn't documented for the most part either, so the way I've mostly developed through this library is trying to find equivalent functionality in the PHP source code.
Take, for example, the engine_init
function, which runs for php.New()
and initializes the PHP VM. My starting point for this was the equivalent embed
SAPI and the php_embed_init
function in particular. You may notice similarities. In a similar vein, support for binding Go method receivers as PHP classes was based on internal PHP classes, such as curl.
Adding support for ZTS would involve looking at points of integration such as engine_init
and adding equivalent #ifdef ZTS
parts just like the PHP source does it, for example:
#ifdef ZTS
tsrm_startup(1, 1, 0, NULL);
(void)ts_resource(0);
ZEND_TSRMLS_CACHE_UPDATE();
#endif
Which should go near the top of engine_init
, just like it appears near the top of php_embed_init
.
Supporting ZTS across PHP 5.x and 7.x is a PITA, so I wouldn't bother supporting PHP 5.x. Getting pre-built PHP versions with ZTS enabled is also a major PITA, so I'd suggest extending the Dockerfile to include the necessary ./configure
parameter, and use that environment for testing (via make docker-test
).