Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Long running PHP script #56

Open
clanstyles opened this issue Jul 21, 2018 · 5 comments
Open

Long running PHP script #56

clanstyles opened this issue Jul 21, 2018 · 5 comments

Comments

@clanstyles
Copy link

How would you handle a long running PHP script? How would we get "sigterm" notifications to allow the PHP script to close gracefully?

@deuill
Copy link
Owner

deuill commented Jul 21, 2018

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.

@clanstyles
Copy link
Author

clanstyles commented Jul 21, 2018

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.

@deuill
Copy link
Owner

deuill commented Jul 22, 2018

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).

@clanstyles
Copy link
Author

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?

@deuill
Copy link
Owner

deuill commented Jul 22, 2018

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants