0

I am getting the error "exceeded maximum execution time" on running the below script. trying to run a cron on my website and each url takes time to process in my website which leads to exceeding the time limit. Once url processed in the website it shows the message from the link in the execution log. How can i make the script run again from start or from url it stopped.

function getUrls() {
try {
var urls = [ 'https://www.testurl.com/link1=processing',
             'https://www.testurl.com/link2=processing',
             'https://www.testurl.com/link3=processing',];

function getUrl(url) {
  var response = UrlFetchApp.fetch(url).getContentText();
  if ( response.indexOf("triggered") >-1 ) {
      Logger.log(response);
    }
  else if ( response.indexOf("complete") >-1 ) {
      Logger.log(response);
      response = UrlFetchApp.fetch(url).getContentText();
    }
  while ( response.indexOf("Records Processed") >-1 ) {
      Logger.log(response);
      return 0;
    }
  }

urls.forEach( getUrl );
}
catch(err) {
Logger.log(err);
}
}
Nisi t
  • 13
  • 6
  • This is similar to another post. Because you are using a non-Google site to fetch your data, only you know what response should be. But your while loop doesn't do anything because you return after the first loop. So the only thing I can think of is your fecth exceeds the App Script time quota. Do any of the getUrl work? – TheWizEd Feb 16 '22 at 21:20
  • First url is the trigger url for 2nd url. Third url is trigger for fourth url. Fourth url will take time to complete (600 items to process) as it will work in batches and after each batch completion it shows message (containing text "complete") in the log. Fourth url will keep running until all batches are finished but i could not finish it due to exceeding time limit of 6 min. In the end when batches finishes it should show message (containing text "Records Processed") in the log. – Nisi t Feb 17 '22 at 06:50
  • I get this after running it a couple of times `{"status":403,"message":"Import #1 already triggered. Request skipped."}`. How to reset requests? Each one takes about 1 second. The last 0.4 seconds. – TheWizEd Feb 17 '22 at 12:12
  • first two urls have id=1 in it with first one is trigger url (url ends with trigger) which requires second url (ending with processing) to complete to get it reset. – Nisi t Feb 17 '22 at 13:00
  • other two urls (third and fourth) have id=2 in it with third one is trigger url (url ends with trigger) which requires fourth url (ending with processing) to complete to get it reset. – Nisi t Feb 17 '22 at 13:01
  • url with trigger will show message as "Cron job triggered" if in reset state but it will show "already triggered. Request skipped" if it has been already triggers and processing url is not complete. – Nisi t Feb 17 '22 at 13:04
  • processing url with id=1 has only 25 data to be processed ie second url, whereas processing url with id=2 has only 600 data to be processed ie 4th url. Time limit exceeds in case of 4th url as the data processing goes beyond 6min – Nisi t Feb 17 '22 at 13:06
  • To be honest with you the way you described it originally the first url was repeated until some message occured. Then the next, then the next. But now I'm not sure. Do you run each one once sequentually? – TheWizEd Feb 17 '22 at 13:14

4 Answers4

0

Your while condition lasts forever. The result of the condition never changes therefore if it is true once, it will run forever.

Or, you are calling getUrl in your forEach and that also leads to it calling itself over and over again. I think that is the real issue.

Neven Subotic
  • 1,399
  • 1
  • 6
  • 18
0

Try this:

function getUrls() {
  const urls = ['https://www.testurl.com/link1=processing', 'https://www.testurl.com/link2=processing', 'https://www.testurl.com/link3=processing'];

  urls.forEach(u => {
    let response = UrlFetchApp.fetch(u).getContentText();
    if (~response.indexOf("triggered")) {
      Logger.log(response);
    }
  });
}

BitWise not

Cooper
  • 59,616
  • 6
  • 23
  • 54
  • Not much you can do about that except possibly get a different account. I sometimes exceed 9 minutes on some scripts but I have not hit maximum and I'm not sure what it is. – Cooper Feb 16 '22 at 22:57
0

If I run each consecutively I get this. I hope this doesn't corrupt your data since it says it imports records but I have no idea where. I've added the code I ran that produced the log below.

