29
Debugging with Dashbird: AWS Lambda Process Exited Before Completing Request
Another generic error message from our favorite FaaS provider AWS Lambda.
And again, there are multiple reasons why this issue could arise. Let's first look at the basics of AWS Lambda to get a better intuition for when things go wrong later.
Lambda is an asynchronous event-based service at heart. The Lambda service asynchronously calls your functions---this doesn't mean that all events are handled asynchronously, but relates mainly to the programming model you have to use inside your function.
This shouldn't be confused with the event handling of Lambda, which can be synchronous and asynchronous.
In synchronous event handling, for example, when your function handles an API Gateway event, the event provider waits until you handled the event so that it can supply its own client with your results.
A browser sends a request to API Gateway, and API Gateway, in turn, sends the request as an event to a Lambda function. The browser will now wait for a response, and API Gateway will wait to handle the event. The longer your function takes, the longer both the browser and API Gateway will wait. Until a maximum of 30 seconds, API Gateway will call it a day and tell the browser your function timed out.
In asynchronous event handling, the event provider will just give the event to Lambda and call it a day. It will not wait for an answer from your Lambda function, whether it takes three seconds or ten minutes.
If you trigger a Lambda function from an S3 upload, the S3 service will never notice if the Lambda function failed or succeeded in handling the event. It will just trigger the function and then march on to other tasks.
Inside your Lambda function, you have to write asynchronous code. You can't simply take some arguments and return your calculations directly, like in the following example.
exports.handler = (event, context) => {
const result = event.data.x * event.data.x;
return { statusCode: 200, body: result };
});
You have to use some asynchronous computing constructs to handle the results. In the currently supported versions of the Node.js runtime for AWS Lambda, you have to use promises or asynchronous functions to handle your return values.
A promise version would look like this:
exports.handler =(event, context) => new Promise((resolve, reject) => {
const result = event.data.x * event.data.x;
resolve({ statusCode: 200, body: result });
});
Your handler function would directly return a new promise object that would be resolved in the future. You would call resolve if all went well or reject if you encountered an error.
And the corresponding asynchronous function looks like that:
exports.handler = async (event, context) => {
const result = event.data.x * event.data.x;
return { statusCode: 200, body: result };
});
An asynchronous function is a regular function marked with async at the beginning. This keyword wraps the whole function in a promise, so you don't have to do it manually. If you return, the promise will be resolved. If you throw an error, the promise will be rejected. This way, you can write code that looks synchronous but behaves asynchronously.
Using an asynchronous function is a "de facto" standard of writing Lambda handlers.
When Node.js version 11 was still supported, you had to use the context or a callback for your returns.
The context version looked like this:
exports.handler = (event, context) => {
const result = event.data.x * event.data.x;
context.done(null, { statusCode: 200, body: result });
});
Here the first argument to context.done is an error object; if none exist, we can use null.
The callback version looked like that:
exports.handler = (event, context, callback) => {
const result = event.data.x * event.data.x;
callback({ statusCode: 200, body: result });
});
As you can see, the asynchronous event handling got streamlined within new Node.js versions, so you only have to return from an asynchronous function, and Node.js will take care of the rest.
Now that we have a basic understanding of how AWS Lambda works let's look at our error.
AWS Lambda process exited before completing request
In the above code examples, we saw how it looks when we complete a request, so you might know what will cause the error to happen.
In the legacy code that uses context.done
or the callback
, this error will happen when your code doesn't reach one of these calls.
In the current versions that use promises or asynchronous functions, this error will happen if the promise didn't resolve or the function didn't return.
So, your function code did all of its work but never reached one of these four end states.
An asynchronous function implicitly returns if no code is left to work on, so even if you don't write a return statement, it will still "complete" just without any returned values. The other three versions of completing require you to call something explicitly, be it context.done
, callback
, or resolve
.
This error is mostly related to legacy behavior. These days other errors trigger instead of this one. So, this error is mostly a "catch what's left" kind of error.
There are multiple reasons why you didn't call the explicit return functions before your function exited, but most of them will lead to a timeout or exceeding memory limit kind of error.
The chances of you writing code that falls in a niche not covered by other errors are quite good. This can happen if you call upstream services, like DynamoDB, for example, and they fail silently for some reason.
Using a standalone Lambda function doesn't make much sense---usually, you use one to connect other services with some transformation and business logic.
Your best course of action here is to look into the limits of these services and all the places where you call them with the AWS SDK. If you sent too much data at once or forgot some attributes in the SDK call, things can fail without any explicit error message.
If you use the asynchronous function style to signal that your function has completed, you can't forget to call the right functions at the end anymore.
29