0

Is it possible for nginx to trigger a php-fpm process, but then close the nginx worker and quickly return an empty page with status 200?

I have some slow php processes that need kicking off a few times a week. They can take between 3 and 4 minutes each. I trigger them with a cron manager site. The php process writes a lock file at the start, and when the process is complete an email is sent and finally the lock file is removed.

Following this guide, in my php-fpm worker pool, I have this: request_terminate_timeout = 300 and in my nginx site config I have fastcgi_read_timeout 300;

It works, but I don't care about the on-screen result. And the cron service I use has a time limit of 5 seconds, and after repeated timeouts, it disables the job.

Yes, I know I could fork a process in php, let it run in the background, and return a 200 to nginx. And yes, I could pay and upgrade my cron service. Nonetheless, it would be an interesting and useful thing to know, anyway.

So, is this possible, or does php-fpm require an open and "live" socket? I ask that because on the "increase your timeout" page referred to above, one answer says

"Lowest of three. It’s line chain. Nginx->PHP-FPM->PHP. Whoever dies first will break the chain".

In other words, does that mean that I can never "trigger" a process, but then close the nginx part of the trigger?

digitaltoast
  • 659
  • 7
  • 23

2 Answers2

2

You can.

  1. exec a php cli script by adding a trailing &, redirecting output to a log file or /dev/null, pass any parameters as json or serialized (use escapeshellarg()), the exec will return 0 immediately (no error); or
  2. use php's ignore_user_abort(), send a Connection: close header, flush any output buffers as well as a normal flush(). Put any slow code after that. You'll need to test this under Nginx.

Either way, return a 1xx code to signify acceptance but no response. And it's up you to make sure your script doesn't run forever; give it a heartbeat so it touch()es a file every so often. If the file is old and it's still running, kill it.

Walf
  • 8,535
  • 2
  • 44
  • 59
  • The first option is more reliable if you can do it that way. Otherwise there're more hints in the comments of php's connection handling docs. – Walf Aug 02 '16 at 15:59
  • Thanks @Walf - your second solution was the answer for me. Shame I can't post code here, so I'd posted it as a separate answer, acknowledging you, and have marked yours as the solution. Thanks! – digitaltoast Aug 02 '16 at 16:13
0

Thanks to @Walf's answer combined with this example from the php site, this SO answer and a little fiddling, this appears to be a solution for nginx that requires no messing with any php or nginx ini or conf files.

$start = microtime(true);
ob_end_clean();
header("Connection: close\r\n");
header('X-Accel-Buffering: no');
header("Content-Encoding: none\r\n");
ignore_user_abort(true); // optional
ob_start();
echo ('Text user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();     // Strange behaviour, will not work
flush();            // Unless both are called !
ob_end_clean();
sleep(35); // simulate something longer than default 30s timeout
$time_elapsed_secs = microtime(true) - $start;
echo $time_elapsed_secs; // you will never see this!

Or, at least, it works perfectly for what I want it to do. Thanks for the answers.

Community
  • 1
  • 1
digitaltoast
  • 659
  • 7
  • 23