-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
issue #1728 Using const or let in global scope have problem in nodejs… #1929
Conversation
core/nodejsActionBase/runner.js
Outdated
@@ -67,8 +67,7 @@ function NodeActionRunner() { | |||
} else { | |||
// The code is a plain old JS file. | |||
try { | |||
eval(message.code); | |||
thisRunner.userScriptMain = eval(message.main); | |||
thisRunner.userScriptMain = eval('(function(){\n' + message.code + '\nreturn ' + message.main + '})()'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum I think we don't want to continue to use eval.
I would preferred to write the message.code to a file, and then have same flow as zip actions by doing a require on the file.
This means appending exports.main = $message.main
to the file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another advantage of using a file is that the file can be named appropriately. the net is that any resulting stack traces from invocation of the action will show up with myAction.js:32
whereas with eval, stack traces will include the runner.js file name and line numbers, with anonymous:32 as the nested file:line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have also prototyped the file-based option, so I can easily switch to the alternative if desired.
However, require basically does the same thing as a function scope/eval except that it also adds additional parameters to the function, e.g., module
and exports
. These names did not exist until now. The file-based solution is fine and certainly more consistent with the zip flow, but at the same time a bigger departure from the current code. It is also bit trickier to get right as the action code could be already messing with exports
and module
. Specifically, I don't think exports.main = $message.main
is defensive enough.
I don't know openwhisk enough yet to know if the action file name can reuse the action name (in particular if valid action name => valid file name). I suspect a temporary subdirectory is also needed to isolate the arbitrary named file and I guess the random folder will show in the stack trace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes use same approach as zip, mktemp for now in tmpfs on random folder name.
I think just using action.js as file is good and simple instead of trying to make sure action name is safe to use as filename and encoding.
In terms of modules.exports.main I think it's fine and low risk or use exports.$message.main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the folder won't show up in the stack trace if we "cd" to that directory and require('./myAction.js')
?
Cold start latency? |
What you mean overhead of write ? I thought about that and I think is negible, and writing to memory instead of IO |
See this branch for a file-based implementation: https://github.com/tardieu/openwhisk/tree/issue-1728-file The code should probably be refactored, but it should be good enough for performance testing. |
I have updated this pull request to use a file/module based implementation for the two flows as requested. |
@csantanapr are these performance tests at the component level and are they in the src ? |
not in openwhisk, yet. |
I was simply raising the issue, yes of the cold start latency - intuitively, avoiding the write to disk would be prudent, and it's a one line fix. Do we really get significant benefits from writing to disk and loading the modules? We don't have performance tests that are easily consumable for benchmarking this change - yet. One suggestion is just to do it natively and re-evaluate later. |
I will try todo some comparisons locally, on what's the diff between eval and writing the string into memory and then doing a require. |
is this the suggestion? i.e. to avoid going to the filesystem?
|
On my macbook using a ramdisk I get a 100x performance penalty... var NodeActionRunner = require("./runner.js"); // master branch
var NodeActionRunnerFile = require("./runner-file.js"); // this pull request
var iterations = 400;
process.chdir('/Users/tardieu/tmp'); // a ramdisk
var message = { code: "function main() { console.log('hello'); }", binary: false, main: "main" };
console.time('master');
for (let i = 0; i < iterations; i++) {
var runner = new NodeActionRunner().init(message).catch(function (error) { console.log(error); });
}
console.timeEnd('master');
console.time('file');
for (let i = 0; i < iterations; i++) {
var runner = new NodeActionRunnerFile().init(message).catch(function (error) { console.log(error); });
}
console.timeEnd('file');
I suspect tmpfs on Linux would be better than this but this result is not encouraging. |
@tardieu wrt to tmpfs: In the past I did some performance testing on tmpfs vs. actual disk using various storage drivers. We are currently using overlay to store container filesystems which has a write speed of about 250M/s compared to tmpfs which peaks at about 700M/s so it is about 3 times faster in writing to a (single) disk. We haven't used tmpfs for user containers yet because RAM is the most expensive resource you can get on a host. Other than that it is a great improvement of course. |
I have implemented @starpit solution in: master...tardieu:issue-1728-vm It fixes the current issue. The performance is similar to the original code and the error messages are sensible. FWIW, using a |
Note that we once used |
iirc there was some discussion about whether we were really seeing a memory leak or gc overhead - but yeah what @markusthoemmes said... it bit us before. |
Given all this information - I'm inclined to accept the original solution as the best compromise. |
Good work @tardieu on getting to the bottom of this and doing the comparisons. Let's go with the original proposal with the function wrapper I'm ok with the trade off this give us the ability of using |
it looks like the c.f. nodejs/node#3113 |
…nodejs actions
I force pushed the original fix. |
PG1/1237 approved. |
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
…nodejs actions (apache#1929)
Fixes issue #1728 by combining the evaluation of message.code and message.main into a single eval scope (similar to nodejs require but without the need for a temporary file).