Clearing Up setTimeout Misconceptions in AWS Lambda’s

  • by

There has been a lot of confusion around the appropriate use of timers in an AWS Lambda. This is mostly due to one developers attempt to implement a “mini scheduler” in a Lambda. This, as has been correctly reported, is not appropriate and is against best practice.

There is an excellent article explaining why here:

https://medium.com/radient-tech-blog/aws-lambda-and-the-node-js-event-loop-864e48fba49

A Failed Trial

function myTimeout(counter)
{
   console.log("Timeout triggered. Counter = " + counter)
   setTimeout(myTimeout, 500, ++counter, callback)
}
exports.handler = function(event, context, callback) {
 context.callbackWaitsForEmptyEventLoop = true
 let counter = 1
 setTimeout(myTimeout, 500, counter)
 console.log("Main handler is done")
}

“By default, the callback will wait until the event loop is empty before freezing the process and returning the results to the caller.”

In the above code context.callbackWaitsForEmptyEventLoop = true replicates the default behavior, which doesn’t address the issue as we shall see.

The setTimout line attempts to start a recursive loop of 500 timeout calls.  It then surrenders control, “going async”.

Main handler is done” is sent to console.

At this point in time the event loop is empty, even though we have a process running (the setTimeout loop, so the Lambda handler exits.

In this scenario the setting for callbackWaitsForEmptyEventLoop is irrelevant.

This code is salvageable by wrapping the calls in a promise and making the call synchronous by using async/await, however, this is probably not a good use of timers in a Lambda for other reasons (related to cost).

A Working Example

Timers are used in async processes all the time.  For instance, in this code: 

   process(resource: any): Promise<any> {
        const httpsCall = this.axiosInstance.get('/any)
            .then(() => {
                <some code>
                return resource;
            })
            .catch((err:any ) => {
                <some error handling code>
                return resource;
            });
        return httpsCall;
    }

This is a pretty typical case – an asynchronous HTTPS call is made, and the function returns a promise.  Within the HTTPS call, a timer is set to ensure the code doesn’t wait forever.   Why does this work?

Because upstream code waits for the promise to resolve, making the call effectively synchronous.

export const index = async (event?: APIGatewayProxyEvent,
                           context?: Context,
Callback?: Function) => {
      const healthCheckService = HealthCheckServiceFactory.create();
   return await healthCheckService.healthCheck(request, params);
};

The default setting for callbackWaitsForEmptyEventLoop is true, so the handler will not exit before the shorter of API Gateway timeout, Lambda timeout, or the Promise is resolved. (Actually callbackWaitsForEmptyEventLoop false will probably have the same result.

Summary

In conclusion, timers CAN be used in AWS Lambda code, they just have to be used appropriately, making sure the code waits for promises to resolve.  The handler code can not “go async” – it must be written to wait for the asynchronous process to complete.

Per the manual:If your code performs an asynchronous task, return a promise to make sure that it finishes running.”

And what is a timer but an asynchronous task…

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.