简体   繁体   English

AWS Lambda节点功能静默失败

[英]AWS Lambda Node Function Silently Fails

I have an AWS Lambda function served through the AWS API gateway. 我有一个通过AWS API网关提供的AWS Lambda函数。 Everything seems to work fine for the most part. 在大多数情况下,一切似乎都正常。 There is one function of our API that isn't working right. API的一项功能无法正常工作。 It is a webhook that receives incoming faxes and the emails us to let us know it came in. 这是一个Webhook,用于接收传入的传真和电子邮件,以通知我们它已经传入。

We have tested everything and this works perfectly on our local machine and even on providers like linode or digital ocean. 我们已经测试了所有内容,并且可以在我们的本地计算机上甚至在linode或digital ocean之类的提供程序上完美运行。 Our problem is only when deployed with AWS Lambda. 我们的问题仅在于与AWS Lambda一起部署时。

  • I have tried increasing memory and execution limits well beyond what is required. 我已经尝试增加内存和执行限制,超出了要求。
  • I've tried with multiple email methods and libraries. 我尝试了多种电子邮件方法和库。
  • I've tried with various function formats. 我尝试了各种功能格式。

The cloudwatch logs for lambda show the console.log(req.body.MediaUrl); lambda的cloudwatch日志显示console.log(req.body.MediaUrl); but there is no other output on stdout or stderr. 但是在stdout或stderr上没有其他输出。 Even if I put obvious flaws into it there will be no errors. 即使我加入了明显的缺陷,也不会有任何错误。 It's almost as if the function silently fails without explaining why. 几乎好像函数在不解释原因的情况下静默失败了。

I have been able to get it to work if I send 2-3 post requests all within about 1-2 seconds of another. 如果我在另一个请求的1-2秒内发送2-3个请求,我就能使其正常工作。 1 maybe 2 emails will be sent, but never all of them. 可能会发送1封2封电子邮件,但绝不会全部发送。 If a single post request is sent as is normal usage you never get an email. 如果按正常用法发送单个帖子请求,您将永远不会收到电子邮件。

// Define a handler for when the fax is initially sent
const nodemailer = require('nodemailer');
const aws = require('aws-sdk');

// Start Email Logic
// AWS access keys are built into Lambda, modify permissions of the executor role
aws.config.update({region: 'us-west-2'});

let transporter = nodemailer.createTransport({
  SES: new aws.SES({
    apiVersion: '2010-12-01',
  }),
 });

exports.received = function(req, res) {

  transporter.sendMail({
  from: '"Fax" <fax@domain.com>',
  to: process.env.FAX_ADDRESS,
  subject: 'A new fax from ' + req.body.From,
  text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
  if (err) {
    console.log(err);
  } else {
    console.log(info.envelope);
    console.log(info.messageId);
    console.log('Email Sent');
  }
});

// log the URL of the PDF received in the fax just in case email fails
console.log(req.body.MediaUrl);

  res.status(200);
  res.send();
};

BTW the above code has another file with all the proper express items to properly serve this. 顺便说一句,上面的代码有另一个文件,其中包含所有适当的快递项目,以正确地服务于此。 Again, it works perfectly on local, other servers and even sometimes when flooding lambda; 同样,它可以在本地服务器和其他服务器上完美运行,甚至有时在淹没lambda时也是如此; it never consistently works on Lambda though. 但是,它永远不会始终在Lambda上运行。

My guess is either I'm an idiot that needs to sleep or I'm missing something about Lambda here. 我的猜测是我是需要睡觉的白痴,还是我在这里缺少有关Lambda的东西。 Can anyone shed some light on what I'm missing? 谁能阐明我所缺少的内容?

EDIT : 编辑:

Thanks to Michael and Hen things fell into place. 多亏了迈克尔和亨恩,事情才得以解决。 When working with Lambda the lambda function will shut down the moment it gets through the main process logic. 使用Lambda时,lambda函数将在通过主流程逻辑时立即关闭。 It won't wait for pending asynchronous items to complete. 它不会等待未完成的异步项目完成。 The following change fixed my issue when using Express with Lambda. 以下更改解决了将Express与Lambda结合使用时的问题。

Notice that I've moved the mail functions to the end of the response and then moved the response status and send to inside of the mail function. 请注意,我已将邮件功能移至响应的末尾,然后将响应状态移至邮件功能的内部。 This is a one of many ways this can be solved but is sweet and simple for our needs. 这是可以解决的许多方法之一,但是对于我们的需求而言,它既甜美又简单。

