serverless-offline 9.2.6 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -21
- package/package.json +9 -7
- package/src/ServerlessOffline.js +1 -10
- package/src/config/commandOptions.js +3 -15
- package/src/config/defaultOptions.js +1 -4
- package/src/config/supportedRuntimes.js +11 -5
- package/src/events/http/HttpServer.js +1 -32
- package/src/lambda/LambdaFunction.js +53 -22
- package/src/lambda/LambdaFunctionPool.js +6 -5
- package/src/lambda/handler-runner/HandlerRunner.js +4 -17
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +0 -5
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +1 -5
- package/src/utils/index.js +4 -3
- package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +0 -75
- package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +0 -45
- package/src/lambda/handler-runner/child-process-runner/index.js +0 -1
package/README.md
CHANGED
|
@@ -121,7 +121,6 @@ All CLI options are optional:
|
|
|
121
121
|
--dockerNetwork The network that the Docker container will connect to
|
|
122
122
|
--dockerReadOnly Marks if the docker code layer should be read only. Default: true
|
|
123
123
|
--enforceSecureCookies Enforce secure cookies
|
|
124
|
-
--hideStackTraces Hide the stack trace on lambda failure. Default: false
|
|
125
124
|
--host -o Host name to listen on. Default: localhost
|
|
126
125
|
--httpPort Http port to listen on. Default: 3000
|
|
127
126
|
--httpsProtocol -H To enable HTTPS, specify directory (relative to your cwd, typically your project dir) for both cert.pem and key.pem files
|
|
@@ -134,10 +133,9 @@ All CLI options are optional:
|
|
|
134
133
|
--noStripTrailingSlashInUrl Don't strip trailing slash from http routes.
|
|
135
134
|
--noTimeout -t Disables the timeout feature.
|
|
136
135
|
--prefix -p Adds a prefix to every path, to send your requests to http://localhost:3000/[prefix]/[your_path] instead. Default: ''
|
|
137
|
-
--printOutput Turns on logging of your lambda outputs in the terminal.
|
|
138
136
|
--reloadHandler Reloads handler with each request.
|
|
139
137
|
--resourceRoutes Turns on loading of your HTTP proxy settings from serverless.yml
|
|
140
|
-
--
|
|
138
|
+
--terminateIdleLambdaTime Number of seconds until an idle function is eligible for termination.
|
|
141
139
|
--useDocker Run handlers in a docker container.
|
|
142
140
|
--useInProcess Run handlers in the same process as 'serverless-offline'.
|
|
143
141
|
--webSocketHardTimeout Set WebSocket hard timeout in seconds to reproduce AWS limits (https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html#apigateway-execution-service-websocket-limits-table). Default: 7200 (2 hours)
|
|
@@ -188,15 +186,6 @@ Lambda handlers for the `node.js` runtime can run in different execution modes w
|
|
|
188
186
|
- global state is being shared across lambda handlers as well as with `serverless` and `serverless-offline`
|
|
189
187
|
- easy debugging
|
|
190
188
|
|
|
191
|
-
#### child-processes (this option is deprecated, please use the default worker-threads instead)
|
|
192
|
-
|
|
193
|
-
- handlers run in a separate node.js instance
|
|
194
|
-
- memory is not being shared between handlers, memory consumption is therefore higher
|
|
195
|
-
- memory is being released when handlers reload or after usage
|
|
196
|
-
- environment (process.env) is not being shared across handlers
|
|
197
|
-
- global state is not being shared across handlers
|
|
198
|
-
- debugging more complicated
|
|
199
|
-
|
|
200
189
|
#### docker
|
|
201
190
|
|
|
202
191
|
- handlers run in a docker container
|
|
@@ -215,10 +204,10 @@ the Lambda handler process is running in a child process.
|
|
|
215
204
|
To use `Lambda.invoke` you need to set the lambda endpoint to the `serverless-offline` endpoint:
|
|
216
205
|
|
|
217
206
|
```js
|
|
218
|
-
|
|
219
|
-
|
|
207
|
+
import { env } from 'node:process'
|
|
208
|
+
import aws from 'aws-sdk'
|
|
220
209
|
|
|
221
|
-
const lambda = new Lambda({
|
|
210
|
+
const lambda = new aws.Lambda({
|
|
222
211
|
apiVersion: '2015-03-31',
|
|
223
212
|
// endpoint needs to be set only if it deviates from the default
|
|
224
213
|
endpoint: env.IS_OFFLINE
|
|
@@ -230,17 +219,17 @@ const lambda = new Lambda({
|
|
|
230
219
|
All your lambdas can then be invoked in a handler using
|
|
231
220
|
|
|
232
221
|
```js
|
|
233
|
-
|
|
234
|
-
|
|
222
|
+
import { Buffer } from 'node:buffer'
|
|
223
|
+
import aws from 'aws-sdk'
|
|
235
224
|
|
|
236
225
|
const { stringify } = JSON
|
|
237
226
|
|
|
238
|
-
const lambda = new Lambda({
|
|
227
|
+
const lambda = new aws.Lambda({
|
|
239
228
|
apiVersion: '2015-03-31',
|
|
240
229
|
endpoint: 'http://localhost:3002',
|
|
241
230
|
})
|
|
242
231
|
|
|
243
|
-
|
|
232
|
+
export async function handler() {
|
|
244
233
|
const clientContextData = stringify({ foo: 'foo' })
|
|
245
234
|
|
|
246
235
|
const params = {
|
|
@@ -623,9 +612,9 @@ Usage in order to send messages back to clients:
|
|
|
623
612
|
Or,
|
|
624
613
|
|
|
625
614
|
```js
|
|
626
|
-
|
|
615
|
+
import aws from 'aws-sdk'
|
|
627
616
|
|
|
628
|
-
const apiGatewayManagementApi = new ApiGatewayManagementApi({
|
|
617
|
+
const apiGatewayManagementApi = new aws.ApiGatewayManagementApi({
|
|
629
618
|
apiVersion: '2018-11-29',
|
|
630
619
|
endpoint: 'http://localhost:3001',
|
|
631
620
|
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dedicatedTo": "Blue, a great migrating bird.",
|
|
3
3
|
"name": "serverless-offline",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "10.0.0",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"
|
|
8
|
-
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./lambda": "./src/lambda/index.js"
|
|
10
|
+
},
|
|
9
11
|
"type": "module",
|
|
10
12
|
"scripts": {
|
|
11
13
|
"format": "eslint . --fix",
|
|
@@ -189,18 +191,18 @@
|
|
|
189
191
|
"@hapi/h2o2": "^9.1.0",
|
|
190
192
|
"@hapi/hapi": "^20.2.2",
|
|
191
193
|
"@serverless/utils": "^6.7.0",
|
|
192
|
-
"aws-sdk": "^2.
|
|
194
|
+
"aws-sdk": "^2.1209.0",
|
|
193
195
|
"boxen": "^7.0.0",
|
|
194
196
|
"chalk": "^5.0.1",
|
|
195
197
|
"execa": "^6.1.0",
|
|
196
198
|
"fs-extra": "^10.1.0",
|
|
197
199
|
"java-invoke-local": "0.0.6",
|
|
198
200
|
"js-string-escape": "^1.0.1",
|
|
199
|
-
"jsonpath-plus": "^7.
|
|
201
|
+
"jsonpath-plus": "^7.2.0",
|
|
200
202
|
"jsonschema": "^1.4.1",
|
|
201
203
|
"jsonwebtoken": "^8.5.1",
|
|
202
204
|
"jszip": "^3.10.1",
|
|
203
|
-
"luxon": "^3.0.
|
|
205
|
+
"luxon": "^3.0.3",
|
|
204
206
|
"node-fetch": "^3.2.10",
|
|
205
207
|
"node-schedule": "^2.1.0",
|
|
206
208
|
"object.hasown": "^1.1.1",
|
|
@@ -211,7 +213,7 @@
|
|
|
211
213
|
},
|
|
212
214
|
"devDependencies": {
|
|
213
215
|
"archiver": "^5.3.1",
|
|
214
|
-
"eslint": "^8.
|
|
216
|
+
"eslint": "^8.23.0",
|
|
215
217
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
216
218
|
"eslint-config-prettier": "^8.5.0",
|
|
217
219
|
"eslint-plugin-import": "^2.25.4",
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
defaultOptions,
|
|
7
7
|
SERVER_SHUTDOWN_TIMEOUT,
|
|
8
8
|
} from './config/index.js'
|
|
9
|
-
import { gray
|
|
9
|
+
import { gray } from './config/colors.js'
|
|
10
10
|
|
|
11
11
|
export default class ServerlessOffline {
|
|
12
12
|
#cliOptions = null
|
|
@@ -83,15 +83,6 @@ export default class ServerlessOffline {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
await Promise.all(eventModules)
|
|
86
|
-
|
|
87
|
-
if (this.#options.useChildProcesses) {
|
|
88
|
-
log.notice()
|
|
89
|
-
log.warning(
|
|
90
|
-
orange(`'--useChildProcesses' is deprecated and will be removed in the next major version. Worker threads, the current default, should provide the same if not an even better developer experience.
|
|
91
|
-
If you are experiencing any issues please let us know: https://github.com/dherault/serverless-offline/issues`),
|
|
92
|
-
)
|
|
93
|
-
log.notice()
|
|
94
|
-
}
|
|
95
86
|
}
|
|
96
87
|
|
|
97
88
|
async #ready() {
|
|
@@ -54,14 +54,6 @@ export default {
|
|
|
54
54
|
type: 'boolean',
|
|
55
55
|
usage: 'Enforce secure cookies',
|
|
56
56
|
},
|
|
57
|
-
functionCleanupIdleTimeSeconds: {
|
|
58
|
-
type: 'string',
|
|
59
|
-
usage: 'Number of seconds until an idle function is eligible for cleanup',
|
|
60
|
-
},
|
|
61
|
-
hideStackTraces: {
|
|
62
|
-
type: 'boolean',
|
|
63
|
-
usage: 'Hide the stack trace on lambda failure. Default: false',
|
|
64
|
-
},
|
|
65
57
|
host: {
|
|
66
58
|
shortcut: 'o',
|
|
67
59
|
type: 'string',
|
|
@@ -118,10 +110,6 @@ export default {
|
|
|
118
110
|
usage:
|
|
119
111
|
'Adds a prefix to every path, to send your requests to http://localhost:3000/prefix/[your_path] instead.',
|
|
120
112
|
},
|
|
121
|
-
printOutput: {
|
|
122
|
-
type: 'boolean',
|
|
123
|
-
usage: 'Outputs your lambda response to the terminal.',
|
|
124
|
-
},
|
|
125
113
|
reloadHandler: {
|
|
126
114
|
type: 'boolean',
|
|
127
115
|
usage: 'Reloads handler with each request.',
|
|
@@ -130,10 +118,10 @@ export default {
|
|
|
130
118
|
type: 'boolean',
|
|
131
119
|
usage: 'Turns on loading of your HTTP proxy settings from serverless.yml.',
|
|
132
120
|
},
|
|
133
|
-
|
|
134
|
-
type: '
|
|
121
|
+
terminateIdleLambdaTime: {
|
|
122
|
+
type: 'string',
|
|
135
123
|
usage:
|
|
136
|
-
'
|
|
124
|
+
'Number of seconds until an idle function is eligible for termination.',
|
|
137
125
|
},
|
|
138
126
|
useDocker: {
|
|
139
127
|
type: 'boolean',
|
|
@@ -13,8 +13,6 @@ export default {
|
|
|
13
13
|
dockerNetwork: null,
|
|
14
14
|
dockerReadOnly: true,
|
|
15
15
|
enforceSecureCookies: false,
|
|
16
|
-
functionCleanupIdleTimeSeconds: 60,
|
|
17
|
-
hideStackTraces: false,
|
|
18
16
|
host: 'localhost',
|
|
19
17
|
httpPort: 3000,
|
|
20
18
|
httpsProtocol: null,
|
|
@@ -26,10 +24,9 @@ export default {
|
|
|
26
24
|
noStripTrailingSlashInUrl: false,
|
|
27
25
|
noTimeout: false,
|
|
28
26
|
prefix: '',
|
|
29
|
-
printOutput: false,
|
|
30
27
|
reloadHandler: false,
|
|
31
28
|
resourceRoutes: false,
|
|
32
|
-
|
|
29
|
+
terminateIdleLambdaTime: 60,
|
|
33
30
|
useDocker: false,
|
|
34
31
|
useInProcess: false,
|
|
35
32
|
webSocketHardTimeout: 7200,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
|
|
3
3
|
|
|
4
4
|
// .NET CORE
|
|
5
|
-
export const supportedDotnetcore = new Set([
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
])
|
|
5
|
+
// export const supportedDotnetcore = new Set([
|
|
6
|
+
// 'dotnet6',
|
|
7
|
+
// 'dotnetcore3.1',
|
|
8
|
+
// ])
|
|
9
9
|
|
|
10
10
|
// GO
|
|
11
11
|
export const supportedGo = new Set(['go1.x'])
|
|
@@ -37,7 +37,7 @@ export const supportedRuby = new Set(['ruby2.7'])
|
|
|
37
37
|
// deprecated runtimes
|
|
38
38
|
// https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
|
|
39
39
|
export const supportedRuntimes = new Set([
|
|
40
|
-
...supportedDotnetcore,
|
|
40
|
+
// ...supportedDotnetcore,
|
|
41
41
|
...supportedGo,
|
|
42
42
|
...supportedJava,
|
|
43
43
|
...supportedNodejs,
|
|
@@ -45,3 +45,9 @@ export const supportedRuntimes = new Set([
|
|
|
45
45
|
...supportedPython,
|
|
46
46
|
...supportedRuby,
|
|
47
47
|
])
|
|
48
|
+
|
|
49
|
+
export const unsupportedDockerRuntimes = new Set([
|
|
50
|
+
'nodejs14.x',
|
|
51
|
+
'nodejs16.x',
|
|
52
|
+
'python3.9',
|
|
53
|
+
])
|
|
@@ -580,21 +580,8 @@ export default class HttpServer {
|
|
|
580
580
|
|
|
581
581
|
// Failure handling
|
|
582
582
|
let errorStatusCode = '502'
|
|
583
|
-
if (err) {
|
|
584
|
-
// Since the --useChildProcesses option loads the handler in
|
|
585
|
-
// a separate process and serverless-offline communicates with it
|
|
586
|
-
// over IPC, we are unable to catch JavaScript unhandledException errors
|
|
587
|
-
// when the handler code contains bad JavaScript. Instead, we "catch"
|
|
588
|
-
// it here and reply in the same way that we would have above when
|
|
589
|
-
// we lazy-load the non-IPC handler function.
|
|
590
|
-
if (this.#options.useChildProcesses && err.ipcException) {
|
|
591
|
-
return this.#reply502(
|
|
592
|
-
response,
|
|
593
|
-
`Error while loading ${functionKey}`,
|
|
594
|
-
err,
|
|
595
|
-
)
|
|
596
|
-
}
|
|
597
583
|
|
|
584
|
+
if (err) {
|
|
598
585
|
const errorMessage = (err.message || err).toString()
|
|
599
586
|
|
|
600
587
|
const found = errorMessage.match(/\[(\d{3})]/)
|
|
@@ -614,10 +601,6 @@ export default class HttpServer {
|
|
|
614
601
|
|
|
615
602
|
log.error(errorMessage)
|
|
616
603
|
|
|
617
|
-
if (!this.#options.hideStackTraces) {
|
|
618
|
-
log.error(err.stack)
|
|
619
|
-
}
|
|
620
|
-
|
|
621
604
|
for (const [key, value] of entries(endpoint.responses)) {
|
|
622
605
|
if (
|
|
623
606
|
key !== 'default' &&
|
|
@@ -894,20 +877,6 @@ export default class HttpServer {
|
|
|
894
877
|
}
|
|
895
878
|
}
|
|
896
879
|
|
|
897
|
-
let whatToLog = result
|
|
898
|
-
|
|
899
|
-
try {
|
|
900
|
-
whatToLog = stringify(result)
|
|
901
|
-
} catch {
|
|
902
|
-
// nothing
|
|
903
|
-
} finally {
|
|
904
|
-
if (this.#options.printOutput) {
|
|
905
|
-
log.notice(
|
|
906
|
-
err ? `Replying ${statusCode}` : `[${statusCode}] ${whatToLog}`,
|
|
907
|
-
)
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
880
|
return response
|
|
912
881
|
}
|
|
913
882
|
}
|
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { dirname, join, resolve } from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { performance } from 'node:perf_hooks'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
5
6
|
import { log } from '@serverless/utils/log.js'
|
|
6
7
|
import { emptyDir, ensureDir, remove } from 'fs-extra'
|
|
7
8
|
import jszip from 'jszip'
|
|
@@ -18,6 +19,8 @@ import { createUniqueId } from '../utils/index.js'
|
|
|
18
19
|
const { ceil } = Math
|
|
19
20
|
const { entries, fromEntries } = Object
|
|
20
21
|
|
|
22
|
+
const setTimeoutPromise = promisify(setTimeout)
|
|
23
|
+
|
|
21
24
|
export default class LambdaFunction {
|
|
22
25
|
#artifact = null
|
|
23
26
|
|
|
@@ -37,6 +40,8 @@ export default class LambdaFunction {
|
|
|
37
40
|
|
|
38
41
|
#handler = null
|
|
39
42
|
|
|
43
|
+
#handlerRunDone = false
|
|
44
|
+
|
|
40
45
|
#handlerRunner = null
|
|
41
46
|
|
|
42
47
|
#idleTimeStarted = null
|
|
@@ -49,13 +54,15 @@ export default class LambdaFunction {
|
|
|
49
54
|
|
|
50
55
|
#memorySize = null
|
|
51
56
|
|
|
57
|
+
#noTimeout = null
|
|
58
|
+
|
|
52
59
|
#region = null
|
|
53
60
|
|
|
54
61
|
#runtime = null
|
|
55
62
|
|
|
56
|
-
#
|
|
63
|
+
#status = 'IDLE' // can be 'BUSY' or 'IDLE'
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
#timeout = null
|
|
59
66
|
|
|
60
67
|
constructor(functionKey, functionDefinition, serverless, options) {
|
|
61
68
|
const {
|
|
@@ -71,28 +78,28 @@ export default class LambdaFunction {
|
|
|
71
78
|
|
|
72
79
|
const { handler, name, package: functionPackage = {} } = functionDefinition
|
|
73
80
|
|
|
74
|
-
|
|
81
|
+
// this._executionTimeout = null
|
|
82
|
+
this.#functionKey = functionKey
|
|
83
|
+
this.#functionName = name
|
|
84
|
+
this.#handler = handler
|
|
85
|
+
|
|
86
|
+
this.#memorySize =
|
|
75
87
|
functionDefinition.memorySize ||
|
|
76
88
|
provider.memorySize ||
|
|
77
89
|
DEFAULT_LAMBDA_MEMORY_SIZE
|
|
78
90
|
|
|
79
|
-
|
|
91
|
+
this.#noTimeout = options.noTimeout
|
|
92
|
+
|
|
93
|
+
this.#region = provider.region
|
|
94
|
+
|
|
95
|
+
this.#runtime =
|
|
80
96
|
functionDefinition.runtime || provider.runtime || DEFAULT_LAMBDA_RUNTIME
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
this.#timeout =
|
|
83
99
|
(functionDefinition.timeout ||
|
|
84
100
|
provider.timeout ||
|
|
85
101
|
DEFAULT_LAMBDA_TIMEOUT) * 1000
|
|
86
102
|
|
|
87
|
-
// this._executionTimeout = null
|
|
88
|
-
this.#functionKey = functionKey
|
|
89
|
-
this.#functionName = name
|
|
90
|
-
this.#handler = handler
|
|
91
|
-
this.#memorySize = memorySize
|
|
92
|
-
this.#region = provider.region
|
|
93
|
-
this.#runtime = runtime
|
|
94
|
-
this.#timeout = timeout
|
|
95
|
-
|
|
96
103
|
this.#verifySupportedRuntime()
|
|
97
104
|
|
|
98
105
|
const env = {
|
|
@@ -141,17 +148,17 @@ export default class LambdaFunction {
|
|
|
141
148
|
handler,
|
|
142
149
|
layers: functionDefinition.layers || [],
|
|
143
150
|
provider,
|
|
144
|
-
runtime,
|
|
151
|
+
runtime: this.#runtime,
|
|
145
152
|
serverlessPath,
|
|
146
153
|
servicePackage: servicePackage.artifact
|
|
147
154
|
? resolve(servicepath, servicePackage.artifact)
|
|
148
155
|
: undefined,
|
|
149
156
|
servicePath: servicepath,
|
|
150
|
-
timeout,
|
|
157
|
+
timeout: this.#timeout,
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
this.#handlerRunner = new HandlerRunner(funOptions, options, env)
|
|
154
|
-
this.#lambdaContext = new LambdaContext(name, memorySize)
|
|
161
|
+
this.#lambdaContext = new LambdaContext(name, this.#memorySize)
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
#startExecutionTimer() {
|
|
@@ -259,16 +266,33 @@ export default class LambdaFunction {
|
|
|
259
266
|
this.#initialized = true
|
|
260
267
|
}
|
|
261
268
|
|
|
262
|
-
get
|
|
263
|
-
return
|
|
269
|
+
get idleTimeInMillis() {
|
|
270
|
+
return performance.now() - this.#idleTimeStarted
|
|
264
271
|
}
|
|
265
272
|
|
|
266
273
|
get functionName() {
|
|
267
274
|
return this.#functionName
|
|
268
275
|
}
|
|
269
276
|
|
|
277
|
+
get status() {
|
|
278
|
+
return this.#status
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async #timeoutAndTerminate() {
|
|
282
|
+
await setTimeoutPromise(this.#timeout)
|
|
283
|
+
|
|
284
|
+
// if the handler has finished before the timeout don't terminate
|
|
285
|
+
if (this.#handlerRunDone) {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await this.#handlerRunner.cleanup()
|
|
290
|
+
|
|
291
|
+
throw new Error('Lambda timeout.')
|
|
292
|
+
}
|
|
293
|
+
|
|
270
294
|
async runHandler() {
|
|
271
|
-
this
|
|
295
|
+
this.#status = 'BUSY'
|
|
272
296
|
|
|
273
297
|
if (!this.#initialized) {
|
|
274
298
|
await this.#initialize()
|
|
@@ -283,7 +307,14 @@ export default class LambdaFunction {
|
|
|
283
307
|
|
|
284
308
|
this.#startExecutionTimer()
|
|
285
309
|
|
|
286
|
-
|
|
310
|
+
this.#handlerRunDone = false
|
|
311
|
+
|
|
312
|
+
const result = await Promise.race([
|
|
313
|
+
this.#handlerRunner.run(this.#event, context),
|
|
314
|
+
...(this.#noTimeout ? [] : [this.#timeoutAndTerminate()]),
|
|
315
|
+
])
|
|
316
|
+
|
|
317
|
+
this.#handlerRunDone = true
|
|
287
318
|
|
|
288
319
|
this.#stopExecutionTimer()
|
|
289
320
|
|
|
@@ -298,7 +329,7 @@ export default class LambdaFunction {
|
|
|
298
329
|
)
|
|
299
330
|
}
|
|
300
331
|
|
|
301
|
-
this
|
|
332
|
+
this.#status = 'IDLE'
|
|
302
333
|
this.#startIdleTimer()
|
|
303
334
|
|
|
304
335
|
return result
|
|
@@ -20,19 +20,20 @@ export default class LambdaFunctionPool {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
#startCleanTimer() {
|
|
23
|
+
const functionCleanupIdleTimeInMillis =
|
|
24
|
+
this.#options.terminateIdleLambdaTime * 1000
|
|
25
|
+
|
|
23
26
|
// NOTE: don't use setInterval, as it would schedule always a new run,
|
|
24
27
|
// regardless of function processing time and e.g. user action (debugging)
|
|
25
28
|
this.#timerRef = setTimeout(() => {
|
|
26
29
|
// console.log('run cleanup')
|
|
27
30
|
this.#pool.forEach((lambdaFunctions) => {
|
|
28
31
|
lambdaFunctions.forEach((lambdaFunction) => {
|
|
29
|
-
const {
|
|
30
|
-
// console.log(idleTimeInMinutes, status)
|
|
32
|
+
const { idleTimeInMillis, status } = lambdaFunction
|
|
31
33
|
|
|
32
34
|
if (
|
|
33
35
|
status === 'IDLE' &&
|
|
34
|
-
|
|
35
|
-
this.#options.functionCleanupIdleTimeSeconds / 60
|
|
36
|
+
idleTimeInMillis >= functionCleanupIdleTimeInMillis
|
|
36
37
|
) {
|
|
37
38
|
// console.log(`removed Lambda Function ${lambdaFunction.functionName}`)
|
|
38
39
|
lambdaFunction.cleanup()
|
|
@@ -43,7 +44,7 @@ export default class LambdaFunctionPool {
|
|
|
43
44
|
|
|
44
45
|
// schedule new timer
|
|
45
46
|
this.#startCleanTimer()
|
|
46
|
-
},
|
|
47
|
+
}, functionCleanupIdleTimeInMillis)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
#cleanupPool() {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
supportedNodejs,
|
|
6
6
|
supportedPython,
|
|
7
7
|
supportedRuby,
|
|
8
|
+
unsupportedDockerRuntimes,
|
|
8
9
|
} from '../../config/index.js'
|
|
9
10
|
|
|
10
11
|
export default class HandlerRunner {
|
|
@@ -23,25 +24,19 @@ export default class HandlerRunner {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
async #loadRunner() {
|
|
26
|
-
const {
|
|
27
|
+
const { useDocker, useInProcess } = this.#options
|
|
27
28
|
const { handler, runtime } = this.#funOptions
|
|
28
29
|
|
|
29
30
|
log.debug(`Loading handler... (${handler})`)
|
|
30
31
|
|
|
31
32
|
if (useDocker) {
|
|
32
|
-
|
|
33
|
-
if (runtime === 'nodejs14.x') {
|
|
33
|
+
if (unsupportedDockerRuntimes.has(runtime)) {
|
|
34
34
|
log.warning(
|
|
35
|
-
|
|
35
|
+
`"${runtime}" runtime is not supported with docker. See https://github.com/lambci/docker-lambda`,
|
|
36
36
|
)
|
|
37
37
|
throw new Error('Unsupported runtime')
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
if (runtime === 'python3.9') {
|
|
41
|
-
log.warning('"python3.9" runtime is not supported with docker.')
|
|
42
|
-
throw new Error('Unsupported runtime')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
40
|
const dockerOptions = {
|
|
46
41
|
host: this.#options.dockerHost,
|
|
47
42
|
hostServicePath: this.#options.dockerHostServicePath,
|
|
@@ -56,14 +51,6 @@ export default class HandlerRunner {
|
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
if (supportedNodejs.has(runtime)) {
|
|
59
|
-
if (useChildProcesses) {
|
|
60
|
-
const { default: ChildProcessRunner } = await import(
|
|
61
|
-
'./child-process-runner/index.js'
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
return new ChildProcessRunner(this.#funOptions, this.#env)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
54
|
if (useInProcess) {
|
|
68
55
|
const { default: InProcessRunner } = await import(
|
|
69
56
|
'./in-process-runner/index.js'
|
|
@@ -86,11 +86,6 @@ export default class InProcessRunner {
|
|
|
86
86
|
// no try/catch so that errors bubble up and are logged with root stack traces
|
|
87
87
|
const result = handler(event, lambdaContext, callback)
|
|
88
88
|
|
|
89
|
-
// // not a Promise, which is not supported by aws
|
|
90
|
-
// if (result == null || typeof result.then !== 'function') {
|
|
91
|
-
// throw new Error(`Synchronous function execution is not supported.`)
|
|
92
|
-
// }
|
|
93
|
-
|
|
94
89
|
const responses = [callbackWrapper]
|
|
95
90
|
|
|
96
91
|
// Promise was returned
|
|
@@ -83,7 +83,7 @@ export default class RubyRunner {
|
|
|
83
83
|
|
|
84
84
|
// console.log(input)
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const { stderr, stdout } = await execa(
|
|
87
87
|
runtime,
|
|
88
88
|
[
|
|
89
89
|
resolve(__dirname, 'invoke.rb'),
|
|
@@ -97,10 +97,6 @@ export default class RubyRunner {
|
|
|
97
97
|
},
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
-
const result = await ruby
|
|
101
|
-
|
|
102
|
-
const { stderr, stdout } = result
|
|
103
|
-
|
|
104
100
|
if (stderr) {
|
|
105
101
|
// TODO
|
|
106
102
|
|
package/src/utils/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export { default as checkDockerDaemon } from './checkDockerDaemon.js'
|
|
2
|
+
export { default as checkGoVersion } from './checkGoVersion.js'
|
|
1
3
|
export { default as createApiKey } from './createApiKey.js'
|
|
2
4
|
export { default as createUniqueId } from './createUniqueId.js'
|
|
3
5
|
export { default as detectExecutable } from './detectExecutable.js'
|
|
4
6
|
export { default as formatToClfTime } from './formatToClfTime.js'
|
|
7
|
+
export { default as generateHapiPath } from './generateHapiPath.js'
|
|
5
8
|
export { default as getHttpApiCorsConfig } from './getHttpApiCorsConfig.js'
|
|
6
9
|
export { default as jsonPath } from './jsonPath.js'
|
|
7
10
|
export { default as lowerCaseKeys } from './lowerCaseKeys.js'
|
|
@@ -10,9 +13,7 @@ export { default as parseMultiValueHeaders } from './parseMultiValueHeaders.js'
|
|
|
10
13
|
export { default as parseMultiValueQueryStringParameters } from './parseMultiValueQueryStringParameters.js'
|
|
11
14
|
export { default as parseQueryStringParameters } from './parseQueryStringParameters.js'
|
|
12
15
|
export { default as splitHandlerPathAndName } from './splitHandlerPathAndName.js'
|
|
13
|
-
|
|
14
|
-
export { default as checkGoVersion } from './checkGoVersion.js'
|
|
15
|
-
export { default as generateHapiPath } from './generateHapiPath.js'
|
|
16
|
+
|
|
16
17
|
// export { default as baseImage } from './baseImage.js'
|
|
17
18
|
|
|
18
19
|
const { isArray } = Array
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path'
|
|
2
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
-
import { log } from '@serverless/utils/log.js'
|
|
4
|
-
import { execaNode } from 'execa'
|
|
5
|
-
|
|
6
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
const childProcessHelperPath = resolve(__dirname, 'childProcessHelper.js')
|
|
8
|
-
|
|
9
|
-
export default class ChildProcessRunner {
|
|
10
|
-
#codeDir = null
|
|
11
|
-
|
|
12
|
-
#env = null
|
|
13
|
-
|
|
14
|
-
#functionKey = null
|
|
15
|
-
|
|
16
|
-
#handler = null
|
|
17
|
-
|
|
18
|
-
#servicePath = null
|
|
19
|
-
|
|
20
|
-
#timeout = null
|
|
21
|
-
|
|
22
|
-
constructor(funOptions, env) {
|
|
23
|
-
const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
|
|
24
|
-
|
|
25
|
-
this.#codeDir = codeDir
|
|
26
|
-
this.#env = env
|
|
27
|
-
this.#functionKey = functionKey
|
|
28
|
-
this.#handler = handler
|
|
29
|
-
this.#servicePath = servicePath
|
|
30
|
-
this.#timeout = timeout
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// no-op
|
|
34
|
-
// () => void
|
|
35
|
-
cleanup() {}
|
|
36
|
-
|
|
37
|
-
async run(event, context) {
|
|
38
|
-
const childProcess = execaNode(
|
|
39
|
-
childProcessHelperPath,
|
|
40
|
-
[this.#functionKey, this.#handler, this.#servicePath, this.#codeDir],
|
|
41
|
-
{
|
|
42
|
-
env: this.#env,
|
|
43
|
-
stdio: 'inherit',
|
|
44
|
-
},
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
childProcess.send({
|
|
48
|
-
context,
|
|
49
|
-
event,
|
|
50
|
-
timeout: this.#timeout,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
let result
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
result = await new Promise((res, rej) => {
|
|
57
|
-
childProcess.on('message', (data) => {
|
|
58
|
-
if (data.error) {
|
|
59
|
-
rej(data.error)
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
res(data.result)
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
} catch (err) {
|
|
66
|
-
// TODO
|
|
67
|
-
log.error(err)
|
|
68
|
-
throw err
|
|
69
|
-
} finally {
|
|
70
|
-
childProcess.kill()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return result
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import process, { argv } from 'node:process'
|
|
2
|
-
import InProcessRunner from '../in-process-runner/index.js'
|
|
3
|
-
|
|
4
|
-
// TODO handle this:
|
|
5
|
-
process.on('uncaughtException', (err) => {
|
|
6
|
-
const {
|
|
7
|
-
constructor: { name },
|
|
8
|
-
message,
|
|
9
|
-
stack,
|
|
10
|
-
} = err
|
|
11
|
-
|
|
12
|
-
process.send({
|
|
13
|
-
// process.send() can't serialize an Error object, so we help it out a bit
|
|
14
|
-
error: {
|
|
15
|
-
constructor: {
|
|
16
|
-
name,
|
|
17
|
-
},
|
|
18
|
-
message,
|
|
19
|
-
stack,
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const [, , functionKey, handler, servicePath, codeDir] = argv
|
|
25
|
-
|
|
26
|
-
process.on('message', async (messageData) => {
|
|
27
|
-
const { context, event, timeout } = messageData
|
|
28
|
-
|
|
29
|
-
// TODO we could probably cache this in the module scope?
|
|
30
|
-
const inProcessRunner = new InProcessRunner(
|
|
31
|
-
{
|
|
32
|
-
codeDir,
|
|
33
|
-
functionKey,
|
|
34
|
-
handler,
|
|
35
|
-
servicePath,
|
|
36
|
-
timeout,
|
|
37
|
-
},
|
|
38
|
-
process.env,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
const result = await inProcessRunner.run(event, context)
|
|
42
|
-
|
|
43
|
-
// TODO check serializeability (contains function, symbol etc)
|
|
44
|
-
process.send({ result })
|
|
45
|
-
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './ChildProcessRunner.js'
|