How do I execute a php-cli from php-cgi with nohup - php

I am trying to execute a php-cli script from php-cgi
What I got below does not work from cgi.
It does however work when I execute it directly from a shell.
Is there a way to accomplish this?
<?php
if (PHP_SAPI === 'cli')
{
require '../boot.php';
Logger::mailit();
exit;
}
class Logger {
private static $instance = NULL;
private function __construct(){}
public function __destruct()
{
if (PHP_SAPI != 'cli')
{
exec('nohup php '. __FILE__ .' &');
}
}
public static function mailit(){
// Database stuff ...
mail( $row->email, $row->subject, $row->message, $row->headers);
}
}
?>

You have to place the call (Logger::mailit()) of your class after the definition (class Logger) of your class.

Got it actually working, and it was all just a little bug
changed this:
require '../boot.php';
to this:
require __dir__.'/../boot.php';

You can either remove the CLI check if (PHP_SAPI === 'cli'), or include the file and call require '../boot.php'; Logger::mailit(); yourself. Or you can look into exec() (or proc_open(), …) to execute the script as its own process. This might be necessary if you need the sandboxing.
have you tried something like exec('your/script.php &')? (NOHUP)
with proc_open() you can setup the environment for your script (including funky file descriptor stuff). with proc_get_status() you can get the PID of the process you started with proc_open(). With posix_kill() you can send signals to a process.
If I'm not mistaking, your parent process (in your case the php-cgi) keeps running until the child process (in your case the php-cli) finished - even if you setup non-blocking execution.

Related

Catching terminal terminations/exits with Symfony Console (CTRL+C)

I have built a command which triggers file downloads from over the internet, however since these files need to be processed by another component, we need to make sure that every file that has been downloaded and has not been modified in the last 10 seconds, is a proper video and not corrupted/partially downloaded.
For this reason, we need to find a way to catch CTRL+C or command terminations and cleanup any applicable file that has not been successfully downloaded.
This is what I tried so far by using symfony/console and symfony/event-dispatcher:
#!/usr/bin/env php
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use Symfony\Component\Console\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use ImportExport\Console\ImportCommand;
use Monolog\Logger;
$dotenv = new Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();
$logger = new Logger('console');
$dispatcher = new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
// gets the command that has been executed
$command = $event->getCommand();
var_dump($command);
});
$application = new Application("Import-Export System", 'v0.1.0-ALPHA');
$application->add(new ImportCommand($logger));
$application->setDispatcher($dispatcher);
$application->run();
However the var_dump() is never shown in the console, if I do CTRL+C.
Suggestions?
When you do CTRL+C it is actually SIGINT that is being sent, not SIGTERM. The best way I can think of is to register handler with http://php.net/manual/en/function.pcntl-signal.php and dispatch the signal with pcntl_signal_dispatch, example:
pcntl_signal(SIGINT,'sigIntHandler');
function sigIntHandler() {
// Do some stuff
}
Of course you need to adjust it to your needs. Note that you can also defer to class methods inside your commands, so you could for example create an AbstractCommand with generic sigIntHandler() and register it in the constructor:
pcntl_signal(SIGINT, [$this, 'sigIntHandler']);
Then use pcntl_signal_dispatch() for example in the loop of your command (it needs to be called in each iteration).
Since Symfony 5.2, it's a native feature: https://symfony.com/blog/new-in-symfony-5-2-console-signals
Implement an interface (SignalableCommandInterface), subscribe to signals, and handle them:
public function handleSignal(int $signal)
{
if (SIGINT === $signal) {
// ...
}
// ...
}

Calling a Python script from PHP using Multi-threading

I'm using PHP to display the Python output in the browser. Since the Python script takes a long time to print the output, The User Interface gets hung till the Python script finishes its execution.
Pthreads for Windows is enabled in PHP for multithreading the shell_exec( ) function which invokes the Python script. Inspite of using Pthreads, the User Interface still gets hung.
Here is the code:
$output_customer_churn_prediction = "";
class WorkerThreads extends Thread {
private $workerId;
public function __construct($id)
{
$this->workerId = $id;
}
public function run()
{
sleep(rand(0, 3));
echo "Worker {$this->workerId} ran" . PHP_EOL;
$output_customer_churn_prediction = shell_exec('python '.$_SERVER['DOCUMENT_ROOT'].'/analytics/python/neural-net-prediction-customer-attrition.py');
print_r($output_customer_churn_prediction);
}
}
$workers[0] = new WorkerThreads(0);
$workers[0]->start();
print_r($output_customer_churn_prediction, true);
Even with multithreading, the User Interface (generated by PHP) gets hung while invoking the Python Neural Net script.
What could be the reason ?
PHP is caching the Output. You Need to deactivate the Output cache. Then your Browser will not hang.
See ob_flush()

Laravel console command catch signal from terminal

