0

Edit: See my answer to this post for the solution to this particular problem; I also removed parts of the question which were no longer relevant to the solution.

I'm new to PHP and development in general and I'm asking, because I haven't found a working solution for my problem yet.

As part of a project I am trying to run a batch script. The batch script creates some analysis files and crucially compiles and starts 2 executables I need to keep running for later.

To start the script, I compose a call $cmd where I set 2 environment variables and the working directory and add some necessary parameters that the script needs in order to start.

I then run

exec($cmd);

This call produces all files in the working directory, and starts the processes, however the batch script now waits for the executable processes to finish, as it cleans up some temporary files after the programs are finished. At this point the PHP program stops executing and waits for the batch script to finish, which it doesn't do.

Over the course of the last 2 days I have tried multiple solutions proposed here

or

exec($cmd.' > NUL 2>&1 &')

and

pclose(popen("start /B " . $cmd . " 1> ".$scrptDir." 2>&1 &", "r"));

where $scrptDir is the working directory I described earlier.

I have not found a working solution, at best the script doesn't start at all and at worst it produces a syntax error when making an AJAX call, which admittedly I have not yet understood why exactly it does that, as AJAX was implemented by a predecessor and left pretty undocumented.

Edit: The syntax errors were caused by myself, because I forgot the semicolon.

This is the PHP code:

    public function runScript() {
        //-- compose the command line to start script 
        $cmd = $this->getCall();

        //-- execute the script
        exec($cmd);        
      
    }
} 

Updates: Starting the script via

pclose(popen("start /B " . $cmd . " 1> NUL 2>&1 &", "r"));

or

popen("start /B " . $cmd . " 1> NUL 2>&1 &", "r");

or

exec('nohup ' . $cmd . ' 2>&1 &');

has the same outcome: the script runs, but the executables close and the script performs the cleanup.

Using

exec($cmd.' > NUL 2>&1 &');

does not have different behaviour from

exec($cmd);
gravy
  • 11
  • 5
  • Hi, please fix the formatting in your question. – S. C. Jan 01 '23 at 17:16
  • While working this out, I'd recommend testing with a dummy script first, to see how these options work. For further help, you may want to update your post with the exact results you get with each option. – Markus AO Jan 01 '23 at 17:32
  • In order to assist you to fix a specific issue with your programming code, we expect that you submit everything we need to reproduce that issue in the same type of environment. – Compo Jan 01 '23 at 21:17
  • @Compo Sorry, I added the code I use to execute the script now. Is it possible, to start the runScript() function asynchroniously? – gravy Jan 01 '23 at 22:25

2 Answers2

1

This is one way to do it. Please be advised that this is not the best way to go about it, especially if you need to do something with the results of an AJAX call.

jQuery

Let's say that you initiate the AJAX request like this:

$(document).ready(function() {
    $.ajax({
        type: 'POST',
        url: 'myscript.php',
        data: {'val1':val1,'val2':val2,....},
        timeout: 100,
        success: function(data) {
            // whatever it is you need to do on success, although you could leave it empty
        },
        error: function (xmlhttprequest, textstatus, message, desc, err) {
            if(textstatus === "timeout") {
                // do something else
            } else {
                // cover the case of an error which is not a timeout
            }
        }
    });
});

The key part here is the timeout. This will abort the pending AJAX, meaning that the JS will not wait for the response, but the server-side script called with it will still execute.


Vanilla Javascript

If you're not using jQuery, you could do the same thing in vanilla Javascript, like this.

var xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "myscript.php", true);

// Post the request with your data
xmlHttp.send(
    JSON.stringify({
        val1 : val1,
        val2 : val2,
        ... etc
    })
);

xmlHttp.onload = function(e) {
    // Check if the request was a success
    if (this.readyState === XMLHttpRequest.DONE && (this.status === 201 || this.staus === 200)) {
        clearTimeout(xmlHttpTimeout); 
        // do whatever it is you do on successful response
    }
}

// Timeout to abort in 100 miliseconds
var xmlHttpTimeout = setTimeout(ajaxTimeout,100);
function ajaxTimeout(){
   xmlHttp.abort();
   // do what you would normally do once initiating the AJAX
}

If you're unsure whether the AJAX request is reaching the backend, and there are no console errors or network errors, you can do the following.

First, create a script called test.php (or give it another name, if you already have a script called like that). Then, inside that script, place the following.

<?php

sleep(15);
$filename = "zztest.txt";
$file = fopen($filename,'a+');

fwrite($file, date("Y-m-d H:i:s"));
sleep(5);
fwrite($file, PHP_EOL.date("Y-m-d H:i:s").PHP_EOL."********".PHP_EOL);
fclose($file);

?>

What this will do is:

  1. Upon initialization, wait for 15 seconds (that's what sleep(15) does)
  2. Create (or open, if it already exists) a *.txt file, and write the current date and time to it
  3. Keep the file open - notice that there is no fclose(...) after fwrite(...). This is generally not good practice, because something might break the script execution, and the file would remain locked, but it's alright in this case, because it's for testing purposes
  4. Once the date and time have been written to the text file, wait for another 5 seconds (sleep(5)), and then write the current date and time to it. PHP_EOL is the newline character
  5. Finally, close the file

The next step is to replace myscript.php call inside your AJAX, so that it calls test.php. The rest of your AJAX setup can remain the same.

Then, execute AJAX as if you would normally. Wait for about 20 seconds, and check if a file called zztest.txt has been created, and if it has, what its contents are.

If it hasn't, then the problem is in the front-end (the AJAX call).

FiddlingAway
  • 1,598
  • 3
  • 14
  • 30
  • I tried using the vanilla javascript you posted and the request goes through and starts the script, then the timeout triggers. However I still can't execute any PHP code, it still waits for the script to end. – gravy Jan 02 '23 at 09:56
  • Please see the updated answer. I've added a way for you to test if the AJAX is working. If it does (what I've explained in the update actually occurs), then the problem might be in your original PHP script. If the code in your original PHP script needs to continue after calling the batch script, you might consider splitting the PHP code in two separate files. This, however, might not work for you, if the PHP code below the `exec($cmd)` relies on its results. – FiddlingAway Jan 02 '23 at 10:14
  • The script created the text file and wrote:" 2023-01-02 11:26:15 2023-01-02 11:26:20 ******** " into it, so I guess the problem lies within the PHP code. – gravy Jan 02 '23 at 10:29
  • Here's something else to try - at the top of your original PHP script, add these two lines of code: `error_reporting(E_ALL); ini_set("display_errors",1);` These will enable error logging. If you're including or requiring any additional files on your PHP script (at the very top), place the two lines beneath them. Also, for testing purposes, temporarily comment out the line of code which is calling the batch script. This is to ensure that it won't run, since you're just looking for errors now. If the problem persists, I suggest opening another question on SO. – FiddlingAway Jan 02 '23 at 10:52
  • Where would these errors be displayed? I am using XAMPP. – gravy Jan 02 '23 at 14:20
  • They should show up in the AJAX response - you can open the Networking section of the developer tools by pressing CTRL + SHIFT + P (Chrome) or CTRL + SHIFT + E (Firefox). After opening the Networking section, trigger your AJAX, and watch for it to show up. When you click on it, a side pane will open up on the right. Look for the Response tab in it, and check the contents. – FiddlingAway Jan 02 '23 at 14:25
1

Ok, I found a workaround.

Instead of

exec($cmd)

I used

$filename = "help.bat";
$file = fopen($filename,'a+');
fwrite($file, $cmd);
fclose($file);
pclose(popen("start /B ". $filename, "r"));

I wrote the composed command $cmd into a batch file and then executed that batch file instead. I think the reason why it is working now is that my composed call was a bit too complex for me, so I tried to make it simpler.

gravy
  • 11
  • 5