This is an automated email from the ASF dual-hosted git repository.

jamesthomas pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/incubator-openwhisk-runtime-nodejs.git


The following commit(s) were added to refs/heads/master by this push:
     new b952414  Refactoring of the service and runner, no semantic changes 
intended. (#133)
b952414 is described below

commit b952414ffa321522298e033c749fcf260ca21abb
Author: rodric rabbah <[email protected]>
AuthorDate: Fri Jun 14 10:06:17 2019 -0400

    Refactoring of the service and runner, no semantic changes intended. (#133)
    
    * Refactor runner. No semantic change intended.
    
    * Replace vars with lets/consts.
    
    * Factor run method. No semantic changes intended.
    
    * Add invariant checks in doRun.
    
    * Javascript-isms. No semantic change intended.
    
    * Fix typo.
---
 core/nodejs10Action/Dockerfile         |   2 +-
 core/nodejs10Action/knative/Dockerfile |   2 +-
 core/nodejsActionBase/runner.js        | 251 ++++++++++++++++-----------------
 core/nodejsActionBase/src/service.js   | 119 ++++++++--------
 4 files changed, 181 insertions(+), 193 deletions(-)

diff --git a/core/nodejs10Action/Dockerfile b/core/nodejs10Action/Dockerfile
index a2dbaf6..9ec24ff 100644
--- a/core/nodejs10Action/Dockerfile
+++ b/core/nodejs10Action/Dockerfile
@@ -28,5 +28,5 @@ COPY ./package.json /
 RUN cd / && npm install --no-package-lock \
     && npm cache clean --force
 EXPOSE 8080
-# The flag --experimental-worker is to be use with care as it's an 
expirimental feature  more info here 
https://nodejs.org/docs/latest-v10.x/api/worker_threads.html
+# The flag --experimental-worker enables worker threads, see 
https://nodejs.org/docs/latest-v10.x/api/worker_threads.html
 CMD node --experimental-worker --expose-gc app.js
diff --git a/core/nodejs10Action/knative/Dockerfile 
b/core/nodejs10Action/knative/Dockerfile
index a519498..b1299cc 100644
--- a/core/nodejs10Action/knative/Dockerfile
+++ b/core/nodejs10Action/knative/Dockerfile
@@ -34,5 +34,5 @@ COPY ./core/nodejs10Action/package.json /
 RUN cd / && npm install --no-package-lock \
     && npm cache clean --force
 EXPOSE 8080
-# The flag --experimental-worker is to be use with care as it's an 
expirimental feature  more info here 
https://nodejs.org/docs/latest-v10.x/api/worker_threads.html
+# The flag --experimental-worker enables worker threads, see 
https://nodejs.org/docs/latest-v10.x/api/worker_threads.html
 CMD node --experimental-worker --expose-gc app.js
diff --git a/core/nodejsActionBase/runner.js b/core/nodejsActionBase/runner.js
index 42cce76..c65bcde 100644
--- a/core/nodejsActionBase/runner.js
+++ b/core/nodejsActionBase/runner.js
@@ -16,172 +16,157 @@
  */
 
 /**
- * Object which encapsulates a first-class function, the user code for
- * an action.
+ * Object which encapsulates a first-class function, the user code for an 
action.
  *
  * This file (runner.js) must currently live in root directory for 
nodeJsAction.
  */
-var util = require('util');
-var child_process = require('child_process');
-var fs = require('fs');
-var path = require('path');
+const fs = require('fs');
+const path = require('path');
 
-const serializeError = require('serialize-error');
+class NodeActionRunner {
 
-function NodeActionRunner() {
-    // Use this ref inside closures etc.
-    var thisRunner = this;
-
-    this.userScriptMain = undefined;
-
-    this.init = function (message) {
-        function assertMainIsFunction() {
-            if (typeof thisRunner.userScriptMain !== 'function') {
-                throw "Action entrypoint '" + message.main + "' is not a 
function.";
-            }
-        }
+    constructor() {
+        this.userScriptMain = undefined;
+    }
 
-        // Loading the user code.
+    /** Initializes the runner with the user function. */
+    init(message) {
         if (message.binary) {
             // The code is a base64-encoded zip file.
-            return unzipInTmpDir(message.code).then(function (moduleDir) {
-                let parts = splitMainHandler(message.main);
-                if (parts === undefined) {
-                    // message.main is guaranteed to not be empty but be 
defensive anyway
-                    return Promise.reject('Name of main function is not 
valid.');
-                }
-
-                // if there is only one property in the "main" handler, it is 
the function name
-                // and the module name is specified either from package.json 
or assumed to be index.js
-                let [index, main] = parts;
-
-                try {
-                    // Set the executable directory to the project dir
+            return unzipInTmpDir(message.code)
+                .then(moduleDir => {
+                    let parts = splitMainHandler(message.main);
+                    if (parts === undefined) {
+                        // message.main is guaranteed to not be empty but be 
defensive anyway
+                        return Promise.reject('Name of main function is not 
valid.');
+                    }
+
+                    // If there is only one property in the "main" handler, it 
is the function name
+                    // and the module name is specified either from 
package.json or assumed to be index.js.
+                    let [index, main] = parts;
+
+                    // Set the executable directory to the project dir.
                     process.chdir(moduleDir);
 
                     if (index === undefined && !fs.existsSync('package.json') 
&& !fs.existsSync('index.js')) {
                         return Promise.reject('Zipped actions must contain 
either package.json or index.js at the root.');
                     }
 
-                    //  The module to require
+                    //  The module to require.
                     let whatToRequire = index !== undefined ? 
path.join(moduleDir, index) : moduleDir;
-                    thisRunner.userScriptMain = eval('require("' + 
whatToRequire + '").' + main);
-                    assertMainIsFunction();
+                    this.userScriptMain = eval('require("' + whatToRequire + 
'").' + main);
+                    assertMainIsFunction(this.userScriptMain, message.main);
 
                     // The value 'true' has no special meaning here; the 
successful state is
                     // fully reflected in the successful resolution of the 
promise.
                     return true;
-                } catch (e) {
-                    return Promise.reject(e);
-                }
-            }).catch(function (error) {
-                return Promise.reject(error);
-            });
-        } else {
+                })
+                .catch(error => Promise.reject(error));
+        } else try {
             // The code is a plain old JS file.
+            this.userScriptMain = eval('(function(){' + message.code + 
'\nreturn ' + message.main + '})()');
+            assertMainIsFunction(this.userScriptMain, message.main);
+
+            return Promise.resolve(true); // See comment above about 'true'; 
it has no specific meaning.
+        } catch (e) {
+            return Promise.reject(e);
+        }
+    };
+
+    run(args) {
+        return new Promise((resolve, reject) => {
             try {
-                thisRunner.userScriptMain = eval('(function(){' + message.code 
+ '\nreturn ' + message.main + '})()');
-                assertMainIsFunction();
-                // See comment above about 'true'; it has no specific meaning.
-                return Promise.resolve(true);
+                var result = this.userScriptMain(args);
             } catch (e) {
-                return Promise.reject(e);
+                reject(e);
             }
-        }
+
+            this.finalizeResult(result, resolve);
+        });
     };
 
-    // Returns a Promise with the result of the user code invocation.
-    // The Promise is rejected iff the user code throws.
-    this.run = function (args) {
-        return new Promise(
-            function (resolve, reject) {
-                try {
-                    var result = thisRunner.userScriptMain(args);
-                } catch (e) {
-                    reject(e);
-                }
-
-                // Non-promises/undefined instantly resolve.
-                Promise.resolve(result).then(function (resolvedResult) {
-                    // This happens, e.g. if you just have "return;"
-                    if (typeof resolvedResult === "undefined") {
-                        resolvedResult = {};
-                    }
-                    resolve(resolvedResult);
-                }).catch(function (error) {
-                    // A rejected Promise from the user code maps into a
-                    // successful promise wrapping a whisk-encoded error.
-
-                    // Special case if the user just called `reject()`.
-                    if (!error) {
-                        resolve({error: {}});
-                    } else {
-                        resolve({error: serializeError(error)});
-                    }
-                });
+    finalizeResult(result, resolve) {
+        // Non-promises/undefined instantly resolve.
+        Promise.resolve(result).then(resolvedResult => {
+            // This happens, e.g. if you just have "return;"
+            if (typeof resolvedResult === "undefined") {
+                resolvedResult = {};
             }
-        );
-    };
+            resolve(resolvedResult);
+        }).catch(error => {
+            // A rejected Promise from the user code maps into a
+            // successful promise wrapping a whisk-encoded error.
+
+            // Special case if the user just called "reject()".
+            if (!error) {
+                resolve({error: {}});
+            } else {
+                const serializeError = require('serialize-error');
+                resolve({error: serializeError(error)});
+            }
+        });
+    }
+}
 
-    // Helper function to copy a base64-encoded zip file to a temporary 
location,
-    // decompress it into temporary directory, and return the name of that 
directory.
-    // Note that this makes heavy use of shell commands because:
-    //   1) Node 0.12 doesn't have many of the useful fs functions.
-    //   2) We know in which environment we're running.
-    function unzipInTmpDir(base64) {
-        var mkTempCmd = "mktemp -d XXXXXXXX";
-        return exec(mkTempCmd).then(function (tmpDir1) {
-            return new Promise(
-                function (resolve, reject) {
-                    var zipFile = path.join(tmpDir1, "action.zip");
-                    fs.writeFile(zipFile, base64, "base64", function (err) {
-                        if (err) {
-                            reject("There was an error reading the action 
archive.");
-                        }
-                        resolve(zipFile);
-                    });
-                }
-            );
-        }).then(function (zipFile) {
-            return exec(mkTempCmd).then(function (tmpDir2) {
-                return exec("unzip -qq " + zipFile + " -d " + 
tmpDir2).then(function (res) {
-                    return path.resolve(tmpDir2);
-                }).catch(function (error) {
-                    return Promise.reject("There was an error uncompressing 
the action archive.");
-                });
+/**
+ * Copies the base64 encoded zip file contents to a temporary location,
+ * decompresses it and returns the name of that directory.
+ *
+ * Note that this makes heavy use of shell commands because the environment is 
expected
+ * to provide the required executables.
+ */
+function unzipInTmpDir(zipFileContents) {
+    const mkTempCmd = "mktemp -d XXXXXXXX";
+    return exec(mkTempCmd).then(tmpDir => {
+        return new Promise((resolve, reject) => {
+            const zipFile = path.join(tmpDir, "action.zip");
+            fs.writeFile(zipFile, zipFileContents, "base64", err => {
+                if (!err) resolve(zipFile);
+                else reject("There was an error reading the action archive.");
             });
         });
-    }
+    }).then(zipFile => {
+        return exec(mkTempCmd).then(tmpDir => {
+            return exec("unzip -qq " + zipFile + " -d " + tmpDir)
+                .then(res => path.resolve(tmpDir))
+                .catch(error => Promise.reject("There was an error 
uncompressing the action archive."));
+        });
+    });
+}
 
-    // Helper function to run shell commands.
-    function exec(cmd) {
-        return new Promise(
-            function (resolve, reject) {
-                child_process.exec(cmd, function (error, stdout, stderr) {
-                    if (error) {
-                        reject(stderr.trim());
-                    } else {
-                        resolve(stdout.trim());
-                    }
-                });
+/** Helper function to run shell commands. */
+function exec(cmd) {
+    const child_process = require('child_process');
+
+    return new Promise((resolve, reject) => {
+        child_process.exec(cmd, (error, stdout, stderr) => {
+            if (!error) {
+                resolve(stdout.trim());
+            } else {
+                reject(stderr.trim());
             }
-        );
-    }
+        });
+    });
+}
 
-    /**
-     * Splits handler into module name and path to main.
-     * If the string contains no '.', return [ undefined, the string ].
-     * If the string contains one or more '.', return [ string up to first 
period, rest of the string after ].
-     */
-    function splitMainHandler(handler) {
-        let matches = handler.match(/^([^.]+)$|^([^.]+)\.(.+)$/);
-        if (matches && matches.length == 4) {
-            let index = matches[2];
-            let main = matches[3] || matches[1];
-            return [index, main]
-        } else return undefined
-    }
+/**
+ * Splits handler into module name and path to main.
+ * If the string contains no '.', return [ undefined, the string ].
+ * If the string contains one or more '.', return [ string up to first period, 
rest of the string after ].
+ */
+function splitMainHandler(handler) {
+    let matches = handler.match(/^([^.]+)$|^([^.]+)\.(.+)$/);
+    if (matches && matches.length == 4) {
+        let index = matches[2];
+        let main = matches[3] || matches[1];
+        return [index, main]
+    } else return undefined
+}
 
+function assertMainIsFunction(userScriptMain, main) {
+    if (typeof userScriptMain !== 'function') {
+        throw "Action entrypoint '" + main + "' is not a function.";
+    }
 }
 
 module.exports = NodeActionRunner;
diff --git a/core/nodejsActionBase/src/service.js 
b/core/nodejsActionBase/src/service.js
index e6beae6..11ffeb7 100644
--- a/core/nodejsActionBase/src/service.js
+++ b/core/nodejsActionBase/src/service.js
@@ -15,23 +15,22 @@
  * limitations under the License.
  */
 
-
-var NodeActionRunner = require('../runner');
+const NodeActionRunner = require('../runner');
 
 function NodeActionService(config) {
 
-    var Status = {
+    const Status = {
         ready: 'ready',
         starting: 'starting',
         running: 'running',
         stopped: 'stopped',
     };
 
-    // TODO: save the entire configuration for use by any of the route handlers
-    var status = Status.ready;
-    var ignoreRunStatus = config.allowConcurrent === undefined ? false : 
config.allowConcurrent.toLowerCase() === 'true';
-    var server = undefined;
-    var userCodeRunner = undefined;
+    const ignoreRunStatus = config.allowConcurrent === undefined ? false : 
config.allowConcurrent.toLowerCase() === 'true';
+
+    let status = Status.ready;
+    let server = undefined;
+    let userCodeRunner = undefined;
 
     function setStatus(newStatus) {
         if (status !== Status.stopped) {
@@ -84,34 +83,32 @@ function NodeActionService(config) {
      *  req.body = { main: String, code: String, binary: Boolean }
      */
     this.initCode = function initCode(req) {
-
         if (status === Status.ready && userCodeRunner === undefined) {
-
             setStatus(Status.starting);
 
-            var body = req.body || {};
-            var message = body.value || {};
+            let body = req.body || {};
+            let message = body.value || {};
 
             if (message.main && message.code && typeof message.main === 
'string' && typeof message.code === 'string') {
-                return doInit(message).then(function(result) {
+                return doInit(message).then(_ => {
                     setStatus(Status.ready);
                     return responseMessage(200, { OK: true });
-                }).catch(function(error) {
+                }).catch(error => {
                     setStatus(Status.stopped);
-                    var errStr = 'Initialization has failed due to: ' + 
error.stack ? String(error.stack) : error;
+                    let errStr = `Initialization has failed due to: 
${error.stack ? String(error.stack) : error}`;
                     return Promise.reject(errorMessage(502, errStr));
                 });
             } else {
                 setStatus(Status.ready);
-                var msg = 'Missing main/no code to execute.';
+                let msg = 'Missing main/no code to execute.';
                 return Promise.reject(errorMessage(403, msg));
             }
         } else if (userCodeRunner !== undefined) {
-            var msg = 'Cannot initialize the action more than once.';
+            let msg = 'Cannot initialize the action more than once.';
             console.error('Internal system error:', msg);
             return Promise.reject(errorMessage(403, msg));
         } else {
-            var msg = 'System not ready, status is ' + status + '.';
+            let msg = `System not ready, status is ${status}.`;
             console.error('Internal system error:', msg);
             return Promise.reject(errorMessage(403, msg));
         }
@@ -126,12 +123,22 @@ function NodeActionService(config) {
      * req.body = { value: Object, meta { activationId : int } }
      */
     this.runCode = function runCode(req) {
-        if (status === Status.ready) {
+        if (status === Status.ready && userCodeRunner !== undefined) {
             if (!ignoreRunStatus) {
                 setStatus(Status.running);
             }
 
-            return doRun(req).then(function(result) {
+            // these are defensive checks against the expected interface 
invariants
+            let msg = req && req.body || {};
+            if (msg.value === null || msg.value === undefined) {
+                msg.value = {};
+            } else if (typeof msg.value !== 'object') {
+                let errStr = `Internal system error: the argument must be a 
dictionary but has type '${typeof msg.value}'.`;
+                console.error('Internal system error:', errStr);
+                return Promise.reject(errorMessage(403, errStr));
+            }
+
+            return doRun(msg).then(result => {
                 if (!ignoreRunStatus) {
                     setStatus(Status.ready);
                 }
@@ -140,13 +147,13 @@ function NodeActionService(config) {
                 } else {
                     return responseMessage(200, result);
                 }
-            }).catch(function(error) {
-                var msg = 'An error has occurred: ' + error;
+            }).catch(error => {
+                let msg = `An error has occurred: ${error}`;
                 setStatus(Status.stopped);
                 return Promise.reject(errorMessage(502, msg));
             });
         } else {
-            var msg = 'System not ready, status is ' + status + '.';
+            let msg = userCodeRunner ? `System not ready, status is 
${status}.` : 'System not initialized.';
             console.error('Internal system error:', msg);
             return Promise.reject(errorMessage(403, msg));
         }
@@ -155,43 +162,41 @@ function NodeActionService(config) {
     function doInit(message) {
         userCodeRunner = new NodeActionRunner();
 
-        return userCodeRunner.init(message).then(function(result) {
-            // 'true' has no particular meaning here. The fact that the promise
-            // is resolved successfully in itself carries the intended message
-            // that initialization succeeded.
-            return true;
-        }).catch(function(error) {
-            // emit error to activation log then flush the logs as this
-            // is the end of the activation
-            console.error('Error during initialization:', error);
-            writeMarkers();
-            return Promise.reject(error);
-        });
+        return userCodeRunner
+            .init(message)
+            // returning 'true' has no particular meaning here. The fact that 
the promise resolved
+            // successfully in itself carries the intended message that 
initialization succeeded.
+            .then(_ => true)
+            // emit error to activation log then flush the logs as this is the 
end of the activation
+            .catch(error => {
+                console.error('Error during initialization:', error);
+                writeMarkers();
+                return Promise.reject(error);
+            });
     }
 
-    function doRun(req) {
-        var msg = req && req.body || {};
+    function doRun(msg) {
         // Move per-activation keys to process env. vars with __OW_ (reserved) 
prefix
-        Object.keys(msg).forEach(
-            function(k) {
-                if (typeof msg[k] === 'string' && k !== 'value'){
-                    var envVariable = '__OW_' + k.toUpperCase();
-                    process.env['__OW_' + k.toUpperCase()] = msg[k];
-                }
-            }
-        );
-
-        return userCodeRunner.run(msg.value).then(function(result) {
-            if (typeof result !== 'object') {
-                console.error('Result must be of type object but has type "' + 
typeof result + '":', result);
+        Object.keys(msg).forEach(k => {
+            if (typeof msg[k] === 'string' && k !== 'value') {
+                let envVariable = '__OW_' + k.toUpperCase();
+                process.env[envVariable] = msg[k];
             }
-            writeMarkers();
-            return result;
-        }).catch(function(error) {
-            console.error(error);
-            writeMarkers();
-            return Promise.reject(error);
         });
+
+        return userCodeRunner
+            .run(msg.value)
+            .then(result => {
+                if (typeof result !== 'object') {
+                    console.error(`Result must be of type object but has type 
"${typeof result}":`, result);
+                }
+                writeMarkers();
+                return result;
+            }).catch(error => {
+                console.error(error);
+                writeMarkers();
+                return Promise.reject(error);
+            });
     }
 
     function writeMarkers() {
@@ -200,8 +205,6 @@ function NodeActionService(config) {
     }
 }
 
-NodeActionService.getService = function(config) {
-    return new NodeActionService(config);
-};
+NodeActionService.getService = config => new NodeActionService(config);
 
 module.exports = NodeActionService;

Reply via email to