I'm facing issues with the creation of Cloudfront invalidations from AWS Lambda.
My case is pretty basic: I have set up a Lambda handler to be triggered by specific S3 objects creations and removals, in order to perform invalidation of cached versions on my Cloudfront distribution. This is the function code, written using nodejs:
const AWS = require('aws-sdk');
exports.handler = async function (event, context) {
const cloudFront = new AWS.CloudFront();
const invalidationParams = {
DistributionId: "XXXX",
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: 2,
Items: [
"/index.html",
"/service-worker.js"
]
}
}
};
cloudFront.createInvalidation(invalidationParams, (error, data) => {
if (error) {
console.log(error, error.stack);
} else {
console.log("Invalidation results", data);
}
});
};
As you can see, nothing too complex. Now, most of the times the handler executes without doing anything, I'm watching logs and nothing gets printed aside from the request id and start and end timestamps, not even Cloudfront errors, which gets me wondering what's going on. After four or five consecutive manual test executions, an invalidation is correctly created, but logs don't report it. Firing it an additional time then prints the invalidation results for the previous run. I find this very odd and confusing.
Is there something I may be missing, judging from context and Lambda code?
Thank you.
This is because of the Lambda runtime model. When you use an async function as the handler, the Lambda execution is considered "done" when it returns (the returning Promise is settled). In your code, you create an invalidation, but the handler function returns before the invalidation is done.
Lambda freezes the function when it is considered "done" and it won't wait for the createInvalidation
to finish. The reason sometimes it works and sometimes it does not is because there is a race condition between the Lambda freezing the function and the invalidation request. I've written an article with a more in-depth exlanation
The solution is simple: wait for the createInvalidation to finish before finishing the handler:
const AWS = require('aws-sdk');
exports.handler = async function (event, context) {
const cloudFront = new AWS.CloudFront();
const invalidationParams = {
DistributionId: "XXXX",
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: 2,
Items: [
"/index.html",
"/service-worker.js"
]
}
}
};
await cloudFront.createInvalidation(invalidationParams).promise();
// by the time the handler reaches this point the invalidation made it through
};