Skip to content

Instantly share code, notes, and snippets.

@IronSavior
Last active February 19, 2022 10:18
Show Gist options
  • Save IronSavior/fe644f11a3fe509338c02443a8bfa369 to your computer and use it in GitHub Desktop.
Save IronSavior/fe644f11a3fe509338c02443a8bfa369 to your computer and use it in GitHub Desktop.
Require AWS Lambda handler to invoke callback before exit (prevent Node exit until handler invokes callback)
const lambda_handler = require('lambda-handler');
exports.handler = lambda_handler(( event, ctx, done ) => {
// This will log a handled error to CloudWatch and return the error to AWS Lambda
throw 'BOOM!';
});
const lambda_handler = require('lambda-handler');
exports.handler = lambda_handler(( event, ctx, done ) => {
// This invocation will return "Complete!" to AWS Lambda as the function's output.
done(null, 'Complete!');
});
const lambda_handler = require('lambda-handler');
exports.handler = lambda_handler(( event, ctx, done ) => {
// This handler doesn't change the event loop. Normally Lambda would immediately exit
// with null output, but wrapping it in lambda_handler() prevents that. This handler will
// eventually time out and be killed by AWS Lambda.
});
"use strict";
const util = require('util');
// Ensure logged inputs, outputs, and context are complete
const inspect = (obj, opts = {}) => util.inspect(obj, Object.assign({depth: 10}, opts));
const info = console.info.bind(console);
const error = console.error.bind(console);
// Wrap the given handler with standard logging, error handling, and prevent Node exit until callback is invoked.
// @returns {Function} Augmented handler function
module.exports = handler => (...args) => invoke_handler(handler, ...args);
// Invoke AWS Lambda handler and ensure that Node will not exit until the handler explicitly invokes its callback
// @param handler {Function} Handler function
// @param event {Object} AWS Lambda input event (forwarded to handler)
// @param context {Object} AWS Lambda context (forwarded to handler)
// @param done {Function} AWS Lambda completion callback
// @returns value returned by handler
function invoke_handler( handler, event, context, done ){
info('Event:', inspect(event));
let finished = false;
prevent_exit_until_finished();
try{
const rv = handler(event, context, finish);
if( rv && rv.then !== undefined && typeof rv.then === 'function' ){
return rv.then(output => finish(null, output), e => finish(e));
}
return rv;
}
catch(e){
finish(e);
}
// Ensures the Node event loop never ends until handler explicitly signals completion via callback
// @param interval_ms {Integer} Timeout interval in milliseconds between checks
// @returns {void}
function prevent_exit_until_finished( interval_ms = 25 ){
if( finished ) return;
setTimeout(prevent_exit_until_finished, interval_ms);
}
// Invoked by handler to signal completion
// @param err {Object} Handler error (null when successfully completed)
// @param output {Object} Output returned to AWS Lambda
// @returns {void}
function finish( err, output ){
if( finished ) return;
finished = true;
if( err ) error('Function handler error:', err);
else info('Function handler output:', inspect(output));
done(...arguments);
}
}
@IronSavior
Copy link
Author

@anthonyroach You described exactly the problem I was trying to solve. I had an AWS StepFunctions workflow that would sometimes have the entire state blown away because Lambda mistakenly exited and captured a null output. I could sometimes see the logged events from where an earlier execution of a function became frozen by the Lambda runtime and was later awakened. I never had that problem again since using this hack.

I'm guessing there must be a bug in the Lambda Node runtime that infrequently allows the process to become frozen even when you have callbacks registered. I only encountered this strange behavior when I had hundreds of concurrent Lambda executions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment