Progress bar with PHP & Ajax

12,652

Solution 1

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.

Solution 2

@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:

  1. Read all data you will need from $_SESSION and save a copy of it.
  2. Call session_write_close to release the session lock.
  3. 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.

Share:
12,652
pawelo
Author by

pawelo

Updated on June 09, 2022

Comments

  • pawelo
    pawelo almost 2 years

    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;
    }
    
  • DaveRandom
    DaveRandom about 12 years
    I nearly said "just use session_write_close() in the post script" until I re-read the question and realised that what he is trying to do is share the progress of the lengthy operation via the session data. Which obviously, you can't do. You could try and session_write_close() and subsequently session_start() when you need to update the progress info - I have never tried this myself but I do recall seeing a question here where someone was finding this didn't work, so if it works anywhere it probably wont work everywhere. Hence You will need to come up with another way of sharing data
  • pawelo
    pawelo about 12 years
    You were rigth. I found function session_write_close() and it seems to be working! All I needed to do was to close session on the beginning of action and reopen it only for updating my session variable. Thank you very much and thanks for your advices.
  • DaveRandom
    DaveRandom about 12 years
    Beware of opening and closing the session data multiple times in the same script - I have seen people complain it doesn't work reliably, so make sure you thoroughly test anything you do come up with.
  • pawelo
    pawelo about 12 years
    Hmmm, maybe better solution is to store progress variable in Db or file. I must check it out, thanks!