Progress during AjaX call displays only initial and final value - php

I need to execute an AjaX call to a PHP script with a long execution time. My goal is to display a progress status of this execution.
The idea is to create an AjaX call to periodically ask the server about the status of the execution. The progress status is stored into $_SESSION['progress'], initially set to 0 and changed from script during execution.
Here's my code on client and server side.
Client-side
// invoke the script (ie. with button)
$('#start').click(function()
{
$.ajax
({
url: "long-script.php"
});
});
// check progress periodically
setInterval(progress, 100);
function progress()
{
$.ajax
({
dataType: "json",
url: "progress.php",
success: function(data)
{
console.log(data);
}
});
}
long-script.php
// just an example to emulate execution
sleep(1);
$_SESSION['progress']=30;
sleep(1);
$_SESSION['progress']=70;
sleep(1);
$_SESSION['progress']=100;
progress.php
header('Content-Type: application/json');
echo json_encode($_SESSION['progress']);
The problem is that console.log() in progress function outputs 0 before the script execution, stops outputing data during the execution, and finally outputs 100 when the script is terminated. What am I missing?

The problem is that the session is not written, until the script ends, or the session closes.
You need to remember that sessions, by default in php are stored as files on the system and is locked in run-time.
What you can do is change the long-script.php file a bit.
session_start();
sleep(1);
$_SESSION['progress']=30;
session_write_close();
sleep(1);
session_start();
$_SESSION['progress']=70;
session_write_close();
sleep(1);
session_start();
$_SESSION['progress']=100;
session_write_close();
The idea is to write to the session after every progress changes.
Then you will need to start the session again.
This may be a wrong way to do this, but you can always look up the session functions in php. Take a look at this session_write_cloe

Related

Ajax call for long PHP task

I need to execute a long (10-60 sec) PHP task from Ajax. Furthermore, the calling page needs to close the connection and go to a new page, with the PHP task finishing in the background.
At first I tried this scenario:
$.ajax({
url: '/ff.php',
type: 'POST'
}).success(function(response) {
console.log("... returned");
});
window.top.location.href = "http://yahoo.com";
But that never executes ff.php as I thought it should. Somehow it goes to the new page before calling the Ajax... or the connection is closed due to the new page, before it executes. This, despite the precautions I thought I needed in ff.php:
ignore_user_abort(true);
ob_end_clean();
header("Connection: close");
ignore_user_abort();
ob_start();
echo ('Text the user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
// Do processing here
session_write_close();
sleep(30);
require_once($_SERVER['DOCUMENT_ROOT'].'/ajax/lib/sendPush.php');
I've seen numerous posts that that top block of code is what's needed to have the PHP return control to the caller immediately but I can't get it to work in this scenario -- I suspect it's because I'm moving to a new page. That last require statement sends a push notification to my app on my iPhone, which woks just fine. I use this to know that my PHP script has been executed and it will part of my production environment when this works.
I have also tried moving the new page URL into the Ajax success block as follows, since I thought the PHP had all the code necessary to return immediately.
$.ajax({
url: '/ff.php',
type: 'POST'
}).success(function(response) {
console.log("... returned");
window.top.location.href = "http://yahoo.com";
});
But in that case, it takes the full 30 secs before going to the next page.
The desired results are: execute the PHP, receive the push right away, and the page goes to the next page quickly. But so far I either get the next page immediately, with NO execution of ff.php or, it does execute but the browser waits the full 30 secs to go to the next page.
UPDATE: As one commenter said below "basically if you redirect before the response then your ajax call will be cancelled." ... so my follow up question is this... is there no way to know when the Ajax has been received and started execution, so I can do the redirect at that time? I looked through the Ajax events and they all seemed to be related exclusively to when execution is complete. Does this imply there is no way to handle a long ajax call via jQuery? We have to use a queue?
Try something like this:
$.ajax({
url: '/ff.php',
type: 'POST'
}).success(function(response) {
console.log("... returned");
});
$('#status').text("I'm redirecting you to a new page...");
setTimeout(function(){window.top.location.href = "http://yahoo.com";},1000); //Put some delay between ajax and redirect
Redirect after success:
$.ajax({
url: '/ff.php',
type: 'POST',
success: function(response) {
console.log("Returned: " + response);
window.top.location.href = "http://yahoo.com";
}
});
Calling jQuery AJAX is async method, so either wait for success event, or use async: false.
And as quoted in this answer, first output content, than close the connection in PHP:
ignore_user_abort(true);
set_time_limit(0);
ob_start();
echo ('Text the user will see');
$size = ob_get_length();
header("Connection: close");
header("Content-Length: $size");
ob_end_flush();
ob_flush();
flush();
session_write_close();
// Do processing below:
Basically if you redirect before the response then your ajax call will be cancelled.
You should look for alternatives for example you can call the ajax request just to schedule the long running tasks.
Even if you find a way to hack this and to make it work, It would not be a good idea, you will never know the request was successful.
So just use cron task if you running linux, or Task Scheduler for Windows Server
According to documentation of ignore_user_abort you should pass true as parameter so i'll be run as long as you want.
edit: i didn't notice it's already there.
Once called PHP function through AJAX, should run until end processing regardles of user abort (or close AJAX connection).
Of course you need also set set_time_limit to extend execution time of script.

Multiple ajax requests at the same time

I have a form that is validated then submitted with the following handler :
submitHandler:function (form) {
$.ajax({
url: 'long_process.php',
type: 'POST',
data: $(form).serialize(),
success: function (result) {
// ... Redirect ...
}
});
//start polling
(function poll() {
setTimeout(function () {
$.ajax({
url: "get_progress.php",
success: function (data) {
console.log(data);
//Setup the next poll recursively
poll();
},
});
}, 3000);
})();
}
long_process.php takes about 30s to finish and in the meantime I'd like to track the progress via get_progress.php which echo the percentage of processing done.
When launching this script I get in the console (edited):
1 POST long_process.php
2 GET get_process.php (3 seconds later)...
...stuck here until long_process.php finishes THEN
3 GET get_process.php (3 seconds later)...
4 GET get_process.php (3 seconds later)...
...
but none of the get_progress.php return any values until the long_process.php is finished.
How can I achieve multiple simultaneous ajax request ? Ultimately this will be used to display a progress bar.
If you are using sessions in your PHP then the one call will block the other, because PHP won't allow two requests to use the same session space at the same time. Your two requests are probably firing, but you are not getting a request to the second until the server finishes with the first.
To solve this:
Option 1: Don't use sessions in the first script that you are calling or, if you must, then unlock the session using session_write_close(). After calling this, of course, you can't write any session variables.
Option 2: If reading and writing session variables is essential then don't use session variables in the second AJAX call and don't declare a session start. Have your first script put it's status somewhere else for you to read (in a DB, a file on /tmp) and then have the second script read the status from there.
Hope that helps!

One ajax call block other ajax call

Once my page is loaded, I perform an Ajax call to a php script, which updates my server. However this script can sometimes take over a minute to complete, and while the script is running, I am unable to perform other Ajax calls, which I need to handle - i.e the first Ajax call should not interrupt the other Ajax calls. Any idea how to do this?
First Ajax call:
$(document).ready(function () {
$.ajax({
url: "checkForUpdatesByCoach.php",
success: function(arg){
if(arg == "200"){
$('body').prepend("<div style='text-align:center;margin-bottom:-12px;' onClick='location.reload()' class='alert alert-success'>Dine hold er blevet opdateret.Tryk for at opdatere!</div>").fadeIn("slow");
}
}
});
});
Second Ajax call (a user triggered call):
$.ajax({
type: "POST",
data: {data:dataAjax},
url: "updateSwimmer1.php",
success: function(arg){
//updating UI
}
});
adeno's comment above is correct.
"in PHP only one script at a time can operate on the same session, so
as to not overwrite session data etc. So when doing two ajax calls to
PHP scripts within the same session, the second has to wait for the
first to finish"
to help speed things up you can write to and end a session early(session_write_close()) to release the session-lock and allow another script using the session to continue.
note: you can still read from your $_SESSION variable after calling session_write_close but you may no longer write to it.
you can find a good example of this here: PHP Session Locks – How to Prevent Blocking Requests
example provided from the link above:
<?php
// start the session
session_start();
// I can read/write to session
$_SESSION['latestRequestTime'] = time();
// close the session
session_write_close();
// now do my long-running code.
// still able to read from session, but not write
$twitterId = $_SESSION['twitterId'];
// dang Twitter can be slow, good thing my other Ajax calls
// aren't waiting for this to complete
$twitterFeed = fetchTwitterFeed($twitterId);
echo json_encode($twitterFeed);
?>

Jquery PHP without response function

I Have a PHP script which need to be run in background and with the help of
ignore_user_abort(true);
Script can be run even close the browser.
But I don't want to close browser every time,
$.ajax({
type: 'POST',
url: 'myphp.php',
data: values,
success: function(re) {alert("somthing");}
});
browser always wait for AJAX response even without mentioning of success:
Is there any way to stop browser waiting time, so that user can browse website normally without waiting for finishing of php script.
handle request with
jQuery XHR
var xmlHttpRequest = $.ajax( {
//....
});
xmlHttpRequest.abort();
At the beginning of your PHP code try to put this :
ob_start();
ob_end_flush();
That will send content to your ajax script and stop it.
I didn't try so it's just an idea ;)
You could try
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
though apparently whether it works depends on the exact PHP version. Or you could just terminate the AJAX call from the client side after a while (possibly after you start receiving content).
That said, the essence of AJAX calls is that "user can browse website normally without waiting" even while they are running, so I'm not sure there is a point to what you are trying to do.

Progress bar with PHP & Ajax

I am working on progress bar which updates progress using ajax requests and session variables. When my program performs time consuming operation such as sending many emails etc. it just set proper session variable (which contains progress value). This operation is started by function post() in code below.
In the meantime, the second function ask() is performed in loop every 500ms. It should return current progress in real time. And here is the problem: every request sent by ask() are waiting for request sent by post() function is finished. The funny part is that if I set some URL like google.com instead of url/to/progress it works just fine except that it is not what I want :). Which means that problem is on the server side.
Not sure if it's important but I use Yii Framework.
All the code below is only scratch (but working) and its only purpose is to show what I meant.
Thanks in advance.
Sorry for my poor english :)
View part:
<script type="text/javascript">
function ask() {
var d = new Date();
var time = d.getTime();
$.ajax({
type: 'get',
url: '/url/to/progress' + '?time=' + time,
success: function(data) {
$("#progress").html(data);
}
})
}
function post() {
var d = new Date();
var time = d.getTime();
$.ajax({
type: 'post',
url: '/url/to/post' + '?time=' + time,
data: {"some": "data"},
success: function(data) {alert(data)}
});
}
$("#test").click(
function() {
post();
var progress = setInterval("ask();", 500);
}
);
</script>
Controller part:
public function actionPost($time) {
sleep(5); // time consuming operation
echo $time . ' : ' . microtime();
exit;
}
public function actionProgress($time) {
echo $time . ' : ' . microtime();
exit;
}
I think your problem here is session related.
When a script has an open session, it has a lock on the session file. This means that any subsequent requests which use the same session ID will be queued until the first script has released it's lock on the session file. You can force this with session_write_close() - but this won't really help you here, as you are trying to share the progress info with the session file so post script would need to keep the session data open and writable.
You will need to come up with another way of sharing data between the post and progress scripts - if post has the session data open throughout it's execution, progress will never be able to access the session data until after post has finished executing. Maybe you could use the session ID to create a temporary file which post has write access to, in which you put the progress indicator data. The progress can check the file and return that data. There are many options for IPC (inter-process communication) - this is not a particularly beautiful one but it does have the advantage of maximum portability.
As a side note - please don't pass strings to setInterval(), pass functions. So your line should actually read:
var progress = setInterval(ask, 500);
But - it would be better to use setTimeout() in the success/error handlers of the ask() ajax function. This is because by using setInterval(), a new request will be initiated regardless of the state of the previous one. It would be more efficient to wait until the previous request has finished before initiating the next one. So I would do something more like this:
<script type="text/javascript">
// We'll set this to true when the initail POST request is complete, so we
// can easily know when to stop polling the server for progress updates
var postComplete = false;
var ask = function() {
var time = new Date().getTime();
$.ajax({
type: 'get',
url: '/url/to/progress' + '?time=' + time,
success: function(data) {
$("#progress").html(data);
if (!postComplete)
setTimeout(ask, 500);
}
},
error: function() {
// We need an error handler as well, to ensure another attempt gets scheduled
if (!postComplete)
setTimeout(ask, 500);
}
}
});
}
$("#test").click(function() {
// Since you only ever call post() once, you don't need a seperate function.
// You can just put all the post() code here.
var time = new Date().getTime();
$.ajax({
type: 'post',
url: '/url/to/post' + '?time=' + time,
data: {
"some": "data"
},
success: function(data) {
postComplete = true;
alert(data);
}
error: function() {
postComplete = true;
}
});
if (!postComplete)
setTimeout(ask, 500);
}
});
</script>
...although this still doesn't fix the session problem.
#DaveRandom above correctly points out that you are a victim of a session storage lock.
The workaround is quite simple. You want to make the script that processes post() release the lock on the session data so that the script that handles ask() can access this session data. You can do that with session_write_close.
The fine print here is that after calling session_write_close you will not have access to session variables, so you need to structure the script for post accordingly:
Read all data you will need from $_SESSION and save a copy of it.
Call session_write_close to release the session lock.
Continue your lengthy operation. If you need session data, get it out of your own copy and not $_SESSION directly.
Alternatively, you can toggle the lock on the session multiple times during the script's lifetime:
session_start();
$_SESSION['name'] = 'Jon';
// Quick operation that requires session data
echo 'Hello '.$_SESSION['name'];
// Release session lock
session_write_close();
// Long operation that does not require session data.
sleep(10);
// Need access to session again
session_start();
echo 'Hello again '.$_SESSION['name'];
This arrangement makes it so that while the script it sleeping, other scripts can access the session data without problems.

Categories