27

I am testing out aws lambda, using nodejs with the 4.3 version. I'm able to successfully complete all the statements in my handler function within the console test, which includes connecting to a mongodb host within our vpc. But, the function always times out. I've found several posts and resources that discuss using the callback, and setting properties on the context, and IAM role permissions, but no matter what I do, it always ends up timing out. Current code:

'use strict';

var Mongoose = require('mongoose');
var Device = require('./device_model');
var Alarm = require('./alarm_model');
var Event = require('./event_model');

var mongoConnection = process.env.MONGO_URL;

var connection = Mongoose.connect(mongoConnection);

Mongoose.connection.once('open', function() {
    console.log("Connecting to mongo at: " + mongoConnection);
    console.log("Mongoose connection in lambda opened");
});

Mongoose.connection.on('error', function(){
    console.error("Error creating mongoose connection in lambda, exiting!");
    process.exit(1);
});

exports.check_alarms = function(event, context, callback) {

    context.callbackWaitsForEmtpyEventLoop = false;
    console.log("The incoming event: " + JSON.stringify(event));

    var device = null;
    Device.findByUUID(event.uuid, function(error, result){
        if(!error){
            device = result;
            console.log("the device: " + JSON.stringify(device));
            if(event.Ale && event.Ale.length > 0) {
                console.log("We have an alarm, checking if already set");
                callback(null, {"status":"alarms"});
            } else {
                console.log("Event contains no alarm; checking for historic active");
                callback(null, {"status":"no alarms"});
            }
        } else {
            console.log("there's a problem on mongo");
            callback("problem", "status not so good");
        }
    });

    callback(null, {"status":"outside of device find block"});
}
wkhatch
  • 2,664
  • 7
  • 36
  • 45
  • The default timeout for a lambda is 3 seconds - what do you have it set to? – stdunbar Jan 12 '17 at 19:57
  • Ah, should have specified that. I've currently got it set to 60 seconds, and still timing out. – wkhatch Jan 12 '17 at 20:03
  • What's the last message that gets logged? – Mark B Jan 12 '17 at 20:06
  • "Event contains no alarm; checking for historic active", from the console.log, which I'm assuming your asking about (as opposed to the aws console specific output) – wkhatch Jan 12 '17 at 20:10
  • That's really weird. Since you are setting `callbackWaitsForEmtpyEventLoop` to `false` it should be exiting as soon as you call the callback function. – Mark B Jan 12 '17 at 20:33
  • I agree; baffling. I thought perhaps due to the VPC access, but that's actually working fine; I'm able to get back valid results from the mongo host within the VPC – wkhatch Jan 12 '17 at 20:35

3 Answers3

53

You have a typo:

context.callbackWaitsForEmtpyEventLoop = false;

should be:

context.callbackWaitsForEmptyEventLoop = false;

Here's what the documentation says about the behavior of callbackWaitsForEmptyEventLoop:

callbackWaitsForEmptyEventLoop

The default value is true. This property is useful only to modify the default behavior of the callback. By default, the callback will wait until the Node.js runtime event loop is empty before freezing the process and returning the results to the caller. You can set this property to false to request AWS Lambda to freeze the process soon after the callback is called, even if there are events in the event loop. AWS Lambda will freeze the process, any state data and the events in the Node.js event loop (any remaining events in the event loop processed when the Lambda function is called next and if AWS Lambda chooses to use the frozen process). For more information about callback, see Using the Callback Parameter.

Minimal example:

// Times out due to typo
exports.function1 = (event, context, callback) => {
    setInterval(() => console.log('Long wait'), 100000);
    context.callbackWaitsForEmtpyEventLoop = false;
    callback(null, 'Hello from Lambda');
};

// Returns successfully
exports.function2 = (event, context, callback) => {
    setInterval(() => console.log('Long wait'), 100000);
    context.callbackWaitsForEmptyEventLoop = false;
    callback(null, 'Hello from Lambda');
};
wjordan
  • 19,770
  • 3
  • 85
  • 98
  • 7
    I would have never spotted that typo in a million years; even if I was trying to print the value, I would have copied the variable name directly. – itachi Jul 21 '17 at 15:49
  • 22
    That's good ol' JavaScript, a language where simple typos make you waste hours of your life. Typescript is your friend. – Marcelo Glasberg Oct 15 '17 at 23:44
  • Just curious.. What is the java equivalent for callbackWaitsForEmptyEventLoop? – User-8017771 Jan 09 '18 at 05:36
  • as far as I understand it, the 'event loop' is a concept particular to the NodeJS environment's concurrency model for which there isn't any direct Java equivalent, so I don't think `callbackWaitsForEmptyEventLoop` Lambda-NodeJS property has a Java equivalent either. – wjordan Jan 10 '18 at 00:25
  • 2
    The doc link and quoted text are outdated. Updated version: https://docs.aws.amazon.com/lambda/latest/dg/running-lambda-code.html – Justin M. Keyes Dec 17 '19 at 01:04
  • Worth noting that this only applies to non-async handlers. https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html – agoldev Jun 09 '22 at 09:02
10

If anybody was confused like I was with how to add callbackWaitsForEmptyEventLoop to new Alexa projects that look like this:

const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
  .addRequestHandlers(
      GetNewFactHandler,
      HelpHandler,
      ExitHandler,
      FallbackHandler,
      SessionEndedRequestHandler,
  )
  .addRequestInterceptors(LocalizationInterceptor)
  .addErrorHandlers(ErrorHandler)
  .lambda();

Example project found here: https://github.com/alexa/skill-sample-nodejs-fact/blob/master/lambda/custom/index.js

This solution worked for me:

// create a custom skill builder
const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = (event, context, callback) => {
  // we need this so that async stuff will work better
  context.callbackWaitsForEmptyEventLoop = false

  // set up the skill with the new context
  return skillBuilder
    .addRequestHandlers(
      GetNewFactHandler,
      HelpHandler,
      ExitHandler,
      FallbackHandler,
      SessionEndedRequestHandler,
    )
    .addRequestInterceptors(LocalizationInterceptor)
    .addErrorHandlers(ErrorHandler)
    .lambda()(event, context, callback);
}
tdon
  • 1,421
  • 15
  • 24
  • Sir, you are a lifesaver! I don't know how I would have crossed this hurdle without this answer. Btw is exports.handler = skillBuilder just syntactically different than exports.handler = (event, context, callback) => {} or is there any change of functionality as well in the second way? – thedreamsaver Dec 05 '20 at 12:34
  • I'm running into this weird issue of interceptors - https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/issues/668 Any ideas? – thedreamsaver Dec 05 '20 at 13:14
2

I was triggering a lambda from android code. The lambda was performing a heavy data lifting operation from DynamoDB and generating a JS object as a response. I could see the generated object in my lambda logs, but my android side code always timed out waiting for a response.

The reason for timeout was the Invocation payload (request and response) limit of 6 MB for synchronous and 256 KB for asynchronous lambda implementation. In my case I had a asynchronous implementation and the resultant object was over 256 KB.

There was no appropriate log message for overshooting this limit which derailed me. Reference: https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html

The workaround I implemented was writing the response to a text file in S3 bucket and passing its reference as a response to the android side instead of passing the actual object itself.

Parth Kapoor
  • 1,494
  • 12
  • 23