function getUrls() {
  try {
    var urls = [ 'https://www.testurl.com/link1=processing',
                 'https://www.testurl.com/link2=processing',
                 'https://www.testurl.com/link3=processing',];

    function getUrl(url) {
      console.log("url = "+url)
      var start = new Date();
      var options = { 'method': 'get' };
      var response = UrlFetchApp.fetch(url, options);
      var end = new Date();
      console.log("response code = "+response.getResponseCode());
      console.log("elapsed time = "+((end.valueOf()-start.valueOf())/1000)+" sec.'");
      console.log("response text = "+response.getContentText());
    }

    var start = new Date();
    urls.forEach( getUrl );
    var end = new Date();
    console.log("total elapsed time = "+((end.valueOf()-start.valueOf())/1000)+" sec.");
  }
  catch(err) {
    Logger.log(err);
  }
}

5:27:21 AM  Notice  Execution started
5:27:22 AM  Info    url = xxxxxx
5:27:22 AM  Info    response code = 200
5:27:22 AM  Info    elapsed time = 0.671 sec.'
5:27:22 AM  Info    response text = {"status":200,"message":"#1 Cron job triggered."}
5:27:22 AM  Info    url = xxxxxx
5:27:25 AM  Info    response code = 200
5:27:25 AM  Info    elapsed time = 2.66 sec.'
5:27:25 AM  Info    response text = {"status":200,"message":"Import #1 complete"}
5:27:25 AM  Info    url = xxxxxx
5:28:39 AM  Info    response code = 200
5:28:39 AM  Info    elapsed time = 74.002 sec.'
5:28:39 AM  Info    response text = {"status":200,"message":"Records Processed 221. Records imported 220 of 600."}
5:28:39 AM  Info    url = xxxxx
5:29:56 AM  Info    response code = 200
5:29:56 AM  Info    elapsed time = 77.081 sec.'
5:29:56 AM  Info    response text = {"status":200,"message":"Records Processed 241. Records imported 240 of 600."}
5:29:56 AM  Info    total elapsed time = 154.448 sec.
5:29:55 AM  Notice  Execution completed
TheWizEd
  • 7,517
  • 2
  • 11
  • 19
  • Its a sample data so not to worry. The messages you got are correct and it adds data to my website. Check your url at 5:27:25 AM and 5:28:39 AM. Both are same. Url with ID =2 and ending with trigger should be url at 5:27:25 AM. anyways i am surprised how did your execution complete without completing the last url data. It should complete like the url with ID=1. Did you run the while loop? – Nisi t Feb 17 '22 at 14:32
  • My log 7:55:40 PM Notice Execution started 7:55:42 PM Info {"status":200,"message":"#1 Cron job triggered."} 7:55:45 PM Info {"status":200,"message":"Import #1 complete"} 7:55:46 PM Info {"status":403,"message":"Import #2 already triggered. Request skipped."} 7:57:06 PM Info {"status":200,"message":"Records Processed 281. Records imported 280 of 600."} – Nisi t Feb 17 '22 at 14:39
  • 7:58:24 PM Info {"status":200,"message":"Records Processed 301. Records imported 300 of 600."} 7:59:40 PM Info {"status":200,"message":"Records Processed 321. Records imported 320 of 600."} 8:01:06 PM Info {"status":200,"message":"Records Processed 341. Records imported 340 of 600."} 8:01:40 PM Error Exceeded maximum execution time – Nisi t Feb 17 '22 at 14:40
  • `urls.forEach( getUrl )` simly runs `url[0]`, then `url[1]`, then `url[2]` and last `url[3]`. If any of these needs to run again to get more data that would be where a while loop comes in and loops until something tells it to stop. – TheWizEd Feb 17 '22 at 14:51
  • yes the last url did not complete which requires rerun till the log message includes text "complete". Hence while loop needs to be implemented. In case of last url it will rerun multiple times with more than 6 minutes leading to max execution time limit. As the limit is reached it leaves the cron incomplete. Thats where i am looking to bypass the limit by creating a trigger to rerun till it receives message with text "complete". – Nisi t Feb 18 '22 at 13:16
  • First I don't know any way around the 6 min quota. Second regarding the idea of a trigger, yes you can create and delete triggers though script but it will be difficult to track the progress of a trigger to see if it needs to be fired again. You would probably need to use Document Properties to record and get the progress. Is it only the last url that loops? – TheWizEd Feb 18 '22 at 13:54
  • yes its the last url that requires loop to process 600 items. Is there a way to stop the script by setting a timer of 5min and then trigger again if the "complete" message not received within 5 min. Can this be helpful https://stackoverflow.com/questions/14450819/google-app-script-timeout-5-minutes and https://medium.com/geekculture/bypassing-the-maximum-script-runtime-in-google-apps-script-e510aa9ae6da and https://script.gs/bypass-script-execution-timeout-programmatically/ – Nisi t Feb 18 '22 at 14:24
  • I have an idea but it would take some time to work out. If you run from a sidebar, you would be using google.script.run to run sequential synchronous functions. Basically it would be nested google.script.run.withSuccesshandler(). If its okay to use your urls for testing? And there are no guarantees. – TheWizEd Feb 18 '22 at 14:42
  • Just curious, how are you running this script? It doesn't seem dependant on any spreadsheet or doc? Do you want stand alone? – TheWizEd Feb 18 '22 at 14:50
  • Yes you can test it as its a sample data url. Just that use the testurl.com in the answer above. – Nisi t Feb 18 '22 at 15:19
  • i am running the script from app script page and later will link to spreadsheet. The script will run based on if sheet is edited or modified. – Nisi t Feb 18 '22 at 15:20
