Is there a possibility to receive several AJAX responses from PHP server simultaneously?

Viewed 297

I want to make a progress bar on my website, which tracks execution of a PHP script.

The PHP script makes a bunch of connections with Google API and stores the data it receives in the database. Sometimes the process can take a minute.

The PHP script is located in ajax/integrations-ajax.php file and launched by GET AJAX request sent, if on the website to click #link button. Below is jQuery code for the request:

$('#link').on('click', function () {
    var interval = setInterval(trackStatus, 1000);
    $.getJSON('ajax/integrations-ajax.php', {action: 'link'}).done(function (json) {            
        if (json.result == true) {
            showMessage('The account is successfully linked.', 'success');
        } else {
            showMessage('There is an error happened.', 'danger');
        }
    })
});

This #link button, also sets interval which fires trackStatus function each second:

function trackStatus() {
    $.getJSON('ajax/status-ajax.php', { 
        action: 'status'
    }).done(function (json) {
        console.log(json.status);
    });
}

As you can see, trackStatus function sends GET AJAX requests to ajax/status-ajax.php file and should show status in browser console every second.

To implement tracking ability on the server I made the PHP script in ajax/integrations-ajax.php file to store status in the database. Its code you can see below:

<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'link') {
    set_status_in_database(0);
    // some execution code;

    set_status_in_database(1);
    // some execution code;

    set_status_in_database(2);
    // some execution code;

    set_status_in_database(3);
    // some execution code;
    echo json_encode(['result' => true ]);
}

And created another PHP file axax/status-ajax.php which can recover the status from the database:

<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'status') {
    $return['result'] = get_status_from_database();
    echo json_encode($return);
}

But the requests appear not to be working simultaneously. I can't receive responses for trackStatus function until the response on completion ajax/integrations-ajax.php script isn't received.

I made a profiling record in browser, which show that: enter image description here

So, is there a possibility to execute requests simultaneously? Or to implement the tracking ability I need to rethink the whole approach?

Thanks in advance for help!


Update Thank you all for your advice! And especially to @Keith, because his solution is the easiest and works. I have put session_write_close() function in the beginning for the script and everything works:

<?php
if(!is_ajax_request()) { exit; }
$action = isset($_GET['action']) ? (string) $_GET['action'] : '';
if ($action == 'link') {
    session_write_close();
    set_status_in_database(0);
    // some execution code;

    set_status_in_database(1);
    // some execution code;

    set_status_in_database(2);
    // some execution code;

    set_status_in_database(3);
    // some execution code;
    echo json_encode(['result' => true ]);
}

Here you can see profiling record from a browser: enter image description here

4 Answers

While PHP can handle concurrent requests without issue, one area that does get serialized is the Session, basically PHP during a request will place an exclusive lock on the SESSION, for that user. IOW: While this lock is on, other requests from the same user will have to wait. This is normally not an issue, but if you have long running requests it will block other requests, like AJax requests etc.

As a default PHP will write session data at then end off the request,. But if you are sure you no longer need to write any session data, calling session_write_close will release the lock much sooner.

More info here -> http://php.net/manual/en/function.session-write-close.php

Would advise trying EventSource. Here is an example.

PHP

<?php
header('Content-Type: text/event-stream');
// recommended to prevent caching of event data.
header('Cache-Control: no-cache'); 

function send_message($id, $message, $progress) {
  $d = array('message' => $message , 'progress' => $progress);
  echo "id: $id" . PHP_EOL;
  echo "data: " . json_encode($d) . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

for($i=0; $i<4; $i++){
  set_status_in_database($i);
  // some execution code;
  send_message($i, "set status in database " . $i + 1 . " of 3' , $i*4);
  sleep(1);
}

send_message('CLOSE', 'Process complete');
?>

JavaScript

var es;

function startTask() {
  es = new eventSource('ajax/status-ajax.php');
  es.addEventListener('message', function(e) {
    var result = JSON.parse(e.data);
    console.log(result.message);
    if(e.lastEventId == 'CLOSE') {
      console.log('Received CLOSE closing');
      es.close();
      showMessage('The account is successfully linked.', 'success');
    } else {
      $('.progress').css("width", result.progress + '%');
    }
  });
  es.addEventListener('error', function(e) {
    console.log('Error occurred', e);
    es.close();
  });
}

function stopTask() {
  es.close();
  console.log('Interrupted');
}

$('#link').on('click', function(e) {
  e.preventDefault();
  startTask($(this));
});

Reference:

Hope that is useful for you.

Both php logic and JavaScript syntax seem to be fine; however, with the minimal amount of php code example it is assumed that it’s resource heavy. MySQL might be busy, which is why get status may wait for MySQL.

I have gone around such a problem by making the update status written to a file instead of competing for database resources.

Since you consider using a different approach, let me recommend GraphQL as a thin layer / api above your database.

There are quite a few Php-solutions out there, for example Siler. Look for one that has subscriptions (not all do), as this would be the feature you are looking for. Subscriptions are used to create a websocket (stream between your Php and Javascript), reducing all status-related communication to one call.

Yes, this may be "shooting cannons at birds", but maybe you have other things flying around, then it might be worth considering. There is a fantastic document to familiarize with the intriguing concept. You'd be able to reuse most of your database-related Php within the resolver functions.

Related