in packages/aws-rfdk/lib/lambdas/nodejs/lib/custom-resource/simple-resource.ts [76:143]
public async handler(event: CfnRequestEvent, context: LambdaContext): Promise<string> {
let status: CfnResponseStatus = CfnResponseStatus.SUCCESS;
let failReason: string | undefined;
let cfnData: object | undefined;
console.log(`Handling event: ${JSON.stringify(event)}`);
const resourceProperties: object = event.ResourceProperties ?? {};
const physicalId: string = calculateSha256Hash(resourceProperties);
try {
const timeout = (prom: any, time: number, exception: any) => {
let timer: any;
return Promise.race([
prom,
new Promise((_r, rej) => timer = setTimeout(rej, time, exception)),
]).finally(() => clearTimeout(timer));
};
// We want to always notify CloudFormation about the success/failure of the Lambda at all times.
// If function execution time is longer than Lambda's timeout, then the function is just stopped
// and CloudFormation is not notified at all. This would result in a hang-up during deployment.
// Thus, we want to stop the execution by ourselves before the Lambda timeout and reserve some time
// for notifying a CloudFormation about a failed deployment because of the timeout.
// 3 seconds should be enough to resolve the request that signals success/failure of the custom resource,
// but if Lambda timeout is too small, we would reserve 20% of the remaining time and still try to notify the CF.
// Check the logs during the development to see if you allocated enough time for your Lambda.
const defaultReserveTimeMs = 3000;
const remainingTimeMs = context.getRemainingTimeInMillis();
let reserveTimeMs = Math.min(0.2 * remainingTimeMs, defaultReserveTimeMs);
if (reserveTimeMs < defaultReserveTimeMs) {
console.debug(`The remaining Lambda execution time of ${reserveTimeMs} ` +
`ms might not be sufficient to send a CloudFormation response. At least ${defaultReserveTimeMs} ms is required. ` +
'Please increase the Lambda timeout.');
}
cfnData = await timeout(
this.handleEvent(event, context, resourceProperties, physicalId),
remainingTimeMs - reserveTimeMs,
new Error('Timeout error'),
);
} catch (e) {
// We want to always catch the exception for a CfnCustomResource CloudFormation
// must be notified about the success/failure of the lambda at all times;
// failure to notify results in a stuck stack that takes at least an hour to
// timeout.
status = CfnResponseStatus.FAILED;
if (types.isNativeError(e)) {
failReason = `${e.message}\n${e.stack}`;
} else {
failReason = String(e);
}
} finally {
// Always send a response to CloudFormation, signal success or
// failure based on whether or not we had an exception.
await sendCfnResponse({
event,
context,
status,
reason: failReason,
physicalId,
data: cfnData,
});
}
const response: string = `${status}` + (failReason ?? '');
console.log(`Result: ${response}`);
return response;
}