exports.received = function(req, res) {

  // log the URL of the PDF received in the fax just in case email fails
  console.log(req.body.MediaUrl);

  transporter.sendMail({
  from: '"Fax" <fax@domain.com>',
  to: process.env.FAX_ADDRESS,
  subject: 'A new fax from ' + req.body.From,
  text: 'You can view the fax at this url:\n\n' + req.body.MediaUrl,
}, (err, info) => {
  if (err) {
    console.log(err);
    res.status(500);
    res.send();
  } else {
    console.log(info.envelope);
    console.log(info.messageId);
    console.log('Email Sent');
    res.status(200);
    res.send();
  }
});
};

It isn't silently failing, it's just going into suspended animation. 它不是无声地失败,它只是进入了暂停的动画。 Trying it again in short succession is actually allowing the earlier attempts to have enough runtime to finish. 实际上,短暂地重试实际上是允许较早的尝试具有足够的运行时间来完成。

The problem seems to be here: 问题似乎在这里:

res.status(200);
res.send();

These should be inside the callback from sendMail() . 这些应该在sendMail()的回调中。 You shouldn't return success until after success, but you're returning it before that, since sendMail() is asynchronous. 您不应该在成功之后才返回成功,但是在此之前您应该返回成功,因为sendMail()是异步的。

You can't expect Lambda to keep doing things for you after you've told it you're done. 告诉您Lambda完成后,您再也无法期望它继续为您做事。 Lambda is request/response.¹ Start function, do stuff, return response, stop. Lambda是请求/响应。¹启动功能,执行操作,返回响应,停止。 The process is frozen, and runtime billing stops. 该过程被冻结,并且运行时计费停止。 If there are things still on the event loop, then they just hang, depending on the value of context.callbackWaitsForEmptyEventLoop , which I doubt should/could be safely changed with express. 如果事件循环上还有东西,则它们会挂起,具体取决于context.callbackWaitsForEmptyEventLoop的值,我怀疑应该/可以通过express安全地对其进行更改。

If the next function invocation reuses the same container (a decision made by the infrastructure), then things you left running are still running when the container is thawed, so they may subsequently finish if the invocations are close enough together in time that sockets haven't timed out or any other potential wall-clock-time-related failures haven't occurred. 如果下一个函数调用重用了相同的容器(由基础结构决定),则在容器解冻时,您仍在运行的事情仍在运行,因此,如果调用在时间上还足够近,而套接字尚未结束,则它们可能随后完成。 t超时或没有发生任何其他与挂钟时间有关的潜在故障。

Only one invocation runs in any one container at any one time, but here you're leaving things running, so work from a previous invocation is actually finishing inside a later one. 一次只能在任何一个容器中运行一次调用,但是在这里您将使事情保持运行状态,因此上一次调用的工作实际上是在下一个调用中完成的。

If you duplicate the console.log(req.body.MediaUrl); 如果您复制console.log(req.body.MediaUrl); line both inside the callback and leaving it where it is now, assuming that value is different for each request, you should actually capture some evidence to confirm what I'm asserting, here. 在回调内部放置一行,然后将其保留在现在的位置,假设每个请求的值都不同,那么您实际上应该在此处捕获一些证据以确认我的主张。


¹ Lambda is request/response . ¹Lambda 是请求/响应 Lambda functions themselves can also be invoked asynchronously as "event" invocations, so that the "response" to the external caller is only an indication that the Lambda infrastructure has agreed to execute the function for you, in cases where you don't want or need to wait for the actual response, but that isn't relevant in this context. Lambda函数本身也可以作为“事件”调用异步调用,因此,在您不希望或不需要的情况下,对外部调用者的“响应”仅表示Lambda基础结构已同意为您执行该函数。需要等待实际响应,但这与上下文无关。 Lambda functions are still request/response, even though under this alternate model the response from the function is discarded, and the invocation is retried twice if an exception is thrown. Lambda函数仍然是请求/响应,即使在此备用模型下,该函数的响应也会被丢弃,并且如果引发异常,则调用将重试两次。

I had the same problem and solved this issue putting the await syntax for now. 我有同样的问题,解决了这个问题,把等待语法了。 For those who needs a better solution (and this is what I'll do later on), creating a specific lambda function will optimize the workflow and bring a better approach for the project. 对于那些需要更好解决方案的人(这是我稍后会做的事情),创建特定的lambda函数将优化工作流程并为项目带来更好的方法。

The difference between running your code locally or running within the lambda is that you need to take care of waiting for your async operations to complete before ending your lambda function. 在本地运行代码或在lambda中运行代码之间的区别在于,您需要在结束lambda函数之前先等待异步操作完成。

What you've experienced is the termination of the lambda before SendMail has ended, having it sent 1 email if you send couple of requests together is just a game of timing. 您所经历的是lambda在SendMail结束之前的终止,如果您一起发送几个请求,则发送1封电子邮件只是计时游戏。

I suggest you to use *await transporter.sendMail* and also be able to report back a real success / failure instead of always returning 200. 我建议您使用*await transporter.sendMail*并且还能够报告真实的成功/失败,而不是总是返回200。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM