An easy way to deal with Google Apps Script’s 6-minute limit

Inclu Cat
5 min readJul 20, 2021

--

Photo by Tsvetoslav Hristov on Unsplash

About the 6-minute limit

Google Apps Script is handy, and it will help you a lot in your work. However, as you use it, you may hit a big wall. That is the six-minute limit on execution time. As the official documentation states, the maximum allowed time per execution of Google Apps Script is 6 minutes. ( As far as I can tell from the official documentation, there is no difference between paid users and free users. )

If the script execution time reaches 6 minutes, the script will stop suddenly, and an error message “Exceeded maximum execution time” will be displayed.

You will see an error message like this. 😥

The processing speed of Apps Script is not that fast, so if you are loading data from Google Sheets or Google Drive, you will reach the time limit in no time. Then, what should we do? I will tell you how to solve this.

How to solve this?

This problem can be solved with Script properties and Time-driven triggers. You can use these mechanisms to rerun the script as many times as you need.

Class PropertiesService allows you to store simple data in key-value pairs scoped to one script. So you can store the number of times your function has processed as the script’s property.

Time-driven triggers let scripts execute at a particular time. So you can set a trigger to run the function next time before the 6-minute limit is reached.

The mechanisms for overcoming the six-minute limit

However, when it comes to actually do it, you’ll find that it’s not easy😓. You can find a lot of solutions that use this mechanism on the Internet. But you’ll soon understand that you have to do many things to use it; you will have to remove triggers and properties that are no longer needed, and you should determine how to pass arguments, and there are many other things.

So I created a Javascript class named LongRun to efficiently deal with the 6-minute time limit. It was created as an extension of my open-source project GAS-Terminal.

The LongRun class

This class hides operations such as PropertiesService and Time-based Triggers behind it, making it easy to perform long-running scripts. Here’s an example code.

function LongRunTest(
// there must be no arguments, because the parameters must be retrieved from LongRun class.
/* times: number, funcExecutionSeconds: number, maxExecutionSeconds: number, triggerDelayMinutes: number */
) {
let longRun = LongRun.instance;
// funcName must equal this function's name.
const funcName = 'LongRunTest';
// you can get the parameters from LongRun class.
// the order of the array is the same as the order of the command definition.
const params = longRun.getParameters(funcName);
const times = parseInt(params[0]);
const funcExecutionSeconds = parseInt(params[1]);
const maxExecutionSeconds = parseInt(params[2]);
const triggerDelayMinutes = parseInt(params[3]);
// you can set the long-running configurations. of course you can use the default values.
longRun.setMaxExecutionSeconds(maxExecutionSeconds); // default is 240 seconds
longRun.setTriggerDelayMinutes(triggerDelayMinutes); // default is 1 minute
// you should get the index to resume(zero for the first time)
let startIndex = longRun.startOrResume(funcName);
if( startIndex === 0 ){
LogUtils.i('--- LongRunTest command started. ---');
}
try {
// Execute the iterative process.
for (let i = startIndex; i < times; i++) {
LogUtils.i('Processing: ' + i);
// Each time before executing a process, you need to check if it should be stopped or not.
if (longRun.checkShouldSuspend(funcName, i)) {
// if checkShouldSuspend() returns true, the next trigger has been set
// and you should get out of the loop.
LogUtils.i('*** The process has been suspended. ***');
break;
}
// *** code your main process here! ***
Utilities.sleep(funcExecutionSeconds * 1000); // demonstrate the process
LogUtils.i('Processing Done!: ' + i);
}
}
catch (e) {
LogUtils.ex(e);
}
finally {
// you must always call end() to reset the long-running variables if there is no next trigger.
const finished = longRun.end(funcName);
if( finished ){
LogUtils.i('--- LongRunTest command finished. ---');
}
}
}

You can get the source code of the LongRun class from my GitHub repository. Please, check it! Of course, you can use the class solely, but it is more effective when used in conjunction with the GAS-Terminal framework.

For those who want to use the LongRun class without GAS-Terminal (ADDED 10/24/2021)

The LongRun class was created to enhance the GAS-Terminal, but it can be used without GAS-Terminal without any problem; you can take the LongRun.ts file only and put it into your project and use it.

I have also created a sample project that shows how to use this class independently. If you are interested, please see this repository.

For those who want to use the LongRun class more easily. (ADDED 12/14/2021)

I think the LongRun class has made it much easier to control tedious long-time processing, but it still remains complicated. If it’s a long process that doesn’t require such detailed control, wouldn’t it be easier to just pass the function to the LongRun class?

So I prepared a global function, executeLongRun() in LongRun.ts, which works by just passing the name of the function to process each one and the number of times to process it. (Thanks to Fabrice for the suggestion!)
This function can optionally be passed parameters, a function to initialize, and a function to be executed on exit.

Here is a sample:

import {executeLongRun, LongRun} from "../LongRun";function LongRunTest() {
const params = [];
params.push("param1");
params.push("param2");
const loopCount = 100;
// execute the long-run task
executeLongRun("main", loopCount, params, "initializer", "finalizer" );
}
// This function will be executed on first or restart. (optional)
function initializer(startIndex: number, params: string[]){
// Do some initial processing.
}
// This function will be executed on each time.
function main(index: number, params: string[]){
// Do some main processing.
}
// This function will be called on interruption or when all processing is complete. (optional)
function finalizer(isFinished: boolean, params: string[]){
// Do some final processing.
}

For a working sample, please see Test2.ts in the repository.

Note that it is not possible to use executeLongRun() to execute different long-time processes simultaneously.

Conclusion

The 6-minute limit problem is painful but can be solved by using the mechanism provided. And there are also some useful classes, such as the LongRun class. Let’s make use of Google Apps Script for long-time processing!👍

Originally published at http://inclucat.wordpress.com on July 20, 2021.

--

--

Inclu Cat

A solo developer living in Japan who is fighting (or wants to fight) the wall of the digital divide.