如何在使用 Symfony Process 组件的请求期间异步启动 Symfony 命令?

How to launch a Symfony Command asynchronously during a request using the Symfony Process component?

我正在尝试使用 Symfony Process 组件执行 Symfony 命令,以便它在收到 API 请求时异步执行。

当我这样做时,我收到 Code: 127(Command not found) 的错误消息,但是当我从我的控制台手动 运行 它时,它就像一个魅力。

这是电话:

public function asyncTriggerExportWitnesses(): bool
{
    $process = new Process(['php /var/www/bin/console app:excel:witness']);
    $process->setTimeout(600);
    $process->setOptions(['create_new_console' => true]);
    $process->start();
    $this->logInfo('pid witness export: ' . $process->getPid());
    if (!$process->isSuccessful()) {
        $this->logError('async witness export failed: ' . $process->getErrorOutput());
        throw new ProcessFailedException($process);
    }

    return true;
}

这是我得到的错误:

The command \"'php /var/www/bin/console app:excel:witness'\" failed.
Exit Code: 127(Command not found)
Working directory: /var/www/public
Output:================
Error Output:================
sh: exec: line 1: php /var/www/bin/console app:excel:witness: not found

我对 Process 组件的使用有什么问题?

这样调用也不行:

$process = new Process(['/usr/local/bin/php', '/var/www/bin/console', 'app:excel:witness']);

这会导致以下错误:

The command \"'/usr/local/bin/php' '/var/www/bin/console' 'app:excel:witness'\" failed.
Exit Code: ()
Working directory: /var/www/public
Output:
================
Error Output:
================

首先,请注意 Process 组件并不意味着 运行 在父进程结束后 异步 。因此,在 API 请求期间将异步作业触发到 运行 不是一个好的用例。

这两条关于 运行 异步处理的评论 in the docs 非常中肯:

If a Response is sent before a child process had a chance to complete, the server process will be killed (depending on your OS). It means that your task will be stopped right away. Running an asynchronous process is not the same as running a process that survives its parent process.

If you want your process to survive the request/response cycle, you can take advantage of the kernel.terminate event, and run your command synchronously inside this event. Be aware that kernel.terminate is called only if you use PHP-FPM.

Beware also that if you do that, the said PHP-FPM process will not be available to serve any new request until the subprocess is finished. This means you can quickly block your FPM pool if you’re not careful enough. That is why it’s generally way better not to do any fancy things even after the request is sent, but to use a job queue instead.

如果您想 运行 异步作业,只需将作业存储在“某处”(e.d 数据库、redis、文本文件等),并让解耦的消费者通过“待处理作业”并执行您需要的任何内容,而无需在 API 请求中触发作业。

上面的内容很容易实现,但您也可以使用 Symfony Messenger 来完成。根据您的 API 请求发送消息,使用您的作业队列消费者使用消息。


综上所述,您对进程的使用也失败了,因为您正在尝试混合使用同步和异步方法。

您第二次尝试调用该命令至少成功找到了可执行文件,但由于您在作业完成之前调用了 isSuccessful()

如果你使用start()(而不是run()),你不能简单地直接调用isSuccessful(),因为工作还没有完成。

以下是执行异步作业的方法(不过,这在 API 请求期间很少有用):

class ProcessCommand extends Command
{

    protected static $defaultName = 'process_bg';

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $phpBinaryFinder = new PhpExecutableFinder();

        $pr = new Process([$phpBinaryFinder->find(), 'bin/console', 'bg']);
        $pr->setWorkingDirectory(__DIR__ . '/../..');

        $pr->start();

        while ($pr->isRunning()) {
            $output->write('.');
        }
        $output->writeln('');

        if ( ! $pr->isSuccessful()) {
            $output->writeln('Error!!!');

            return self::FAILURE;
        }

        $output->writeln('Job finished');

        return self::SUCCESS;
    }

}