4

I have 2 PHP files. One file is caller.php and the other is worker.php

caller.php will start worker.php on the (linux) system and caller.php should be ended immediately (while worker.php is still working on the server)

The worker.php is taking a lot of time and it will write the status to a database or a file.

I want to be able to open caller.php in the browser which starts 'php worker.php', close the browser, come back in 5 minutes and check the status.. (or the script will send a mail after completion) - any ideas how to do this?

MilMike
  • 12,571
  • 15
  • 65
  • 82
  • You should really do this with an intermediate. If you plan for scale, and don't run on a cheap webhost, I can highly recommend either Gearman, and Redis also works quite awesomely :) But I suspect Gearman is the closest to your requirements. – Evert Jul 02 '12 at 18:49

3 Answers3

2

You can end client connection and still continue processing, all done. You may also want to increase php timeout if processing takes more time than php/httpd global config allows. See set_time_limit();.

This is not exactly what you asked but I think it may be X/Y problem so here we use technique that can be used to:

  • Display Processing started page to user.
  • close user connection.
  • start longer processing.

So basically this answers to your described needs instead of "how to do X?".

Caller

// Ignore user abort and set Connection close header
ignore_user_abort(true);
header("Connection: close");

// Start controlled buffering
ob_start();
echo "<h1>Processing started, come back tomorrow to see results</h1>";

// Get buffer length and manually add it to headers.
$size = ob_get_length();
header("Content-Length: $size");

// Flush buffers, user sees "Processing started" message.
ob_end_flush();
flush();

// At this point connection to client should be closed already.
// Here we start our worker, there is no need to do fork() or 
// anything like that. Client browser is not listening anymore.
// All we need to do is simply start working and start writing
// status logs to somewhere so that client can retrieve status
// with another request, let's say from checkworkerstatus.php
include "worker.php";
$worker = new worker();
$worker->start_long_processing();

Not forking or killing caller to leave worker running alone?

That's right, and that's because there's no need to.

Yes, you asked for behavior where "caller.php will start worker.php on the (linux) system and caller.php should be ended immediately (while worker.php is still working on the server)". However, there is no need to do it that way if you don't want to do multithreading-singleuser app with php. Just let users 1. start something, 2. leave it running, 3. disconnect users, 4. go back to start where users can start more something or quit if bored enough already.

Of course you can replace everything after flush(); with anything, difference is that clients does not anymore listen so that all output goes to black hole named /dev/null. For example test it with something like this:

...end_flush();
flush();

// At this point connection to client should be closed already.
echo "Nobody sees this message";
while(rand(0,10) != 1) {
    echo "Nobody sees this message";
    error_log(date("mdyHis")." Still running...");
    sleep(10);
}
...

See http://php.net/features.connection-handling for more information.
Also read http://php.net/function.flush if there seems to be any problems with flushing buffer and closing user connection.
There is also another question (with answers) about Why does PHP not support multithreading?

Community
  • 1
  • 1
1
  1. Caller runs worker
  2. Worker creates FIFO file in /tmp/ and writes to it about progress (you can use also sockets)
  3. Caller reads this file and shows user actual progress

If you'll configure Apache/nginx good it will allow executing two PHP files in the same while (like mine).

You should read about posix_mkfifo: http://php.net/manual/en/function.posix-mkfifo.php (there are also examples of usage).

djmati11
  • 597
  • 5
  • 18
1

this can be a bit strong but is the quickest and direct way:

  • caller create a html with <IFRAME>
  • SRC of IFRAME is the worker (here caller pass params to worker as GET-params)
  • first lines of worker are in the code below

Code:

ignore_user_abort(true);
set_time_limit(0);

when you close the browser worker (should) countinue to work!

see http://www.php.net/manual/en/function.ignore-user-abort.php

Ivan Buttinoni
  • 4,110
  • 1
  • 24
  • 44