3

I have an automatic synchronization that send a CURL Request to an equipment and I do this request for every equipment that I have (like 60). The problem is:
- If the communication succeed everything works fine.
- But if the communication failed the web page will wait until the timeout is gone. So the client side crashes for 3-4 minutes... I have important grids that stop loading the data in that 3-4 minutes.

The automatic synchronization is a function in javascript that do an AJAX Request to call php controller below. How can I prevent this? I don't know what else to try... The AJAX is async , so I don't get it why the webpage stops.

Controller:

$list = $panels_repository->getNetwork();

            $thread = new PollingThread($list);
            $thread->start();
            $thread->join();

            $result = $thread->result;
            $resultLength = sizeof($result);

            //...

Thread:

class PollingThread extends Thread {
private $panels_list;
private $alarm_status;
public $result;

public function __construct($list) {
    $this->panels_list = $list;
}

public function run() {
    $panels_list = $this->panels_list;

    $alarmsUpdated = array();
    $panels = array();

    foreach($panels_list as $panel) {
        $alarms_list = $panel->getAlarmsList();

        //Get updated alarms status
        $panel->getDiagnosticStatus($alarms_list);

        //Save the results
        array_push($alarmsUpdated, $alarms_list);
    }

    $this->result = $alarmsUpdated;

}  

getDiagnosticStatus

$input = "<?xml version='1.0' encoding='ISO-8859-1'?>
                <?getParameters message?>
                <displayMLRequest xmlns='http://www.peek.se/DisplayML/' version='1.12'
                                  dateTime='2008-01-10T15:09:51+02:00'>
                    <getParameters/>
                </displayMLRequest>";

    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, $url); //Set IP to communicate

    //Set POST XML Input
    curl_setopt( $ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
    curl_setopt($ch, CURLOPT_POSTFIELDS, $input);

    //Return response as string & TimeOuts
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

    //Execute
    $output = curl_exec($ch); 

    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    curl_close($ch);  

Javascript:

    $('#stations_tree').on('changed.jstree', function (e, data) {
    //....

    var dataObject = {
        type: "Selected",
        childrenID: childrenID_array,
        parentsID: parentsID_array
    };

    $.ajax({
        type: "POST",
        url: "controllers/PanelsController/",
        data: dataObject,
        cache: false,
        success: function ()
        {
            $("#dg_selected_stops").jsGrid("loadData");
            $("#dg_selected_pids").jsGrid("loadData");
        }
    });

}).jstree({
    plugins: ["checkbox", "state", "types"],
    "types": {
        "default": {
            "icon" : false
        }
    },
    core: {
        data: {
            url: "json/stations.json",
            dataType: "json",
            success: function () {
                //Save Panels Network as session variable
                var dataObject = {
                    type: "Network"
                };

                $.ajax({
                    type: "POST",
                    url: "controllers/PanelsController/",
                    data: dataObject,
                    cache: false,
                    success: function() {
                        //Get EquipmentStatus for each panel
                        var dataType = {
                            type: "Save"
                        };

                        $.ajax({
                            type: "POST",
                            url: "controllers/EquipmentStatusController/",
                            data: dataType,
                            cache: false,
                            success: function () {
                                //Status Unknown
                                changeTreeIcons();
                                loadAlarmsData();

                                polling();
                            }
                        });   
                    }
                });
            }
        }
    }
});

Function polling() - Where the JS stops until timeout is gone

function polling() {
var dataObject = {
    type: "Polling",
};

$.ajax({
    type: 'POST',
    url: "controllers/DatabaseController/",
    data: dataObject,
    success: function(response) {
        //loadAlarmsData();
        //changeTreeIcons();
    }
});

}

EDIT: I have checked that if I try to do an AJAX Request after the polling as began, the webpage only do that request after php script is done. So the JS that blocks are tables that loadData with ajax requests. How can I resolve this?

BackSpace
  • 45
  • 6
  • Are any errors being thrown on these bad CURL requests? – The One and Only ChemistryBlob Aug 30 '16 at 15:01
  • @TheOneandOnlyChemistryBlob No, If I had echo "test" in getDiagnosticStatus the web prints 6-7 times. If I do only one curl request in a file test the error log is: * Hostname 172.18.56.132 was found in DNS cache * Trying 172.18.56.132... * Connection timed out after 10000 milliseconds * Closing connection 0 – BackSpace Aug 30 '16 at 15:20
  • 2
    if the problem is with the webpage stopping then post the javascript, not the php – Robert Parham Aug 30 '16 at 20:25
  • Consider also not making 60 requests on a single page, but combine into a single larger request / response. – random_user_name Aug 30 '16 at 21:35
  • Post edited, now with javascript. @cale_b The problem is that I use an interface that contains the method getDiagnosticStatus. So every panel is implementing that interface, because each panel have a different IP to comunicate. How can I combine all? Imagine that first panel communicates with success, but the second don't. How will I handle that with the single larger request? – BackSpace Aug 31 '16 at 08:46

1 Answers1

2

It is not complete solution for copy/paste, but it can give you idea how to solve your problem. You can try next (php-fpm only).

On frontend side run synchronization using js and call your controller, on client side in controller send response for browser and call fastcgi_finish_request() before your CURL operation will start. This function flushes all response data to the client and finishes the request, but PHP script continue job.

...

$list = $panels_repository->getNetwork();

$key = 'my_unique_operation_key'; // it key need you for get data on client side
$resp = [
   'status'=>'start',
   'operation_key' => $key
];
echo json_encode($resp);
fastcgi_finish_request(); // close connection and continue ...

$thread = new PollingThread($list, $key); // send $key also
...

In PollingThread:

...
foreach($panels_list as $panel) {
    $alarms_list = $panel->getAlarmsList();

    //Get updated alarms status
    $panel->getDiagnosticStatus($alarms_list);

    //Save the results
    array_push($alarmsUpdated, $alarms_list);

    ...
    // save operation progress for example in memcache
    $progressData = some data about progress and $alarmsUpdated, etc...
    $memcache_obj->set('operation_'.$key, json_encode($progressData));
}

Somewhere In Controller add action which returns data parts:

function getDataPartially_action(){
  $key = $_GET['key']; // not forget validate
  ...
  $jsonData = $memcache_obj->get('operation_'.$key);  // get current state from memcache by key
  echo $jsonData;
  exit();
}

On frontend side:

// call controller and start operation
$.ajax({
  url: '/controller/uri/here',
  beforeSend: function() {
    // here you can place spinner or progress bar ...
  },
  success: function(json) {
    // in json you get progress key after fastcgi_finish_request()
    // and run visual progress
    getDataPartially(key);
  }
});

// load data partially
function getDataPartially(key){
  var timerId = setInterval(function() {
    $.ajax({
      url: '/controller/uri/here/getDataPartially_action?key=' + key,      
      success: function(json) {
        // in json you have data for grids and progress info
        // if json contains finish info stop progress
        clearInterval(timerId);
        // hide progress bar and etc ...
    }
});}, 2000);
}

p.s. In CURL function you can use CURLOPT_PROGRESSFUNCTION and get more info about progress:

$ch = curl_init();
    curl_setopt ...
    curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, 'curl_progress_callback');
    ...
