Service invoked too many times in a short time: exec qps. - Google Sheets - google-apps-script

I have been using custom functions to break out simple mathematics into readable JavaScript, but am getting the following error:
Service invoked too many times in a short time: exec qps. Try Utilities.sleep(1000) between calls. (line 0).
I have tried sleeping for a random time, but that doesn't help. My functions look like this:
function conversationGrowthRate(clientCount, initialGrowthRate, conversationCount) {
//Utilities.sleep(Math.random() * 30000);
for (var i = clientCount; i > 10; i--) {
if (initialGrowthRate > 0) {
conversationCount += initialGrowthRate
initialGrowthRate -= 0.000003
}
}
return conversationCount;
}
function conversionGrowth(clientCount, conversationCount, initialConversionRate, conversionGrowthRate, maxConversionRate, coversPerBooking, initialConversationGrowthRate, initialConversationCount) {
//Utilities.sleep(Math.random() * 30000);
if (clientCount <= 50) {
return coversPerBooking * conversationCount * initialConversionRate;
}
else {
var firstFiftyClientConversations = conversationGrowthRate(50,initialConversationGrowthRate, initialConversationCount)*30*50; //~30
var additionalConversionGrowthRate = (clientCount-50) * conversionGrowthRate;
var totalConversionRate = initialConversionRate + additionalConversionGrowthRate;
var additionalClientConversations = conversationGrowthRate(clientCount-50, initialConversationGrowthRate, initialConversationCount) * 30 * (clientCount-50);
if (totalConversionRate < maxConversionRate) {
return coversPerBooking * ((firstFiftyClientConversations * initialConversionRate) + (additionalClientConversations * totalConversionRate));
}
else {
return coversPerBooking * (conversationCount * maxConversionRate);
}
}
}
function salesProductivity(currentExecs, prevMonthExecs, prevTwoMonthExecs, prevThreeMonthExecs, prevFourMonthExecs, salesPerExecPerMonth) {
//Utilities.sleep(Math.random() * 30000);
var firstMonthHires = currentExecs - prevMonthExecs;
var secondMonthHires = prevMonthExecs - prevTwoMonthExecs;
var thirdMonthHires = prevTwoMonthExecs - prevThreeMonthExecs;
var fourthMonthHires = prevThreeMonthExecs - prevFourMonthExecs;
var longerHires = prevFourMonthExecs;
return (secondMonthHires * (0.33 * salesPerExecPerMonth)) + (thirdMonthHires * (0.66 * salesPerExecPerMonth)) + (fourthMonthHires * (1 * salesPerExecPerMonth)) + (longerHires * salesPerExecPerMonth);
}
I changed nothing before it started working.

