I have a very simple lambda function (nodeJS) which put the event received in kinesis stream. Here is the source code:
'use strict';
const AWS = require('aws-sdk');
const kinesis = new AWS.Kinesis({apiVersion: '2013-12-02'});
exports.handler = async (event, context, callback) => {
let body = JSON.parse(event.body);
let receptionDate = new Date().toISOString();
let partitionKey = "pKey-" + Math.floor(Math.random() * 10);
// Response format needed for API Gateway
const formatResponse = (status, responseBody) => {
return {
statusCode: status,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(responseBody)
}
}
// body.events is an array of events. Just add the reception date in each events.
for(let e of body.events) {
e.reception_date = receptionDate;
}
console.log("put In kinesis stream");
let kinesisParams = {
Data: new Buffer(JSON.stringify(body) + "\n"),
PartitionKey: partitionKey,
StreamName: 'event_test'
};
kinesis.putRecord(kinesisParams, (err, res) => {
console.log("Kinesis.putRecord DONE");
if(err) {
console.log("putRecord Error:", JSON.stringify(err));
callback(null, formatResponse(500, "Internal Error: " + JSON.stringify(err)));
} else {
console.log("putRecord Success:", JSON.stringify(res));
callback(null, formatResponse(200));
}
});
};
When this code is executed, here are the logs in cloudwatch:
START RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408 Version: $LATEST
2019-01-11T09:39:11.925Z 5d4d7526-1a40-401f-8417-06435f0e5408 put In kinesis stream
END RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408
REPORT RequestId: 5d4d7526-1a40-401f-8417-06435f0e5408 Duration: 519.65 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 28 MB
It seems that kinesis.putRecord is not called... I don't see anything in kinesis stream logs. I'm certainly wrong somewhere, but I don't know where !
kinesis.putRecord is an asynchronous operation, which calls callback (The second param) when it's finished (whether successful or with an error).
async function is a function that returns a promise. Lambda will finish its execution when this promise is resolved, even if there are other asynchronous operations which are not done yet.
Since your function returns nothing, then the promise is immediately resolved when the function ends and therefore the execution will be finished immediately - without waiting to your async kinesis.putRecord task.
When using an async handler, you don't need to call callback. Instead, you return what ever you want, or throw an error. Lambda will get it and respond respectively.
So you have 2 options here:
Since you don't have any await in your code, just remove the async. In this case Lambda is waiting for the event loop to be emtpy (Unless you explicitly change context.callbackWaitsForEmptyEventLoop)
Change the kinesis.putRecord to something like:
let result;
try {
result = await kinesis.putRecord(kinesisParams).promise();
} catch (err) {
console.log("putRecord Error:", JSON.stringify(err));
throw Error(formatResponse(500, "Internal Error: " + JSON.stringify(err));
}
console.log("putRecord Success:", JSON.stringify(result));
return formatResponse(200);
In the second option, the lambda will keep running until kinesis.putRecord is finished.
For more information about Lambda behavior in this case, you can see the the main code which execute your handler under /var/runtime/node_modules/awslambda/index.js in the lambda container.
#ttulka could you explain a bit more? Give advices or code samples ? –
Adagyo
It's about the async processing evolution in JavaScript.
First, everything was done with callback, it's the oldest approach. Using callbacks everywhere leads to "Callback Hell" (http://callbackhell.com).
Then Promises was introduced. Working with Promises looks a bit like working with Monads, everything is packed into a "box" (Promise), so you have to chain all your calls:
thisCallReturnsPromise(...)
.then(data => ...)
.then(data => ...)
.then(data => ...)
.catch(err => ...)
Which is a bit unnatural to humans, so ECMAScript 2017 proposed a syntactic sugar in async functions (async/await) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Async/await syntax allows you to work with async promises like with a normal sync code:
const data = await thisCallReturnsPromise(...)
Don't forget, the await call must be inside an async function:
async () => {
const data = await thisCallReturnsPromise(...)
return await processDataAsynchronouslyInPromise(data)
}
AWS Lambda supports Node.js v8.10, which fully implements this syntax.
Just found the solution: Removing "async" keyword make it work !
exports.handler = (event, context, callback) => { ... }
Related
Was looking at using the SSM Parameter Store SDK to grab secrets for a lambda function. I'm also using epsagon to wrap the async handler function. Epsagon needs to be initialized with some secrets, and then is used to wrap the handler function:
import * as epsagon from 'epsagon'
epsagon.init({
token: EPSAGON_ACCOUNT_TOKEN,
})
export const lambdaHandler = epsagon.lambdaWrapper(async (event) => {
// do stuff
})
Started using aws-parameter-cache to grab config values from SSM Param Store, but since they are resolved with an API call, it takes an await to get the values fully resolved.
import { ssmParameter } from 'aws-parameter-cache'
const param = ssmParameter({ name: 'foo' })
const value = await param.value; // <-- can only be done inside an async function (nodejs12)
Since we don't yet have top level await in nodejs12, is there a way to resolve the variables outside of the handler function? Is it possible to wait for the API call for await param.value to finish so that I can initialize epsagon with a value stored in SSM Param Store?
import * as epsagon from 'epsagon'
import { ssmParameter } from 'aws-parameter-cache'
const ssmParam = ssmParameter({ name: 'epsagonToken' })
const epsagonToken = await ssmParam.value // fails since outside of async func
epsagon.init({
token: epsagonToken,
})
export const lambdaHandler = epsagon.lambdaWrapper(async (event) => {
const epsagonToken = await ssmParam.value // works here but too late
})
Would this "just work" in nodejs 14.3.0 with top-level await? Custom runtime?
Or maybe some form of never-rejecting top-level async function, like in the top answer to this: how-can-i-use-async-await-at-the-top-level?
Need the handler to be the callback to the top-level async function--from what I've read this is essentially how top-level async works in 14.3. Looking for way to store all secrets in SSM Param store and reduce cf template ENV variable mappings.
Basically there is no easy way to do top-level await in this case, but there are some easy workarounds around it. For example, here is an implementation of another wrapper that you can use to initialize Epsagon:
import * as epsagon from 'epsagon'
import { ssmParameter } from 'aws-parameter-cache'
const ssmParam = ssmParameter({ name: 'epsagonToken' })
const withEpsagon = (wrapped) => {
let epsagonInited = false
const epsagonizedFunction = epsagon.lambdaWrapper(wrapped)
return async (event, context, callback) => {
if (!epsagonInited) {
const epsagonToken = await ssmParam.value
epsagon.init({
token: epsagonToken,
})
epsagonInited = true
}
return epsagonizedFunction(event, context, callback)
}
}
export const lambdaHandler = withEpsagon(async (event) => {
// your code here
})
This code will resolve the SSM parameter on its first execution (right after a cold start, which is a time you would have to spend on the cold start anyway), and memorize that it already initialized Epsagon so you don't have to waste time on every time the Lambda is invoked.
track the time elapsed in lambda function stop it at 9-10 min .
save the point at which it stopped and continue untill task is completed
compulsory use lambda function
I support John Rotenstein's response, stating that if you're using lambda this way, you probably shouldn't be using lambda. Out of my own curiosity though, I think the code you'd be looking for is something along the following lines (written in Node.JS)
let AWS = require('aws-sdk');
let functionName = 'YOUR_LAMBDA_FUNCTION_NAME'
let timeThreshholdInMillis = 5000 // 5 seconds
exports.handler = async (event, context, callback) => {
let input = JSON.parse(event['foo']); // input from previous lambda, omitted try-catch for brevity
let done // set this variable to true when your code is done
let interval = setInterval(() => {
if (done) callback(null, true)
let remainingTime = context.get_remaining_time_in_millis();
if (remainingTime < timeThreshholdInMillis) {
// Going to restart
clearInterval(interval);
// save your progress (local or remote) to `input` variable
let lambda = new AWS.Lambda();
return lambda.invoke({
FunctionName: functionName,
Payload: JSON.stringify({ 'foo': input }) // Pass your progress here
}, (err, data) => {
// kill container
if (err) callback(err)
else callback(null, data)
});
}
}, 1000);
}
Edit: an example
This is to clarify how 'passing progress' would work in recursive lambdas.
Let's say you want to increment a variable (+1) every second and you want to do this in Lambda.
Givens
We will increment the variable by 1 once every 1000 ms.
Lambda will run until remaining time is < 5000 ms.
Lambda execution timeout is 60,000 ms (1 minute).
Lambda function pseudo code:
function async (event, context) {
let counter = event.counter || 0
setInterval(() => {
if (context.get_remaining_time_in_millis() < 5000) {
// start new lambda and pass `counter` variable
// so it continues counting and we don't lose progress
lambda.invoke({ payload: { counter } })
} else {
// Add 1 to the counter variable
counter++
}
}, 1000)
}
Not sure what you are trying out, but have a look at AWS Step Functions to better orchestrate your serverless recursion fun.
Also be aware of costs.
Getting Started:
https://aws.amazon.com/getting-started/hands-on/create-a-serverless-workflow-step-functions-lambda/
Example:
https://docs.aws.amazon.com/step-functions/latest/dg/sample-project-transfer-data-sqs.html
We're trying to develop a self-invoking lambda to process S3 files in chunks. The lambda role has the policies needed for the invocation attached.
Here's the code for the self-invoking lambda:
export const processFileHandler: Handler = async (
event: S3CreateEvent,
context: Context,
callback: Callback,
) => {
let bucket = loGet(event, 'Records[0].s3.bucket.name');
let key = loGet(event, 'Records[0].s3.object.key');
let totalFileSize = loGet(event, 'Records[0].s3.object.size');
const lastPosition = loGet(event, 'position', 0);
const nextRange = getNextSizeRange(lastPosition, totalFileSize);
context.callbackWaitsForEmptyEventLoop = false;
let data = await loadDataFromS3ByRange(bucket, key, nextRange);
await database.connect();
log.debug(`Successfully connected to the database`);
const docs = await getParsedDocs(data, lastPosition);
log.debug(`upserting ${docs.length} records to database`);
if (docs.length) {
try {
// upserting logic
log.debug(`total documents added: ${await docs.length}`);
} catch (err) {
await recurse(nextRange.end, event, context);
log.debug(`error inserting docs: ${JSON.stringify(err)}`);
}
}
if (nextRange.end < totalFileSize) {
log.debug(`Last ${context.getRemainingTimeInMillis()} milliseconds left`);
if (context.getRemainingTimeInMillis() < 10 * 10 * 10 * 6) {
log.debug(`Less than 6000 milliseconds left`);
log.debug(`Invoking next iteration`);
await recurse(nextRange.end, event, context);
callback(null, {
message: `Lambda timed out processing file, please continue from LAST_POSITION: ${nextRange.start}`,
});
}
} else {
callback(null, { message: `Successfully completed the chunk processing task` });
}
};
Where recurse is an invocation call to the same lambda. Rest of the things work as expected it just times out whenever the call stack comes on this invocation request:
const recurse = async (position: number, event: S3CreateEvent, context: Context) => {
let newEvent = Object.assign(event, { position });
let request = {
FunctionName: context.invokedFunctionArn,
InvocationType: 'Event',
Payload: JSON.stringify(newEvent),
};
let resp = await lambda.invoke(request).promise();
console.log('Invocation complete', resp);
return resp;
};
This is the stack trace logged to CloudWatch:
{
"errorMessage": "connect ETIMEDOUT 63.32.72.196:443",
"errorType": "NetworkingError",
"stackTrace": [
"Object._errnoException (util.js:1022:11)",
"_exceptionWithHostPort (util.js:1044:20)",
"TCPConnectWrap.afterConnect [as oncomplete] (net.js:1198:14)"
]
}
Not a good idea to create a self-invoking lambda function. In case of an error (could also be a bad handler call on AWS side) a lambda function might re-run several times. Very hard to monitor and debug.
I would suggest using Step Functions. I believe this tutorial can help Iterating a Loop Using Lambda
From the top of my head, if you prefer not dealing with Step Functions, you could create a Lambda trigger for an SQS queue. Then you pass a message to the queue if you want to run the lambda function another time.
I have an AWS Lambda Function A that calls another Lambda Function B
For sake of discussion I want to invoke it synchronously - wait for results
and process them.
I want to do something like this in Lambda A:
let results = lambda.invoke(LambdaB);
// Do some stuff with results
The issue is that when I use the SDK APi to invoke Lambda B, I pass in a function that processes the results of that invocation. The processing function gets invoke and processes the results, but I'm noticing that the
// Do some other stuff with results
line is executing before that processing function completes, so the results
are not available yet. I'm still somewhat new to the NodeJS way of doing things, so what is the paradigm to ensure that I have the results I want before I move on to more processing with Lambda A? Here is what I have in a nuthell:
// Lambda A code
let payLoad = undefined;
let functionName = "LambdaB";
let params = {
FunctionName: functionName,
InvocationType: "RequestResponse",
LogType: "Tail",
Payload: '{name: "Fred"}'
};
lambda.invoke(params, function(err,data) {
if (err) {
// process error
} else {
payLoad = JSON.parse(data.Payload);
// payLoad is set properly here.
}
});
console.log("Do stuff with results');
console.log("PAYLOAD=" + payLoad);
// payLoad is undefined here
// How can I ensure it is set by the time I get here
// or do I need to use a different paradigm?
You will need to use async/await.
So your code will look like that :
exports.handler = async () => {
// Lambda A code
let functionName = "LambdaB";
let result = undefined;
let payload = undefined;
let params = {
FunctionName: functionName,
InvocationType: "RequestResponse",
LogType: "Tail",
Payload: '{name: "Fred"}'
};
try {
result = await lambda.invoke(params).promise();
payload = JSON.parse(result.Payload);
} catch (err) {
console.log(err);
}
console.log("Do stuff with results');
console.log(payload);
return;
}
Watchout : the lambda handler has to be an async function to use async/await in it !
I am using the following nodeJS lambda function to invoke a java lambda function.
var AWS = require('aws-sdk');
var lambda = new AWS.Lambda({httpOptions:{timeout: 300000}});
exports.handler = function(event, context) {
var params = {
FunctionName: 'sensi', // the lambda function we are going to invoke
InvocationType: 'RequestResponse',
Payload: '{"input":"alum_ROW,10,-1"}',
LogType:'Tail'
};
lambda.invoke(params, function(err, data) {
if (err) {
context.fail(err);
} else {
context.succeed(data.LogResult);
}
})
};
The execution of the caller lambda is successful and the function I am calling is getting executed correctly. The problem is the caller lambda ends before the called lambda function ends and I got null as data.payload and hexadecimal characters as data.LogResult. obviously what is needed is to wait until the called lambda is finished.
below the lambda i am trying to call:
public boolean run(String input ){
String [] inputs= input.split(",");
String systemId=inputs[0];
int iterations=Integer.parseInt(inputs[1]);
int output=Integer.parseInt(inputs[2]);
//custom code
return true;
}
This function is inside class Launchers.Launcher and therefore the sensi lambda function has handler Launchers.Launcher::run
EDIT: SOLUTION PROVIDED BY #JOHN
adding: context.callbackWaitsForEmptyEventLoop = false;
Compiling your question, (correct me if I'm wrong and I'll edit the answer):
You have a lambda X, that calls lambda Y.
Lambda Y returns some value Z for each execution.
You want lambda X to receive the value Z, when calling lambda Y.
You want the callback to be called with the return value Z, after lambda Y ends execution.
According to your params:
You request a synchronous execution, by specifying RequestResponse, which is good for this purpose (receiving the result of lambda Y).
You request the last 4KB of logs produced by lambda Y - by specifying Tail.
According to the documentation and given I was right in compiling your question, the LogResult is not what you're looking for, but the Payload is.
The following is from the Response Elements section:
LogResult
It is the base64-encoded logs for the Lambda function
invocation. This is present only if the invocation type is
RequestResponse and the logs were requested.
The response returns the following as the HTTP body.
Payload
It is the JSON representation of the object returned by the
Lambda function. This is present only if the invocation type is
RequestResponse.
API_Invoke Reference
Edit (added example):
I've created two lambdas, according to my above description, X and Y.
Here's X's code:
let aws = require('aws-sdk');
let lambda = new aws.Lambda();
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const params = {
FunctionName: "Y",
InvocationType: "RequestResponse",
LogType: "Tail",
Payload: '{"name":"jonathan"}'
};
lambda.invoke(params, (err, res) => {
if (err) {
callback(err);
}
console.log(JSON.stringify(res));
callback(null, res.Payload);
});
};
Here's Y's code:
exports.handler = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
console.log(JSON.stringify(event));
setTimeout(() => {
callback(null, "My name is Jonathan");
}, 1000 * 10); // 10 seconds delay
};
What you're trying to achieve is do-able.
Lambda X invokes synchronously Lambda Y and waits for its response (waits 10 seconds).
You were accessing the wrong property of the callback input, you accessed the LogResult property instead of the Payload property.
Here's a log of X's arbitrary execution:
START RequestId: a6a98e8d-31b9-11e7-aba1-d5d86092115f Version: $LATEST
2017-05-05T17:38:40.805Z a6a98e8d-31b9-11e7-aba1-d5d86092115f {"StatusCode":200,"LogResult":"U1RBUlQgUmVxdWVzdElkOiBhNzVmMjIzZi0zMWI5LTExZTctYmRmYy0xMzJkMDc0Zjc3YzggVmVyc2lvbjogJExBVEVTVAoyMDE3LTA1LTA1VDE3OjM4OjMwLjY4NloJYTc1ZjIyM2YtMzFiOS0xMWU3LWJkZmMtMTMyZDA3NGY3N2M4CXsibmFtZSI6ImpvbmF0aGFuIn0KRU5EIFJlcXVlc3RJZDogYTc1ZjIyM2YtMzFiOS0xMWU3LWJkZmMtMTMyZDA3NGY3N2M4ClJFUE9SVCBSZXF1ZXN0SWQ6IGE3NWYyMjNmLTMxYjktMTFlNy1iZGZjLTEzMmQwNzRmNzdjOAlEdXJhdGlvbjogMTAwMjcuMjkgbXMJQmlsbGVkIER1cmF0aW9uOiAxMDEwMCBtcyAJTWVtb3J5IFNpemU6IDEyOCBNQglNYXggTWVtb3J5IFVzZWQ6IDE3IE1CCQo=","Payload":"\"My name is Jonathan\""}
END RequestId: a6a98e8d-31b9-11e7-aba1-d5d86092115f
REPORT RequestId: a6a98e8d-31b9-11e7-aba1-d5d86092115f Duration: 11233.34 ms Billed Duration: 11300 ms Memory Size: 128 MB Max Memory Used: 30 MB
Note the structure of the callback's input:
{
"StatusCode": 200,
// this is the logs that Y's printed to the "console" (base64 encoded)
"LogResult": "U1RBUlQgUmVxdWVzdElkOiBhNzVmMjIzZi0zMWI5LTExZTctYmRmYy0xMzJkMDc0Zjc3YzggVmVyc2lvbjogJExBVEVTVAoyMDE3LTA1LTA1VDE3OjM4OjMwLjY4NloJYTc1ZjIyM2YtMzFiOS0xMWU3LWJkZmMtMTMyZDA3NGY3N2M4CXsibmFtZSI6ImpvbmF0aGFuIn0KRU5EIFJlcXVlc3RJZDogYTc1ZjIyM2YtMzFiOS0xMWU3LWJkZmMtMTMyZDA3NGY3N2M4ClJFUE9SVCBSZXF1ZXN0SWQ6IGE3NWYyMjNmLTMxYjktMTFlNy1iZGZjLTEzMmQwNzRmNzdjOAlEdXJhdGlvbjogMTAwMjcuMjkgbXMJQmlsbGVkIER1cmF0aW9uOiAxMDEwMCBtcyAJTWVtb3J5IFNpemU6IDEyOCBNQglNYXggTWVtb3J5IFVzZWQ6IDE3IE1CCQo=",
"Payload": "\"My name is Jonathan\"" // <--- this is the returned value
}
Hopefully this answers your question.