3

Requirement:

I need to run a background process (per a user request) that takes about 30 to 60 seconds to complete. I'd like to give the user some status feedback. Note: Toly is right, 'Background' is not required.

What's working:

The process prints about 20 status messages during this time and I retrieve them with a proc_open and listening on a read pipe using fgets. I can save those messages into a session var and using timestamps (to help debug) I can see that the session array is getting written to with these messages as the process progresses.

The Trouble:

My plan was to poll the server with ajax calls (every sec) to retrieve these session vars for display in the DOM. The bottleneck seems to be that the server cannot service the ajax request while it's still running the background process. Everything dumps out at once when the background process completes. From what I can tell, the issue is not with output buffering because using (debugging) timestamps saved with each process message shows the server is writing to the session var sequentially, so that's how I know the proc_open and pipe reads are working as I expect. The issue appears to be the server not being able to give the AJAX request it's JSON object until it is done with the process; or, probably more accurately, done with the loop that is reading the pipe.

Obvious Misconception:

I thought sending a process to the background (using &) might give me a solution here. Apparently I do not know the difference between a background process and a forked process. What benefit is gained - if any - by running a process in the background when doing so appears to make no difference to me in this scenario?

Possible Solutions:

  1. I do not expect the user initiated process that runs this process/scenario to be that heavy, but if there's something I can build into this solution that would help a heavy load then I would like to do that now.
  2. Is this a multi-threading (pthreads) or a multi-process (fork) solution?
  3. Or, should I save a process id, let go polling it with a while( .. fgets ..) statement and then come back to the process after the server has serviced the ajax request?
  4. I suppose I could run fake status messages and then response accurately when the results come back after completion. The time to process the request is not dependent upon the user, so my fake timing could be pretty accurate. However, I would like to know what the solution would be to provide real-time feedback.
Ricalsin
  • 950
  • 9
  • 28

2 Answers2

3

After google-ing one day for a technique to get the same behavior you are describing here I come up with an easy solution for my project.

A bit of important theory: - session_start () and a set like $_SESSION["x"] = "y" will always lock the session file.

Case scenario: - A - process.php - running through an ajax call - B - get_session.php - a second ajax call;

The main problem is/was, that even if you set a $_SESSION inside a process that is being run through an AJAX it will always have to wait the for the session file to get unlocked and it will result into a sync between the two processes (A. + B.) - both finishing at the same time!

So, the easiest way to fix this matter and get a good result is by using session_write_close() after each set. E.g.:

%_SESSION["A"] = "B";
$_SESSION["x"] = "y";
session_write_close();

PS: Best approach is to have a customed set of functions to handle the sessions.

Sorry for the mark-up. I just created an stack account.

adelindev
  • 538
  • 4
  • 10
0

Why would you think that you need a background process? Also, where did you get the idea that you needed one?

A normal php script, with sufficient time out set, with flush() function used every step of the way will give you the output you need for your AJAX.

What's even easier, since you use sessions - AJAX request to a separate handler, that will just check what's in session, and if there is smth new - will return you the new part.

$_SESSION['progress'] = array();

inside process.php

$_SESSION['progress'][] = 'Done 5%';
// complete some commands
$_SESSION['progress'][] = 'Done 10%';

inside ajax.php

if(count($_SESSION['progress']) > $_GET['laststep']) {
    // echo the new messages
}

inside your normal page

$.ajax('ajax.php', 'GET', 'laststep=1', success: function(data){ show(data);})

Something like that should work.

Anatoliy Kim
  • 768
  • 4
  • 13
  • you exactly describe my approach, but I still have the problems listed. If I have a while loop which keeps me on the proc_open pipe - even though it has a sleep() - it does not seem to service the ajax request until it completes. I've been dumping the buffers (all of them) all along. You think the ajax call is handled by a 'separate handler' - meaning a separate process and therefore my thinking of creating a separate thread or process is unneeded? – Ricalsin Apr 13 '14 at 21:48
  • 'separate handler' means a separate php script. if the main process is done in process.php, have a second script, in separate file, that will handle ajax requests – Anatoliy Kim Apr 14 '14 at 04:00
  • Having a separate handler and a separate process - or another thread within a process - are all different things. `If the main process is done...` sort of sums up the trouble. In php, a sleep() is blocking unless steps are taken to address the process/thread, so your suggestion is not (realistically) feasible - it is doable but at greater explanation detail (and, in my view, not worth it). I appreciate your reply, but it does not an answer my question. – Ricalsin Apr 14 '14 at 14:12
  • As I am trying to explain to you - you do not need the sleep() function. Suppose you are performing some operations in a php script called 'process.php' - while doing these time consuming operations you can periodically update a $_SESSION variable. The requests from your client should be sent to a different php script, let's call it 'ajax_handler.php'. In 'ajax_handler.php' you can access the current value of $_SESSION variable and return the progress to the browser. – Anatoliy Kim Apr 15 '14 at 04:09
  • unless you build a PHP version that can either fork processes or run multiple threads then what you are suggesting is not possible; because just sending a request to a different script does not mean it will be (immediately) run if the interpreter is busy executing something else - hence the understanding of PHP being single-threaded OUT OF THE BOX. Please google pThreads and Zend Thread Safety (ZTS) to get a better understanding of how these things work together to actually provide multi-threaded capability in a PHP environment. – Ricalsin Apr 15 '14 at 16:42
  • A forked processes establishes a [duplicate environment](http://stackoverflow.com/questions/19068434/php-fork-process-getting-child-output-in-parent#answer-19069236). In my view (and I'd be happy to be wrong here), a process will not write any session vars (for any other process to read) until it completes execution. Alternatively, creating a connection via a pipe is not something that works like a deferred promise - where you can come back to it when there is output to be read. – Ricalsin Apr 15 '14 at 17:37
  • This is where I introduce session_write_close() to you, which frees session locks from the long-running process. You will have to reopen the session to write to it again, but there are code solutions for that: http://stackoverflow.com/questions/12315225/reopening-a-session-in-php – Anatoliy Kim Apr 15 '14 at 18:37
  • Interesting link, but I think it proves that hacking like that is not better than knowing/building a multi-threaded environment. BTW, you are introducing this as a solution to fix your original solution - no? :) – Ricalsin Apr 15 '14 at 18:52
  • No, its just an example on how to write to the session after you released the lock. Who knows - you might want to code it in OOO style with your framework of classes. Just a proof that it is possible. – Anatoliy Kim Apr 15 '14 at 19:11
  • Unless you are strained for time or require a perfect solution - just try to do it that way. If it works - then surely it will save you the trouble of building something with a more sophisticated approach? – Anatoliy Kim Apr 15 '14 at 19:15