Google Sheets runs custom functions remotely through a service called exec. If there are "too many" calls (according to some undocumented quota, or, at least, I haven't found any such documentation) to any custom functions, this error will be emitted.
If the calls are happening because many cells are calling the custom functions, I don't see how Utilities.sleep(milliseconds) can help since exec would be called before sleep is (indeed, it'll be exec that'll be calling sleep whether directly or indirectly).
As some comments have mentioned, converting the function to accept ranges and return arrays (and changing the sheet to accommodate said function) is the way to go (until Google add smarter throttling).

This should work for any google service that's hitting a usage quota.
while(true) {
try {
return '<whatever's hitting the quota>';
}
catch(error) {
Utilities.sleep(1000);
}
}
It doesn't give up
It doesn't impose a delay unless needed

Related

Exception: Failed to retrieve form data. Please wait and try again

I have been getting this error a lot lately due to which my customer thinks this is a bug from my end but it issue from Google's end.
const respondentEmail = e.response.getRespondentEmail();, is where it is giving me an error, can someone help me solve this issue as I'm not able to find any on internet
async function onFormSubmit(e) {
Logger.log("Inside On Form Submit");
const form = FormApp.getActiveForm();
let latestFormResponse;
const formResponses = form.getResponses();
const respondentEmail = e.response.getRespondentEmail();
if (respondentEmail === "") return false
///.. other code
}
can anyone please help me out? I'm loosing customers due to this bug :(
Assuming that the issue is intermittent, you could retry the problematic call with exponential backoff. Replace this line:
const respondentEmail = e.response.getRespondentEmail();
...with:
let respondentEmail;
try {
respondentEmail = exponentialBackoff_(() => e.response.getRespondentEmail());
} catch (error) {
console.log(error.message);
respondentEmail = '';
}
Here's a simple exponentialBackoff_() implementation:
/**
* Calls a closure, retries on failure, and returns the value it gives.
*
* Usage:
* exponentialBackoff_(myFunction);
* // ...or:
* exponentialBackoff_(() => myFunction(param1, param2));
* // ...or:
* const result = exponentialBackoff_(() => myFunction(param1, param2));
* // ...or:
* const respondentEmail = exponentialBackoff_(() => e.response.getRespondentEmail());
*
* #see https://en.wikipedia.org/wiki/Exponential_backoff
* #param {Function} action The closure to call.
* #param {Number} maxNumTries Optional. The number of times to retry. Defaults to 5.
* #return {Object} The closure return value.
*/
function exponentialBackoff_(action, maxNumTries = 5) {
// version 1.0, written by --Hyde, 29 December 2022
// - see https://stackoverflow.com/a/74952372/13045193
for (let tryNumber = 1; tryNumber <= maxNumTries; tryNumber++) {
try {
return action();
} catch (error) {
if (tryNumber >= maxNumTries) {
throw error;
}
Utilities.sleep(2 ** (tryNumber - 1) * 1000);
}
}
}
In my opinion, this issue occurs from google and we don’t have much control over it, try delaying the call using Sleep method.

How to fix "Service Documents failed while accessing document" while inserting a lot of data?

This is a follow-up question derivated from How to solve error when adding big number of tables
With the code below, I get the following message when, for 500 tables. BUt it works fine for 200 for example.
Exception: Service Documents failed while accessing document with id
The error happens on line 22, inside de if body = DocumentApp.getActiveDocument().getBody();
You also have the table template id to try, but here is an image
Image Table Template
function RequirementTemplate_Copy() {
var templatedoc = DocumentApp.openById("1oJt02MfOIQPFptdWCwDpj5j-zFdO_Wrq-I48mUq9I-w");
return templatedoc.getBody().getChild(1).copy()
}
function insertSpecification_withSection(){
// Retuns a Table Template Copied from another Document
reqTableItem = RequirementTemplate_Copy();
var body = DocumentApp.getActiveDocument().getBody();
// Creates X number of separated tables from the template
for (var i = 1; i < 501; i++){
table = reqTableItem.copy().replaceText("#Title#",String(i))
body.appendTable(table);
if((i % 100) === 0) {
DocumentApp.getActiveDocument().saveAndClose();
body = DocumentApp.getActiveDocument().getBody()
}
}
}
It looks that the error message isn't related to the number of tables to be inserted because it occurs before adding the tables.
Just wait a bit an try again. If the problem persist try your code using a different account if the code runs on the second account it's very possible that you first account exceeded a limit... there are some limits to prevent abuse that aren't published and that might change without any announcement.
Using the fix suggested for the code from my answer to the previous question and changing the number for iteration limit to 1000 and 2000 works fine
The following screenshot shows the result for 1000
Here is the code used for the tests
function insertSpecification_withSection(){
startTime = new Date()
console.log("Starting Function... ");
// Retuns a Table Template Copied from another Document
reqTableItem = RequirementTemplate_Copy();
var body = DocumentApp.getActiveDocument().getBody();
// Creates X number of separated tables from the template
for (var i = 0; i < 2000; i++){
table = body.appendTable(reqTableItem.copy());
// if((i % 100) === 0) {
// DocumentApp.getActiveDocument().saveAndClose();
// }
//
}
endTime = new Date();
timeDiff = endTime - startTime;
console.log("Ending Function..."+ timeDiff + " ms");
}
function RequirementTemplate_Copy() {
//---------------------------------------------------------------------------------------------------------------------------------------------------
var ReqTableID = PropertiesService.getDocumentProperties().getProperty('ReqTableID');
try{
var templatedoc = DocumentApp.openById(ReqTableID);
} catch (error) {
DocumentApp.getUi().alert("Could not find the document. Confirm it was not deleted and that anyone have read access with the link.");
//Logger.log("Document not accessible", ReqTableID)
}
var reqTableItem = templatedoc.getChild(1).copy();
//---------------------------------------------------------------------------------------------------------------------------------------------------
return reqTableItem
}
function setReqTableID(){
PropertiesService.getDocumentProperties().setProperty('ReqTableID', '1NS9nOb3qEBrqkcAQ3H83OhTJ4fxeySOQx7yM4vKSFu0')
}

How to make a user on my website ping a specific ip address?

I'm wondering how website like this one : https://ping.eu/ping/ manage to make our ip ping an other ip and get the result.
Someone have an idea ?
Thanks
It Doesn't. A PHP script(on the server) will most likely do it with "PHP Sockets". Have a look at
this: https://www.php.net/manual/en/sockets.examples.php
Else it could use exec() function, but that would be a security flaw.
So to answer your question: The website will ping the IP address not the 'client'
If you want to ping a server, i.e. an actual web address/URL like www.google.com, you can look at this JSFiddle http://jsfiddle.net/GSSCD/203/ or GitHub repository https://github.com/jdfreder/pingjs.
Here's some code from the JSFiddle:
function Pinger_ping(ip, callback) {
if(!this.inUse) {
this.inUse = true;
this.callback = callback
this.ip = ip;
var _that = this;
this.img = new Image();
this.img.onload = function() {_that.good();};
this.img.onerror = function() {_that.good();};
this.start = new Date().getTime();
this.img.src = "http://" + ip;
this.timer = setTimeout(function() { _that.bad();}, 1500);
}
}
Another way to ping a server/web address is to use JavaScript and the XMLHttpRequest() function it supports:
HTML:
<div id="result"></div>
JavaScript:
function http_ping(fqdn) {
var NB_ITERATIONS = 4; // number of loop iterations
var MAX_ITERATIONS = 5; // beware: the number of simultaneous XMLHttpRequest is limited by the browser!
var TIME_PERIOD = 1000; // 1000 ms between each ping
var i = 0;
var over_flag = 0;
var time_cumul = 0;
var REQUEST_TIMEOUT = 9000;
var TIMEOUT_ERROR = 0;
document.getElementById('result').innerHTML = "HTTP ping for " + fqdn + "</br>";
var ping_loop = setInterval(function() {
// let's change non-existent URL each time to avoid possible side effect with web proxy-cache software on the line
url = "http://" + fqdn + "/a30Fkezt_77" + Math.random().toString(36).substring(7);
if (i < MAX_ITERATIONS) {
var ping = new XMLHttpRequest();
i++;
ping.seq = i;
over_flag++;
ping.date1 = Date.now();
ping.timeout = REQUEST_TIMEOUT; // it could happen that the request takes a very long time
ping.onreadystatechange = function() { // the request has returned something, let's log it (starting after the first one)
if (ping.readyState == 4 && TIMEOUT_ERROR == 0) {
over_flag--;
if (ping.seq > 1) {
delta_time = Date.now() - ping.date1;
time_cumul += delta_time;
document.getElementById('result').innerHTML += "</br>http_seq=" + (ping.seq-1) + " time=" + delta_time + " ms</br>";
}
}
}
ping.ontimeout = function() {
TIMEOUT_ERROR = 1;
}
ping.open("GET", url, true);
ping.send();
}
if ((i > NB_ITERATIONS) && (over_flag < 1)) { // all requests are passed and have returned
clearInterval(ping_loop);
var avg_time = Math.round(time_cumul / (i - 1));
document.getElementById('result').innerHTML += "</br> Average ping latency on " + (i-1) + " iterations: " + avg_time + "ms </br>";
}
if (TIMEOUT_ERROR == 1) { // timeout: data cannot be accurate
clearInterval(ping_loop);
document.getElementById('result').innerHTML += "<br/> THERE WAS A TIMEOUT ERROR <br/>";
return;
}
}, TIME_PERIOD);
}
But, of course, these are web addresses, not IP addresses, so I'm not sure if that's what you're aiming for. I'm also not sure if you're looking for the amount of time spent to get the connection and the number of packets and bytes sent and received, or if you just want to validate the connection. The above code does all of those things. For IP addresses, you can try an AJAX request to ping a server, which only sees if there is a connection using YOUR SERVER'S IP address, NOT THE USER'S CLIENT, like this:
client --AJAX-- yourserver --ICMP ping-- targetservers
You could also try:
Using a Java applet with isReachable
Writing a server-side script which pings, and using AJAX to communicate to your server-side script
You might also be able to ping in Flash (using ActionScript)
One last hypothetical and unorthodox way to get an IP address is to inspect and view the source of the website you mentioned and copy some code, mostly JavaScript, and test it on your end and try to implement it.

How to Get Calendar Event global status?

This is concerning Google script to access Calendar event info
I'm looking for a method to get a Calendar Event global status? like for a Calendar Event with as status of attendess:
10 guests ==> 4 Yes, 3 MayBe, 1 No, 2 Awaiting
I've searched but I find only the way to get the status by guest, then implement a code to count individualy the global status.
Is there a way to get directly the global status?
Thanks in advance.
I happen to have written a function that does this. Here's an example of usage:
var summary = guestSummary(event.getGuestList());
Logger.log(summary);
...
[13-06-26 22:39:58:253 EDT] {respondedMaybe=0.0, awaiting=0.0, additional=0.0, respondedNo=0.0, respondedYes=1.0, invited=1.0}
I elected to use the guest list as a parameter, in order to support both CalendarEvent and CalendarEventSeries.
/**
* Return an object enumerating guest list summary information.
*
* #param {EventGuest[]} guestlist Array of EventGuests.
*
* #returns {object} guest list summary
*/
function guestSummary( guestlist ) {
var invited = guestlist.length;
var respondedYes = 0;
var respondedMaybe = 0;
var respondedNo = 0;
var awaiting = 0;
var additional = 0;
guestlist.forEach( function (guest) {
switch (guest.getGuestStatus()) {
case CalendarApp.GuestStatus.INVITED:
awaiting++;
break;
case CalendarApp.GuestStatus.YES:
respondedYes++;
break;
case CalendarApp.GuestStatus.NO:
respondedNo++;
break;
case CalendarApp.GuestStatus.MAYBE:
respondedMaybe++;
break;
default:
break;
}
additional += guest.getAdditionalGuests();
});
return {
invited : invited,
respondedYes : respondedYes,
respondedMaybe : respondedMaybe,
respondedNo : respondedNo,
awaiting : awaiting,
additional : additional
};
}

"Error Encountered: Invalid Argument" in handler function

I'm getting this error in my handler function but I've no clue what's causing it. I've copied the code and debugged it in a non-handler function and there was no error.
function _responseToNext(e) {
var app = UiApp.getActiveApplication();
app.getElementById('btnPrev').setEnabled(true);
var current = parseInt(CacheService.getPublicCache().get('currentItem'));
var agendaItems = Utilities.jsonParse(CacheService.getPublicCache().get('agenda'));
agendaItems[current]['notes'] = e.parameter.tAreaNotes;
agendaItems[current]['status'] = e.parameter.lboxStatus;
CacheService.getPublicCache().put('agenda', Utilities.jsonStringify(agendaItems));
current = current + 1;
CacheService.getPublicCache().put('currentItem', current);
fillAgendaDetail(app);
// only enabled 'Next' if there are more items in the agenda
if (current < agendaItems.length-1) {
app.getElementById('btnNext').setEnabled(true);
}
return app;
}
I suppose, the error cause is that the Cache get method returns null during the 1st execution when the cache is empty. The Utilities.jsonParse throws an exception and the cache becomes in any case empty. Try to use the following modified code.
function _responseToNext(e) {
var app = UiApp.getActiveApplication();
app.getElementById('btnPrev').setEnabled(true);
var cachedCurrent = CacheService.getPublicCache().get('currentItem');
var current;
if (cachedCurrent == null) {
current = 0;
}
else {
current = parseInt(cachedCurrent);
}
var cachedAgendaItems = CacheService.getPublicCache().get('agenda');
var agendaItems;
if (cachedAgendaItems == null) {
agendaItems = [][];
}
else {
agendaItems = Utilities.jsonParse();
}
agendaItems[current]['notes'] = e.parameter.tAreaNotes;
agendaItems[current]['status'] = e.parameter.lboxStatus;
CacheService.getPublicCache().put('agenda', Utilities.jsonStringify(agendaItems));
current = current + 1;
CacheService.getPublicCache().put('currentItem', current);
fillAgendaDetail(app);
// only enabled 'Next' if there are more items in the agenda
if (current < agendaItems.length-1) {
app.getElementById('btnNext').setEnabled(true);
}
return app;
}
Also please mention that the Public Cache (CacheService.getPublicCache()) is the same for all users of your script. In your case, this means, if two users user1#example.com and user2#example.com use the script they will have the same current and agendaItems variables values, i.e. it can be a situation when the _responseToNext handler is already executed under the user1 authority - the current variable is equal to 1, after the user2 executes the _responseToNext handler - the current variable is equal to 2 and so on. If you do not need such behaviour, use the CacheService.getPrivateCache().