I'm trying to catch signals in terminal. I know that I can use pcntl_signal() function but it doesn't work for me.
I am trying with this code:
public function handle() {
pcntl_signal(SIGINT, function ($signo) {
echo "CATCH!\n";
exit;
});
while (true) { echo("!\n"); sleep(2); }
}
When I press Ctrl+C I don't see any result, because program still is working. What do I wrong?
I want to stop program after press Ctrl+C and call __destruct() method.
The pcntl_signal() function won't work without this line in your file:
declare(ticks = 1);
It should probably be at the top of the file, before the class definition.
A tick is an event that occurs regularly during the program execution, and in this case each tick triggers the program to check the signals coming in. I think. I don't know why it's not enabled by default, possibly due to increased CPU load while it's in operation?
I've seen some assertions that ticks were deprecated after PHP 5.3, but I just tested in PHP 7.0.9 and can confirm that pcntl_signal() doesn't work without it.
This can be done with the Laravel SignalableCommandInterface now
class MyCommand extends Command implements SignalableCommandInterface
{
public function handleSignal(int $signal): void
{
echo "CATCH!\n";
exit;
}
// Which signals will be handled
public function getSubscribedSignals(): array
{
return [SIGINT, SIGTERM];
}
}

Aborting and resuming a Symfony Console command

I have a Symfony Console command that iterates over a potentially big collection of items and does a task with each of them. Since the collection can be big, the command can take a long time to run (hours). Once the command finishes, it displays some statistics.
I'd like to make it possible to abort the command in a nice way. Right now if I abort it (ie with ctrl+c in the CLI), there is no statistics summary and no way to output the parameters needed to resume the command. Another issue is that the command might be terminated in the middle of handling an item - it'd be better if it could only terminate in between handling items.
So is there a way to tell a command to "abort nicely as soon as possible", or have the ctrl+c command be interpreted as such?
I tried using the ConsoleEvents::TERMINATE event, though the handlers for this only get fired on command completion, not when I ctrl+c the thing. And I've not been able to find further info on making such resumable commands.
This is what worked for me. You need to call pcntl_signal_dispatch before the signal handlers are actually executed. Without it, all tasks will finish first.
<?php
use Symfony\Component\Console\Command\Command;
class YourCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
pcntl_signal(SIGTERM, [$this, 'stopCommand']);
pcntl_signal(SIGINT, [$this, 'stopCommand']);
$this->shouldStop = false;
foreach ( $this->tasks as $task )
{
pcntl_signal_dispatch();
if ( $this->shouldStop ) break;
$task->execute();
}
$this->showSomeStats($output);
}
public function stopCommand()
{
$this->shouldStop = true;
}
}
You should take a look at RabbitMqBundle's signal handling. Its execute method just links some callbacks via the pcntl_signal() function call. A common case should look pretty much like this:
<?php
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand as Command;
class YourCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
pcntl_signal(SIGTERM, array(&$this, 'stopCommand', $output));
pcntl_signal(SIGINT, array(&$this, 'stopCommand', $output));
pcntl_signal(SIGHUP, array(&$this, 'restartCommand', $output));
// The real execute method body
}
public function stopCommand(OutputInterface $output)
{
$output->writeln('Stopping');
// Do what you need to stop your process
}
public function restartCommand(OutputInterface $output)
{
$output->writeln('Restarting');
// Do what you need to restart your process
}
}
The answers are more complex than they need to be. Sure, you can register POSIX signal handlers, but if the only signals that need to be handled are basic interrupts and the like, you should just define a destructor on the Command.
class YourCommand extends Command
{
// Other code goes here.
__destruct()
{
$this->shouldStop = true;
}
}
A case where you would want to register a POSIX signal is for the SIGCONT signal, which can handle the resumption of a process that was stopped (SIGSTOP).
Another case would be where you want every signal to behave differently; for the most part, though, SIGINT and SIGTERM and a handful of others would be registered with the same "OMG THE PROCESS HAS BEEN KILLED" operation.
Aside from these examples, registering signal events is unnecessary. This is why destructors exist.
You can even extend Symfony's base Command class with a __destruct method, which would automatically provide cleanup for every command; should a particular command require additional operations, just overwrite it.

Will the same singleton instance be available after php script command line call?

I have script with defined class (for instance, Singleton.php). This class implements classic singleton pattern as in PHP manual:
class Singleton {
private static $instance;
public static function getInstance()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
public function run() {
// bunch of "thread safe" operations
} }
$inst = Singleton::getInstance();
$inst->run();
Question. If I call this script twice from command line ('php Singleton.php'), will run() method be really "thread safe"? It seems that it will not. I used to imitate single-process run via text file where some flag is stored, but it seems that there might be other cases. Your thoughts?
Singletons have nothing to do with thread-safety. They are here to only have one instance of an object per process.
so, to answer your question: no, your script is not thread safe. php will start one process (not thread) for each call on the cli. both processes will create an instance of your class and both will try to write the file.
the process to later write the file will win, and overwrite changes from the first process.
PHP is not threaded - it is process oriented. Each invocation of PHP (wether it be commandline or apache instance) is memory independent.
Your singleton will only be unique to that one process.
(oh and instead of doing $c=__CLASS__; $instance = new $c; you should use 'self' like $instance = new self();. Same result, less fuss. Also be sure to set your __construct() private/protected)
If you run this script from the command line twice (concurrently, I guess), you will get two completely distinct processes, therefore the thread safety is not an issue: there are no threads here.

Categories