// where curl_progress_callback is:

    function curl_progress_callback($dltotal, $dlnow, $ultotal, $ulnow){
        $curlInfo = curl_getinfo($ch); // a lot info about connection
        echo $curlInfo['connect_time'];
        echo $curlInfo['http_code'] ...      
    }
Alex K
  • 2,613
  • 1
  • 20
  • 31
  • Thanks for the help! I am analyzing your answer. Your point is to use fastcgi_finish_request() , so the client side don't stop. And after that read information with getDataPartially? My doubt is with the url, where am I supposed to put getDataPartially? Sorry man, a little newbie with that :/ – BackSpace Aug 31 '16 at 08:50
  • @BackSpace, right, you need read information with getDataPartially. For implement that you need to add action in Controller and call this action url in getDataPartially() function. I updated my answer. – Alex K Aug 31 '16 at 09:14
  • At the moment I am trying to implementing your answer. I updated my question with the javascript code, could you look at it pls? I think that the problem is in php, but I don't know. – BackSpace Aug 31 '16 at 09:39
  • Your polling() function is not polling, it is regular single ajax request. Polling working in cycle, see my getDataPartially() function. It running every 2 seconds and get data updated by getDiagnosticStatus. On php side you can answer with $progressData['status'] = 'finish' and in js side if 'finish' exists in responce call clearInterval(timerId); it stop polling requests. More polling examples: http://stackoverflow.com/questions/6835835/jquery-simple-polling-example – Alex K Aug 31 '16 at 09:58
  • Where did I save the variable memcache_obj? When the getDataPartially action is called the php return an error because memcache_obj is not defined – BackSpace Aug 31 '16 at 10:31
  • I gave you short version of code, you need memcached installed and initialized. See here, it's very simple: http://php.net/manual/en/memcached.set.php – Alex K Aug 31 '16 at 10:39
  • It's being hard... Sorry for all the doubts. I am using WAMP Server, I saw some posts about enable memcached. But not only one about php-fpm and I tried run the function fastcgi_finish_request and WAMP don't have it. You think that I can implement this in WAMP? – BackSpace Aug 31 '16 at 11:06
  • php-fpm on WAMP is a problem... You can read how to keep PHP working after closing connection without fastcgi_finish_request() : http://stackoverflow.com/questions/138374/close-a-connection-early – Alex K Aug 31 '16 at 11:21
  • I tried that solution and the php returns an answer to JS and the AJAX Request finishes. But the PHP script that keeps running is blocking the webpage... The loadData of grids are calling Controllers that go to sessions variables get info, is that the problem? The controller that do the polling, save the result in some session variables. My hope is gone... – BackSpace Aug 31 '16 at 11:49
  • Have you tried http://php.net/manual/en/function.register-shutdown-function.php ? – Alex K Aug 31 '16 at 13:17
  • Yes :/ Same error: "Fatal error: Maximum execution time of 120 seconds exceeded" – BackSpace Aug 31 '16 at 14:08
  • You've clearly put a lot of work into helping OP. Thank you! – random_user_name Aug 31 '16 at 19:34
  • http://stackoverflow.com/questions/6346785/curl-causing-page-to-hang With that I think I can't solve the problem the way I needed... – BackSpace Sep 02 '16 at 09:05