From 85a334d4d1d663dc7c92588ee6ba7439ede6e507 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Sun, 19 Jul 2020 19:11:28 -0400 Subject: [PATCH 1/4] Issue #931 fix for not allowing caching --- package-lock.json | 29 +++++++++++++++++-- package.json | 1 + src/lambda/handler-runner/HandlerRunner.js | 20 ++++++++----- .../ChildProcessRunner.js | 5 +++- .../childProcessHelper.js | 3 +- .../in-process-runner/InProcessRunner.js | 9 ++++-- .../handler-runner/java-runner/JavaRunner.js | 5 +++- .../python-runner/PythonRunner.js | 5 +++- .../handler-runner/ruby-runner/RubyRunner.js | 5 +++- .../WorkerThreadRunner.js | 5 +++- .../workerThreadHelper.js | 3 +- 11 files changed, 71 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81aa8a655..60de37bae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "serverless-offline", - "version": "6.4.0", + "version": "6.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3609,8 +3609,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "5.3.1", @@ -3733,6 +3732,30 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "clear-module": { + "version": "4.1.1", + "resolved": "https://kdev-341194987187.d.codeartifact.us-east-1.amazonaws.com:443/npm/MainDevelopment/clear-module/-/clear-module-4.1.1.tgz", + "integrity": "sha512-ng0E7LeODcT3QkazOckzZqbca+JByQy/Q2Z6qO24YsTp+pLxCfohGz2gJYJqZS0CWTX3LEUiHOqe5KlYeUbEMw==", + "requires": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "parent-module": { + "version": "2.0.0", + "resolved": "https://kdev-341194987187.d.codeartifact.us-east-1.amazonaws.com:443/npm/MainDevelopment/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "requires": { + "callsites": "^3.1.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://kdev-341194987187.d.codeartifact.us-east-1.amazonaws.com:443/npm/MainDevelopment/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + } + } + }, "cli-boxes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", diff --git a/package.json b/package.json index 750aff23a..3580e07f1 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "@hapi/hapi": "^18.4.1", "boxen": "^4.2.0", "chalk": "^3.0.0", + "clear-module": "^4.1.1", "cuid": "^2.1.8", "execa": "^4.0.0", "extend": "^3.0.2", diff --git a/src/lambda/handler-runner/HandlerRunner.js b/src/lambda/handler-runner/HandlerRunner.js index 3ce6fc8c8..c247c2c5e 100644 --- a/src/lambda/handler-runner/HandlerRunner.js +++ b/src/lambda/handler-runner/HandlerRunner.js @@ -21,7 +21,12 @@ export default class HandlerRunner { } async _loadRunner() { - const { useDocker, useChildProcesses, useWorkerThreads } = this.#options + const { + useDocker, + useChildProcesses, + useWorkerThreads, + allowCache, + } = this.#options const { functionKey, @@ -35,7 +40,7 @@ export default class HandlerRunner { if (useDocker) { const { default: DockerRunner } = await import('./docker-runner/index.js') - return new DockerRunner(this.#funOptions, this.#env) + return new DockerRunner(this.#funOptions, this.#env, allowCache) } if (supportedNodejs.has(runtime)) { @@ -43,7 +48,7 @@ export default class HandlerRunner { const { default: ChildProcessRunner } = await import( './child-process-runner/index.js' ) - return new ChildProcessRunner(this.#funOptions, this.#env) + return new ChildProcessRunner(this.#funOptions, this.#env, allowCache) } if (useWorkerThreads) { @@ -53,7 +58,7 @@ export default class HandlerRunner { const { default: WorkerThreadRunner } = await import( './worker-thread-runner/index.js' ) - return new WorkerThreadRunner(this.#funOptions, this.#env) + return new WorkerThreadRunner(this.#funOptions, this.#env, allowCache) } const { default: InProcessRunner } = await import( @@ -65,22 +70,23 @@ export default class HandlerRunner { handlerName, this.#env, timeout, + allowCache, ) } if (supportedPython.has(runtime)) { const { default: PythonRunner } = await import('./python-runner/index.js') - return new PythonRunner(this.#funOptions, this.#env) + return new PythonRunner(this.#funOptions, this.#env, allowCache) } if (supportedRuby.has(runtime)) { const { default: RubyRunner } = await import('./ruby-runner/index.js') - return new RubyRunner(this.#funOptions, this.#env) + return new RubyRunner(this.#funOptions, this.#env, allowCache) } if (supportedJava.has(runtime)) { const { default: JavaRunner } = await import('./java-runner/index.js') - return new JavaRunner(this.#funOptions, this.#env) + return new JavaRunner(this.#funOptions, this.#env, allowCache) } // TODO FIXME diff --git a/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js b/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js index 55393f214..800cc2552 100644 --- a/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +++ b/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js @@ -9,8 +9,9 @@ export default class ChildProcessRunner { #handlerName = null #handlerPath = null #timeout = null + #allowCache = false - constructor(funOptions, env) { + constructor(funOptions, env, allowCache) { const { functionKey, handlerName, handlerPath, timeout } = funOptions this.#env = env @@ -18,6 +19,7 @@ export default class ChildProcessRunner { this.#handlerName = handlerName this.#handlerPath = handlerPath this.#timeout = timeout + this.#allowCache = allowCache } // no-op @@ -37,6 +39,7 @@ export default class ChildProcessRunner { childProcess.send({ context, event, + allowCache: this.#allowCache, timeout: this.#timeout, }) diff --git a/src/lambda/handler-runner/child-process-runner/childProcessHelper.js b/src/lambda/handler-runner/child-process-runner/childProcessHelper.js index 38741dcb4..3102a5484 100644 --- a/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +++ b/src/lambda/handler-runner/child-process-runner/childProcessHelper.js @@ -23,7 +23,7 @@ process.on('uncaughtException', (err) => { const [, , functionKey, handlerName, handlerPath] = process.argv process.on('message', async (messageData) => { - const { context, event, timeout } = messageData + const { context, event, allowCache, timeout } = messageData // TODO we could probably cache this in the module scope? const inProcessRunner = new InProcessRunner( @@ -32,6 +32,7 @@ process.on('message', async (messageData) => { handlerName, process.env, timeout, + allowCache, ) let result diff --git a/src/lambda/handler-runner/in-process-runner/InProcessRunner.js b/src/lambda/handler-runner/in-process-runner/InProcessRunner.js index 6ec07b76e..4839abdee 100644 --- a/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +++ b/src/lambda/handler-runner/in-process-runner/InProcessRunner.js @@ -1,4 +1,5 @@ import { performance } from 'perf_hooks' +import clearModule from 'clear-module' export default class InProcessRunner { #env = null @@ -6,13 +7,15 @@ export default class InProcessRunner { #handlerName = null #handlerPath = null #timeout = null + #allowCache = false - constructor(functionKey, handlerPath, handlerName, env, timeout) { + constructor(functionKey, handlerPath, handlerName, env, timeout, allowCache) { this.#env = env this.#functionKey = functionKey this.#handlerName = handlerName this.#handlerPath = handlerPath this.#timeout = timeout + this.#allowCache = allowCache } // no-op @@ -36,7 +39,9 @@ export default class InProcessRunner { Object.assign(process.env, this.#env) // lazy load handler with first usage - + if (!this.#allowCache) { + clearModule(this.#handlerPath) + } const { [this.#handlerName]: handler } = await import(this.#handlerPath) if (typeof handler !== 'function') { diff --git a/src/lambda/handler-runner/java-runner/JavaRunner.js b/src/lambda/handler-runner/java-runner/JavaRunner.js index 8dc3c951a..69c08ee3a 100644 --- a/src/lambda/handler-runner/java-runner/JavaRunner.js +++ b/src/lambda/handler-runner/java-runner/JavaRunner.js @@ -10,8 +10,9 @@ export default class JavaRunner { #functionName = null #handler = null #deployPackage = null + #allowCache = false - constructor(funOptions, env) { + constructor(funOptions, env, allowCache) { const { functionName, handler, @@ -23,6 +24,7 @@ export default class JavaRunner { this.#functionName = functionName this.#handler = handler this.#deployPackage = functionPackage || servicePackage + this.#allowCache = allowCache } // no-op @@ -71,6 +73,7 @@ export default class JavaRunner { function: this.#functionName, jsonOutput: true, serverlessOffline: true, + allowCache: this.#allowCache, }) const httpOptions = { diff --git a/src/lambda/handler-runner/python-runner/PythonRunner.js b/src/lambda/handler-runner/python-runner/PythonRunner.js index f44583252..04d1a2805 100644 --- a/src/lambda/handler-runner/python-runner/PythonRunner.js +++ b/src/lambda/handler-runner/python-runner/PythonRunner.js @@ -13,14 +13,16 @@ export default class PythonRunner { #handlerName = null #handlerPath = null #runtime = null + #allowCache = false - constructor(funOptions, env) { + constructor(funOptions, env, allowCache) { const { handlerName, handlerPath, runtime } = funOptions this.#env = env this.#handlerName = handlerName this.#handlerPath = handlerPath this.#runtime = platform() === 'win32' ? 'python.exe' : runtime + this.#allowCache = allowCache if (process.env.VIRTUAL_ENV) { const runtimeDir = platform() === 'win32' ? 'Scripts' : 'bin' @@ -96,6 +98,7 @@ export default class PythonRunner { const input = stringify({ context, event, + allowCache: this.#allowCache, }) const onErr = (data) => { diff --git a/src/lambda/handler-runner/ruby-runner/RubyRunner.js b/src/lambda/handler-runner/ruby-runner/RubyRunner.js index d3aa72c5b..e9b89524b 100644 --- a/src/lambda/handler-runner/ruby-runner/RubyRunner.js +++ b/src/lambda/handler-runner/ruby-runner/RubyRunner.js @@ -10,13 +10,15 @@ export default class RubyRunner { #env = null #handlerName = null #handlerPath = null + #allowCache = false - constructor(funOptions, env) { + constructor(funOptions, env, allowCache) { const { handlerName, handlerPath } = funOptions this.#env = env this.#handlerName = handlerName this.#handlerPath = handlerPath + this.#allowCache = allowCache } // no-op @@ -64,6 +66,7 @@ export default class RubyRunner { const input = stringify({ context: _context, event, + allowCache: this.#allowCache, }) // console.log(input) diff --git a/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js b/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js index 238fa7a3b..f012cd852 100644 --- a/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +++ b/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js @@ -5,12 +5,14 @@ const workerThreadHelperPath = resolve(__dirname, './workerThreadHelper.js') export default class WorkerThreadRunner { #workerThread = null + #allowCache = false - constructor(funOptions /* options */, env) { + constructor(funOptions /* options */, env, allowCache) { // this._options = options const { functionKey, handlerName, handlerPath, timeout } = funOptions + this.#allowCache = allowCache this.#workerThread = new Worker(workerThreadHelperPath, { // don't pass process.env from the main process! env, @@ -51,6 +53,7 @@ export default class WorkerThreadRunner { { context, event, + allowCache: this.#allowCache, // port2 is part of the payload, for the other side to answer messages port: port2, }, diff --git a/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js b/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js index ee78da6b2..1304b3b54 100644 --- a/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +++ b/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js @@ -4,7 +4,7 @@ import InProcessRunner from '../in-process-runner/index.js' const { functionKey, handlerName, handlerPath } = workerData parentPort.on('message', async (messageData) => { - const { context, event, port, timeout } = messageData + const { context, event, port, timeout, allowCache } = messageData // TODO we could probably cache this in the module scope? const inProcessRunner = new InProcessRunner( @@ -13,6 +13,7 @@ parentPort.on('message', async (messageData) => { handlerName, process.env, timeout, + allowCache, ) let result From 4a4bdf6b5d7bde7bd2714fb858bfaeb605d2ea2b Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Sun, 19 Jul 2020 20:32:55 -0400 Subject: [PATCH 2/4] Test update for invoke's recursive nature --- tests/integration/lambda-invoke/serverless.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/integration/lambda-invoke/serverless.yml b/tests/integration/lambda-invoke/serverless.yml index 24c4800d8..16fc34e06 100644 --- a/tests/integration/lambda-invoke/serverless.yml +++ b/tests/integration/lambda-invoke/serverless.yml @@ -51,3 +51,7 @@ functions: invokedAsyncHandler: handler: lambdaInvokeAsyncHandler.invokedAsyncHandler + +custom: + serverless-offline: + allowCache: true From 6689c02dd44d2fd03fb9b54be090306f5bef8d36 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Wed, 26 Aug 2020 18:49:10 -0400 Subject: [PATCH 3/4] Updating allowing CLI options, help, and documentation --- README.md | 1 + package.json | 2 +- src/config/commandOptions.js | 3 +++ src/config/defaultOptions.js | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c75bb72a..fad6d7d68 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ All CLI options are optional: --useWorkerThreads Uses worker threads for handlers. Requires node.js v11.7.0 or higher --websocketPort WebSocket port to listen on. Default: 3001 --useDocker Run handlers in a docker container. +--allowCache Allows the code of lambda functions to cache if supported. ``` Any of the CLI options can be added to your `serverless.yml`. For example: diff --git a/package.json b/package.json index 3580e07f1..33d489964 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "list-contributors": "echo 'clone https://github.com/mgechev/github-contributors-list.git first, then run npm install' && cd ../github-contributors-list && node bin/githubcontrib --owner dherault --repo serverless-offline --sortBy contributions --showlogin true --sortOrder desc > contributors.md", "prepare": "npm run build", "prepublishOnly": "npm run lint && npm run build", - "test": "npm run build && jest --verbose --silent --runInBand", + "test": "npm run build && jest --runInBand", "test:unit": "jest --verbose --silent --runInBand --config jest.config.units.js", "test:cov": "npm run build && jest --coverage --silent --runInBand --collectCoverageFrom=src/**/*.js", "test:log": "npm run build && jest --verbose", diff --git a/src/config/commandOptions.js b/src/config/commandOptions.js index baacd44a2..5a29e3365 100644 --- a/src/config/commandOptions.js +++ b/src/config/commandOptions.js @@ -84,4 +84,7 @@ export default { functionCleanupIdleTimeSeconds: { usage: 'Number of seconds until an idle function is eligible for cleanup', }, + allowCache: { + usage: 'Allows the code of lambda functions to cache if supported', + }, } diff --git a/src/config/defaultOptions.js b/src/config/defaultOptions.js index 1da5b7404..0cb66dc97 100644 --- a/src/config/defaultOptions.js +++ b/src/config/defaultOptions.js @@ -24,4 +24,5 @@ export default { websocketPort: 3001, useDocker: false, functionCleanupIdleTimeSeconds: 60, + allowCache: false, } From 20bd9a488d88a467b057f083c0dc595b59fd2de4 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Wed, 26 Aug 2020 18:57:59 -0400 Subject: [PATCH 4/4] Fixing test script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f52cb36b..6d6551bb2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "list-contributors": "echo 'clone https://github.com/mgechev/github-contributors-list.git first, then run npm install' && cd ../github-contributors-list && node bin/githubcontrib --owner dherault --repo serverless-offline --sortBy contributions --showlogin true --sortOrder desc > contributors.md", "prepare": "npm run build", "prepublishOnly": "npm run lint && npm run build", - "test": "npm run build && jest --runInBand", + "test": "npm run build && jest --verbose --silent --runInBand", "test:unit": "jest --verbose --silent --runInBand --config jest.config.units.js", "test:cov": "npm run build && jest --coverage --silent --runInBand --collectCoverageFrom=src/**/*.js", "test:log": "npm run build && jest --verbose",