【问题标题】:Strange "memory" behaviour on AWS Lambda using with Node and Node-gyp使用 Node 和 Node-gyp 在 AWS Lambda 上出现奇怪的“记忆”行为
【发布时间】:2016-06-23 21:09:52
【问题描述】:

我有一个用 NodeJS 编写的 AWS Lambda,调用过程非常简单

NODEJS -> NodeModule(CPP) -> Extern C Function,这个设置是用 node-gyp 编译的。 你可以在https://drive.google.com/open?id=0B-2d-CuY5fkwS3lwdE96R1V6NEk看到完整的代码

CPP 节点模块调用 C 中的一个函数,该函数运行一个循环。并增加两个变量,一个在 C 函数的范围内,另一个在 C 代码的主范围内。

当您在本地运行此代码时。循环递增,两个变量都达到 11,正如预期的那样,你运​​行了多少。但是,当您在 AWS Lambda 中运行相同的代码时,每次调用都会有某种“内存”。并且一般范围内没有被重置的变量正在增加,是 11、22、33 等的倍数。

重复一遍,这永远不会在本地发生,两个变量始终为 11。 你可以通过运行构建 1. node-gyp clean 配置构建 2. node app.js(用于本地运行)

Index.js 用于 AWS Lambda

我真的无法解释这种行为? Lambda 是否有某种上下文或某种“内存”或缓存?

我已经为此创建了一个开放 API 网关。 (随时刷新并查看正在运行的“记忆”)。

https://koj2yva6z9.execute-api.us-east-1.amazonaws.com/dev/testLambdaCache

这种行为有时不一致,有时计数会重置。或者您可以通过上传新的 AWS lambda 代码来重置。

感谢您对这种奇怪行为的任何想法。

app.js(用于本地测试)

var addon = require('./build/Release/addon');
console.log(addon.testCache());
console.log(" addon method completed");

index.js(用于 lambda)

console.log('Loading function');

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

    var addon = require('./build/Release/addon');
    var returnvalue=addon.testCache();
    console.log(returnvalue);
    console.log(" addon method completed");
    callback(null, "success::"+returnvalue);
}

base.cc(C 代码的包装器)

#include <node.h>
#include <iostream>
#include <stdlib.h>
#include<string>
#include<cstring>

using namespace std;

extern "C" char* testCache();

namespace demo {

    using v8::FunctionCallbackInfo;
    using v8::HandleScope;
    using v8::Isolate;
    using v8::Local;
    using v8::Object;
    using v8::String;
    using v8::Value;
    using v8::Exception;

    void Method(const FunctionCallbackInfo<Value>& args) {
        Isolate* isolate = args.GetIsolate();
        cout << "C++ method started\n";
        char *returnStrings=NULL;
        returnStrings= testCache();
        args.GetReturnValue().Set(String::NewFromUtf8(isolate,  returnStrings ));
    }

    void init(Local<Object> exports) {
        NODE_SET_METHOD(exports, "testCache", Method);
    }

    NODE_MODULE(addon, init)

}  

decoder.c(运行循环的c代码)

int tmpCounter=0;


char* testCache()
{
    int counter=0;
    printf("Local counter --> %d  Global Counter --> %d\n",counter,tmpCounter); 
    for(int i=0;i <10; i++)
    {
        counter = counter +1;
        tmpCounter = tmpCounter +1;
        //sleep(1);
    }
    printf("Local counter --> %d  Global Counter --> %d\n",counter,tmpCounter); 

    counter=counter+1;
    tmpCounter=tmpCounter+1;

    char strCounter[100];
    char strTmpCounter[100];
    snprintf(strCounter, 16, "%d", counter);

    snprintf(strTmpCounter, 16, "%d", tmpCounter);

    char *returnString=NULL;
    returnString=malloc(1000);

    strcat(returnString, "Count:");
    strcat(returnString, strCounter);
    strcat(returnString, " TmpCount:");
    strcat(returnString, strTmpCounter);
    strcat(returnString, "\0");
    printf("%s\n",returnString);
    fflush(stdout);
    return returnString;

}

【问题讨论】:

  • 发布您的代码。你不能与世界分享你的 gdrive...顺便说一句,我可以猜到:本地范围的 var 是在没有初始化的情况下声明的,我的意思是:int local_val; 所以UB
  • 文件有多个,除了google drive有没有更好的分享方式?
  • 看看MCVE。开始使用循环发布c 函数。
  • 谢谢我也在这里添加了最小代码。

标签: c node.js amazon-web-services aws-lambda


【解决方案1】:

Lambda 是否提供某种上下文或某种“内存”或缓存?

我不会说它是“可用的”,因为它是可预测的或一致的,因为您不应该围绕它进行设计,但是是的,存在容器重用。

查看实际效果:

创建一个 uuid,或随机数,或类似的东西,并将其存储在处理程序外部的全局变量中。然后,在处理程序内部,记录它。您会看到相同的进程或进程组(由 uuid 标识)可能但不一定会处理时间上接近的后续请求。

假设你的函数完成了,一段时间过去了,然后你再次调用它。 Lambda 可能会重新创建一个新容器 [...]

但是,如果您没有更改代码并且没有经过太多时间,Lambda 可能会重用以前的容器。这为双方提供了一些性能优势:Lambda 可以跳过 nodejs 语言初始化,而您可以跳过代码中的初始化。如果沙盒被重用,您上次写入 /tmp 的文件仍然存在。

https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/

【讨论】:

  • 我了解容器重用。但为什么代码仍在上下文中。如果在本地运行相同的代码,则该值将被重置。如果在本地运行,它确实意味着它被无限重用的容器。在这种情况下,不仅容器被重用,我觉得代码从未离开上下文。
  • 我明白你在说什么。这是一个问题。在本地,两个迭代是否相互作用? var addon = require('./build/Release/addon'); console.log(addon.testCache()); var addon2 = require('./build/Release/addon'); console.log(addon2.testCache());
  • 是的,如果我在我的代码中使用插件实例,那么这是可以预期的,但是如果您查看 lambda 中的初始化程序,则处理程序正在实例化对象插件(因此不是全局实例化的)。所以每次调用处理程序时,插件都应该按照我的理解重新实例化。
【解决方案2】:

所有对strcattestCache 的调用都是UB,因为mallocated 内存未启动。

使用calloc更改它

returnString=calloc(1, 1000);

或将第一个更改为sprintf

sprintf(returnString, "Count:");

而且strcat(returnString, "\0");没用

最后总是检查 malloc 和朋友返回值,例如

if (returnString != NULL)
{
   // OK, MEMORY ALLOCATED
}
else
{
   // ERROR
}

【讨论】:

  • 我正在寻找的是“int tmpCounter=0;”的原因没有被重置并且“int counter = 0;”每次我启动 lambda 调用时都会重置。同样的代码在本地执行时会重置两个计数器我知道其他可以修复的小问题。
  • @sandeepzgk 为什么要花时间检查未定义的行为代码?更正您的 undefined 并重新测试,以便我们继续检查其他内容。
猜你喜欢
  • 2015-05-04
  • 1970-01-01
  • 2020-09-11
  • 2016-01-29
  • 2013-09-17
  • 2018-02-07
  • 1970-01-01
  • 2017-02-14
  • 1970-01-01
相关资源
最近更新 更多