0

Here is what I have so far. I have an empty spread sheet. I'm not using your urls, simply making repeated calls to UrlFetchApp.fetch("http://www.google.com/");. You can duplicate and try it out. If I knew the best way to test for urls[3] completion I can work it into the script but for now simply testing against the number of urls. I've added a new function repeatUrl. I can't test it because I'm really not sure of the response that stops loop. But it should work. And because each google.script.run is only about 1-2 min. you should not exceed the time limit. (I think).

In the app script I have:

In Code.gs:

function onOpen() {
  var menu = SpreadsheetApp.getUi().createMenu("Fetch Urls");
  menu.addItem("Side Bar","showSideBar");
  menu.addToUi();
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
};

function showSideBar() {
  try {
    var html = HtmlService.createTemplateFromFile('HTML_Test').evaluate();
    SpreadsheetApp.getUi().showSidebar(html);
  }
  catch(err) {
    SpreadsheetApp.getUi().alert(err);
  }
}

function fetchUrl(url) {
  try {
    var response = UrlFetchApp.fetch("http://www.google.com/");
    console.log(response);
    return response.getResponseCode();
  }
  catch(err) {
    return "Error: "+err.message;
  }
}

I have HTML_Test (HTML file):

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <input type="button" value="Fetch Urls" onclick="fetchOnClick()">
    <?!= include("JS_Test"); ?>
  </body>
</html>

And finally I have JS_Test (HTML file):

<script>
  var urls = [ 'https://www.testurl.com/link1=processing',
               'https://www.testurl.com/link2=processing',
               'https://www.testurl.com/link3=processing'];    

  function fetchUrl( index ) {
    try {
      google.script.run.withSuccessHandler(
        function(response) {
          alert("index= "+index+" response= "+response);
          //if( response.includes("Error:") ) return;
          index++;
          if( index < urls.length ) {
            fetchUrl( index );
          }
        }
      ).fetchUrl(urls[index]);
    }
    catch(err) {
      alert("Error in fetchUrl: "+err);
    }
  }
  
  function repeatUrl(url) {
    try {
      google.script.run.withSuccessHandler(
        function(response) {
          alert(" response= "+response);
          if( respone.includes("Records imported") ) {
            repeatUrl(url);
          }
        }
      ).fetchUrl(url);
    }
    catch(err) {
      alert("Error in fetchUrl: "+err);
    }
  }

  function fetchOnClick() {
    try {
      fetchUrl(0);
      repeatUrl(url[3]);
    } 
    catch(err) {
      alert(err);
    }
  }    
</script>
TheWizEd
  • 7,517
  • 2
  • 11
  • 19
  • Thanks and appreciate for your time and code. But being a novice i am not sure how to implement this. And regarding urls, you can use them in your system for testing but when you paste the code here just use testurl.com as i dont want this page to show up in google searches. So you can use the url for testing and it wont corrupt anything. – Nisi t Feb 18 '22 at 17:18
  • You mean these `var urls = [ 'https://www.testurl.com/link1=processing', 'https://www.testurl.com/link2=processing', 'https://www.testurl.com/link3=processing',];`? – TheWizEd Feb 18 '22 at 17:29
  • yes use those urls here while posting here. – Nisi t Feb 18 '22 at 17:38
  • The getContextText() of the test urls are HTML ` – TheWizEd Feb 18 '22 at 18:21