Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens when I "sleep" in GAS ? (execution time limit workaround)

Behind this (not so much I admit...) funny question is a real question about a workaround I use without really understanding how it works.

First a brief description of my use case, all this is happening in a document bound UiApp showing up in the sidebar :

I have to create and send by email a couple of hundred documents in a mail merge application written in GAS. It takes of course far too long to be processed in one batch without hitting the 5 minutes execution time limit so I tried a few different workarounds to get the task done :

  1. use a time stamp (stored in ScriptProperties) when I start the process and when I reach a predefined value near the limit I store the current values (pointers, useful vars...) and return to the user interface asking the user to continue (or not). That works pretty well but need a human action to complete the whole task.
  2. So I setup a solution using a timer trigger that I create in the first handler call and this trigger calls the doc creation / sending function. This is also working nicely but the trigger called function are not able to interact with the UI since it seems that only handler functions can update the UI. The problem there is that I cannot show the progress nor easily show when the process ends.
  3. Then I remembered a small app that a wrote some time ago just for fun : it was a timer that used a checkBox as a server handler trigger (from an idea suggested long ago by Romain Vialard on the old Google forum) and decided to try this trick in my mail sending process.

It works perfectly, I process 40 documents batches is each call (during about 3 minutes) then pause for a while and start again until it gets finished. Each call is triggered by the checkBox linked server handler, the check box itself is changed in the handler function, creating its own trigger this way.

My question (finally ;-) is : knowing that the whole process might take 30 to 60 minutes how precisely is that possible ? How / why are these server handler function considered as multiple process since they are created from inside the function itself ?

I hope I'm clear enough, (which I doubt since it is a bit confuse in my mind :-)

I join below the code of the clock test app that gave me the idea, it will probably make things easier to understand.

function doGet() {
  var app = UiApp.createApplication().setTitle('Counter/Timer');
  var Panel = app.createAbsolutePanel().setStyleAttribute('padding','35');
  var counter = app.createHTML().setId('counter').setHTML('<B>Timer = wait</B>').setStyleAttribute('fontSize','40px');// set start display
  var clo = app.createTextBox().setName('clo').setId('clo').setValue('0').setVisible(false);//set start value in seconds
  var handler1 = app.createServerHandler('doSomething').addCallbackElement(Panel);
  var chk1 = app.createCheckBox('test1').addValueChangeHandler(handler1).setVisible(true).setId('chk1').setVisible(false);
  app.add(Panel.add(chk1).add(counter).add(clo));
  chk1.setValue(true,true);// start the process
  return app}

function doSomething(e) {
  var app = UiApp.getActiveApplication();
  var xx = Number(e.parameter.clo);
  var disp = app.getElementById('counter')
  xx++ ;// replace by xx-- to count downwards
  if(xx>600){ // 10 minutes timeout for example
  disp.setHTML('<B> GAME OVER ;-)</B>').setStyleAttribute('fontSize','80px').setStyleAttribute('color','RED')
  return app
  }
  var cnt = app.getElementById('clo').setValue(xx)
  disp.setHTML('<B>'+T(xx)+'</B>')
  Utilities.sleep(1000); // instead of sleeping do something !
// below comes the "active" part
  var chk1 = app.getElementById('chk1').setValue(false,false)
  var chk1 = app.getElementById('chk1').setValue(true,true)
  return app;
}

function T(val){
  var min = parseInt(val/60);
  var sec = val-(60*min);
  if(sec<10){sec='0'+sec}
  if(min<10){min='0'+min}
  var st = '>  '+min+':'+sec
  return st
}

enter image description here

like image 594
Serge insas Avatar asked Jun 02 '13 19:06

Serge insas


1 Answers

The statement that the server handler function calls aren't independent processes because they "are created from inside the function itself" isn't quite true.

You've set up a checkBox element chk1 with a server handler doSomething. Whenever the checkBox is checked, then, an event is dispatched to the server. (...and your script is causing those events with every chk1.setValue() call) The checkBox and surrounding UI code is running in your browser - click "show source" or use an explorer to see what's been served to your browser by the google servers. (Warning - it's obfuscated. But you might recognize some of your strings, and from that your client-side code.)

Here's what we're told in the documentation for Class ServerHandler:

When a ServerHandler is invoked, the function it refers to is called on the Apps Script server in a "fresh" script.

That's the key to extending your operating time: each dispatched event results in invocation of doSomething() in a completely new operating context - it's like you've opened the script editor in a different browser, and clicked "run" on your script. The "fresh" script has no access to var values of previous run... but it is also given it's own set of operating restrictions, including timers.

PS: You should make sure that server-side handler is "thread safe" by using Lock, since you're accessing shared resources that may be accessed by multiple instances of the doSomething() callback. Related to that, it's possible to hit another limit with this script:

Screenshot - error

Just for fun, I commented out .setVisible(false) on chk1, so the checkBox would be visible. I then rapidly clicked it several dozen times. The time display ran out-of-order, and eventually the above error popped up. (minutes later) It's an artificial situation, of course, but still an error state that's easily avoided.

PPS: I wonder if one could use the same technique to dispatch multiple parallel server-side handlers, and thus reduce the elapsed time to complete the whole job?

like image 163
Mogsdad Avatar answered Sep 24 '22 11:09

Mogsdad