Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS Lambda Invoke does not execute the lambda function

I created 4 Lambda functions to process information that will be written into a MySQL table. the first three function just select, insert and update a MYSQL table record respectively.

I then created a 4th function to accept the record detail as part of the event parameter. This function will first try to select the record by invoking the first lambda function and if it finds it, will update the record on the table using the update lambda function. If it does not find it, it will invoke the insert function to add the record. I am using pool.query on the 3 functions that manipulates the MySQL table. I am also using lambda.invoke to call those three functions from the 4th function.

I was able to successfully test the 4th function locally by passing the record details as parameter and it was able to successfully call the three Lambda function and update the mySQL table record. The problem that I am having is that when I upload the function in AWS Lambda, it does not invoke any of the three functions. I am not seeing any errors in the log so I don't know how to check where the problem is. Here's ,y code that invokes the other functions:

exports.handler = (event, context, callback) => {

var err = null;
var payload = {
        qryString : event.qryString,
        record: event.updaterecord,
        dbConfigPool : event.dbConfigPool
           }

var params = {
  FunctionName: 'getInventory', 
  Payload: JSON.stringify(payload) 
}
console.log(' before invoke ' + JSON.stringify(params) )
lambda.invoke(params, function(err, data) {
console.log(' aftr invoke ' + JSON.stringify(params) )
  if (err) {
    console.log('err ' + err, err.stack); // an error occurred
    event.message = err + ' query error';
  }
  else    { 
      console.log('success' + JSON.stringify(data));   
      console.log(' status code ' + JSON.stringify(data.StatusCode));
      console.log(' Payload ' + JSON.stringify(JSON.parse(data.Payload)));
      var rowsTemp = JSON.parse(data.Payload);
      var rows = rowsTemp.data;
      if (!rowsTemp.recordExist) {
          console.log('insert')
            // Update inventory record only if quantity is not negative
          var newQuantity = 0
      newQuantity = parseFloat(event.updaterecord.quantity);
      if (Math.sign(newQuantity) === 1) {
        var payload = {
                    record: event.updaterecord,
                    dbConfigPool : event.dbConfigPool
                       }

        console.log('insert' + JSON.stringify(payload));
        var params = {
          FunctionName: 'insertInventory', 
          Payload: JSON.stringify(payload) 
        }
            lambda.invoke(params, function(err, data) {
              if (err) console.log(err, err.stack); // an error occurred
              else     console.log(data);           // successful response
            });
        }

  }
  else {
      newQuantity = 0
      newQuantity = parseFloat(event.updaterecord.quantity) + parseFloat(rows[0].quantity);
      if (Math.sign(newQuantity) === 1) {
      event.updaterecord.quantity = newQuantity;
      } else {
        // Set to zero if the result is negative
      event.updaterecord.quantity = 0;
      }
      console.log('value ' + JSON.stringify(newQuantity) + ' updaterecord' + JSON.stringify(event.updaterecord.quantity) );
      var payload = {
                    qryString : event.qryString,
                    record: event.updaterecord,
                    dbConfigPool : event.dbConfigPool
                       }
      console.log('update' + JSON.stringify(payload));
        var params = {
          FunctionName: 'updateInventory', 
          Payload: JSON.stringify(payload) 
        }
        console.log(' before invoke ' + JSON.stringify(params) )
        lambda.invoke(params, function(err, data) {
        console.log(' after invoke ' + JSON.stringify(params) )
          if (err) {
            console.log('err ' + err, err.stack); // an error occurred
            event.message = err + ' query error';
          } else {
              console.log(data);
          } // else
        }); // lambda invoke
  }
  }        // successful response
});

console.log(' end of function');
var completed = true;
context.callbackWaitsForEmptyEventLoop = false; 
callback(null, completed);

}

Apologies if the code is quite long. But I wanted to show that I did put a number of console.logs to monitor where is goes through. The cloudwatch logs only shows the first message before the first lambda.invoke and then it shows the last message for the end of the function.

I am also not seeing any log entries in cloudwatch for the three functions that has been invoked.

06/17 ok, since I am still unable to make this work, I simplified the code to just the following:

exports.handler = (event, context, callback) => {

var err = null;
var updatedRecord = false;
var responseDetail = {};
var payload = {
        qryString : event.qryString,
        record: event.updaterecord,
        dbConfigPool : event.dbConfigPool
           }

var params = {
  FunctionName: 'getInventory', 
  Payload: JSON.stringify(payload) 
}
console.log(' before invoke ' + JSON.stringify(params));
lambda.invoke(params, function(err, data) {
  if (err) {
  event.message = err + ' query error';
  callback(err,event.message);
  }
  else    { 

  console.log('success' + JSON.stringify(data));   
  console.log(' status code ' + JSON.stringify(data.StatusCode));
  console.log(' Payload ' + JSON.stringify(JSON.parse(data.Payload)));
  callback(null, data);

  }        // successful response
});

console.log(' end of function');
//    var completed = true;
//    context.callbackWaitsForEmptyEventLoop = false; 
//    callback(null, completed);

}

However, the function is timing out when I do my test. I have also given the role attached to the functions full Lambda and RDS access.

like image 593
leo c Avatar asked Jan 06 '23 21:01

leo c


2 Answers

First of all - welcome to callback hell! I will return to this later.

This is a simple code that invokes a lambda function.

var params = {
  FunctionName: 'LAMBDA_FUNCTION_NAME', /* required */
};
lambda.invoke(params, function(err, data) {
  if (err) {
   console.log(err, err.stack); // an error occurred
  }
  else {
   console.log(data);           // successful response
  }
});

lambda.invoke function has two parameters (params, function(err,data){..}). The first one is a simple JSON object. The second one is a function - a callback function. This function will be "called back" when the execution of lambda.invoke (you could think LAMBDA_FUNCTION_NAME) ends. If an error occurs it would be "stored" in err variable otherwise returned data will be stored in data variable (This is not the right explanation but I am trying to keep it simple here).

What happens when you want to invoke two lambda functions one after another?

var params1 = {
  FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
};
lambda.invoke(params1, function(err, data) {
  if (err) {
   console.log(err, err.stack); // an error occurred
  }
  else {
   console.log('Lambda function 1 invoked!');
   console.log(data);           // successful response
  }
});
var params2 = {
  FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
};
lambda.invoke(params2, function(err, data) {
  if (err) {
   console.log(err, err.stack); // an error occurred
  }
  else {
   console.log('Lambda function 2 invoked!');
   console.log(data);           // successful response
  }
});
console.log('I am done!');

Depending on execution time of LAMBDA_FUNCTION_1_NAME and LAMBDA_FUNCTION_2_NAME you could see different outputs like:

Lambda function 1 invoked!
I am done!

or

Lambda function 1 invoked!
Lambda function 2 invoked!
I am done!

or even

Lambda function 1 invoked!
I am done!
Lambda function 2 invoked!

This is because you are calling lambda.invoke and after that (without waiting) you are calling lambda.invoke again. After that (of course without waiting) the previous functions to end you are calling console.log('I am done!');

You could solve this by putting each function in previous function's callback. Something like this:

var params1 = {
  FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
};
lambda.invoke(params1, function(err, data) {
  if (err) {
   console.log(err, err.stack); // an error occurred
  }
  else {
   console.log('Lambda function 1 invoked!');
   console.log(data);           // successful response
   var params2 = {
    FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
   };
   lambda.invoke(params2, function(err, data) {
      if (err) {
       console.log(err, err.stack); // an error occurred
      }
      else {
       console.log('Lambda function 2 invoked!');
       console.log(data);           // successful response
       console.log('I am done!');
      }
    });
  }
});

That way your output would be:

Lambda function 1 invoked!
Lambda function 2 invoked!
I am done!

But if you want to invoke 3 or more functions one after another you will end up with nested code. This is the callback hell. You could re-write you code in that way. But in my opinion it is a good idea to check waterfall async library

async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
      // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
})

Pseudo code should look like this:

async.waterfall([
    function(callback1) {
        var params1 = {
            FunctionName: 'LAMBDA_FUNCTION_1_NAME', /* required */
        };
        lambda.invoke(params1, function(err, data) {
            if (err) {
                console.log(err, err.stack); // an error occurred
            }
            else {
                console.log('LAMBDA_FUNCTION_1_NAME finished!');
                callback1(null,data);
            }
        });
    },
    function(result_from_function_1, callback2) {
        console.log(result_from_function_1); // outputs result from LAMBDA_FUNCTION_1_NAME
        var params2 = {
            FunctionName: 'LAMBDA_FUNCTION_2_NAME', /* required */
        };
        lambda.invoke(params2, function(err, data) {
            if (err) {
                console.log(err, err.stack); // an error occurred
            }
            else {
                console.log('LAMBDA_FUNCTION_2_NAME finished!');
                callback2(null,data); 
            }
        });
    },
    function(result_from_function_2, callback3) {
        console.log(result_from_function_2); // outputs result from LAMBDA_FUNCTION_2_NAME
        var params3 = {
            FunctionName: 'LAMBDA_FUNCTION_3_NAME', /* required */
        };
        lambda.invoke(params3, function(err, data) {
            if (err) {
                console.log(err, err.stack); // an error occurred
            }
            else {
                console.log('LAMBDA_FUNCTION_3_NAME finished!');
                callback3(null,data);
            }
        });
    }
], function (err, result) {
    // result now equals LAMBDA_FUNCTION_3_NAME result
})

Please note that all callbacks (callback1, callback2 and callback3) could be with name "callback" only. I changed their names for better understanding.

like image 64
bpavlov Avatar answered Jan 08 '23 10:01

bpavlov


Think about what this does:

lambda.invoke(params, function(err, data) { ...

It starts "doing something" (the fact that happens to be invoking another lambda function is actually unimportant) and when "something" is done, it calls function(), right?

But it also returns immediately, and the next statement executes.

Meanwhile "something" is being handled by the asynchronous event loop.

The "next statement" is

console.log(' end of function');

Then, you're telling lambda "hey, I may have some async stuff going on, but don't worry about waiting for it to finish":

context.callbackWaitsForEmptyEventLoop = false; 

So the good news is your code is doing what is was written to do... but that turns out to be wrong.

Everywhere you have one of these two things...

// an error occurred
// successful response

...those are the places you should be calling callback(), not at the end of the handler function, which your code reaches very quickly, as it is supposed to.

You shouldn't need to use context.callbackWaitsForEmptyEventLoop = false; if you properly disconnect your database connections and all modules you include are well-behaved. While that has its purposes, there seem to be a lot of people using it to cover up for subtle unfinished business.

Or, for a tidier solution, where you only have one mention of the callback at the end and your function { function { function { nesting doesn't get so out of control, use async-waterfall.

like image 43
Michael - sqlbot Avatar answered Jan 08 '23 10:01

Michael - sqlbot