@intelligems/sst 2.49.6-ig.1 → 2.49.6-ig.10
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/cli/commands/dev.js +69 -3
- package/cli/sst.js +0 -1
- package/iot.d.ts +31 -0
- package/iot.js +169 -8
- package/package.json +2 -2
- package/runtime/debug-bridge-logging.d.ts +24 -0
- package/runtime/debug-bridge-logging.js +98 -0
- package/runtime/debug-file-logger.d.ts +80 -0
- package/runtime/debug-file-logger.js +148 -0
- package/runtime/event-trace-logging.d.ts +27 -0
- package/runtime/event-trace-logging.js +69 -0
- package/runtime/handlers/node.js +62 -21
- package/runtime/handlers.d.ts +6 -2
- package/runtime/handlers.js +130 -59
- package/runtime/iot.js +31 -4
- package/runtime/mono-build-config.d.ts +58 -0
- package/runtime/mono-build-config.js +104 -0
- package/runtime/request-utils.d.ts +21 -0
- package/runtime/request-utils.js +102 -0
- package/runtime/runtime.d.ts +1 -0
- package/runtime/server.d.ts +4 -1
- package/runtime/server.js +122 -29
- package/runtime/worker-pool-logging.d.ts +29 -0
- package/runtime/worker-pool-logging.js +185 -0
- package/runtime/workers.d.ts +39 -7
- package/runtime/workers.js +831 -38
- package/support/bridge/live-lambda.mjs +38 -38
- package/support/nodejs-runtime/index.mjs +44 -11
- package/support/python-runtime/runtime.py +116 -45
- package/README.md +0 -43
- package/package.json.bak +0 -156
package/runtime/iot.js
CHANGED
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
import { useBus } from "../bus.js";
|
|
2
|
-
import { useIOT } from "../iot.js";
|
|
2
|
+
import { useIOT, useIOTControl } from "../iot.js";
|
|
3
3
|
import { lazy } from "../util/lazy.js";
|
|
4
|
+
import { logInvokeTrace } from "./worker-pool-logging.js";
|
|
5
|
+
import { logIot } from "./debug-bridge-logging.js";
|
|
6
|
+
import { logEventTrace } from "./event-trace-logging.js";
|
|
4
7
|
export const useIOTBridge = lazy(async () => {
|
|
5
8
|
const bus = useBus();
|
|
6
9
|
const iot = await useIOT();
|
|
10
|
+
// The ack goes out on its own socket so it can never queue behind a response
|
|
11
|
+
// body. See `useIOTControl`.
|
|
12
|
+
const control = await useIOTControl();
|
|
7
13
|
const topic = `${iot.prefix}/events`;
|
|
8
14
|
bus.subscribe("function.success", async (evt) => {
|
|
9
|
-
|
|
15
|
+
const { workerID, requestID } = evt.properties;
|
|
16
|
+
logIot(`reqId=${requestID?.slice(0, 8)} Publishing function.success to worker ${workerID.slice(0, 8)}`);
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
await iot.publish(topic + "/" + workerID, "function.success", evt.properties);
|
|
19
|
+
logIot(`reqId=${requestID?.slice(0, 8)} function.success published in ${Date.now() - startTime}ms`);
|
|
10
20
|
});
|
|
11
21
|
bus.subscribe("function.error", async (evt) => {
|
|
12
|
-
|
|
22
|
+
const { workerID, requestID } = evt.properties;
|
|
23
|
+
logIot(`reqId=${requestID?.slice(0, 8)} Publishing function.error to worker ${workerID.slice(0, 8)}`);
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
await iot.publish(topic + "/" + workerID, "function.error", evt.properties);
|
|
26
|
+
logIot(`reqId=${requestID?.slice(0, 8)} function.error published in ${Date.now() - startTime}ms`);
|
|
13
27
|
});
|
|
14
28
|
bus.subscribe("function.ack", async (evt) => {
|
|
15
|
-
|
|
29
|
+
const { workerID, requestID, functionID } = evt.properties;
|
|
30
|
+
logIot(`reqId=${requestID?.slice(0, 8)} Publishing function.ack to worker ${workerID.slice(0, 8)}`);
|
|
31
|
+
logInvokeTrace("IOT_ACK_START", workerID, `worker=${workerID.slice(0, 8)}`);
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
await control.publish(topic + "/" + workerID, "function.ack", evt.properties);
|
|
34
|
+
const elapsed = Date.now() - startTime;
|
|
35
|
+
logIot(`reqId=${requestID?.slice(0, 8)} function.ack published in ${elapsed}ms`);
|
|
36
|
+
logInvokeTrace("IOT_ACK_DONE", workerID, `worker=${workerID.slice(0, 8)}`);
|
|
37
|
+
logEventTrace("IOT_ACK", {
|
|
38
|
+
requestID,
|
|
39
|
+
functionID,
|
|
40
|
+
workerID,
|
|
41
|
+
elapsed,
|
|
42
|
+
});
|
|
16
43
|
});
|
|
17
44
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global mono build configuration for SST dev mode.
|
|
3
|
+
*
|
|
4
|
+
* Mono build mode bundles all Lambda handlers into a single file (.mono-build/index.mjs)
|
|
5
|
+
* instead of building each handler individually. This significantly speeds up dev mode
|
|
6
|
+
* by sharing compilation work across all handlers.
|
|
7
|
+
*
|
|
8
|
+
* Detection latches on: the first check that finds the bundle enables mono
|
|
9
|
+
* build for the rest of the session. It deliberately does NOT latch off, so a
|
|
10
|
+
* caller arriving before the bundle has been written cannot disable it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const useMonoBuildConfig: () => {
|
|
13
|
+
/**
|
|
14
|
+
* Whether mono build mode is enabled. Re-checked until the bundle is
|
|
15
|
+
* found, so a check made before it was written does not stick.
|
|
16
|
+
* When true, all Node.js handlers use the shared .mono-build bundle.
|
|
17
|
+
*/
|
|
18
|
+
readonly enabled: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The mono bundle directory (.mono-build)
|
|
21
|
+
*/
|
|
22
|
+
dir: string;
|
|
23
|
+
/**
|
|
24
|
+
* The mono bundle entry file (.mono-build/index.mjs)
|
|
25
|
+
*/
|
|
26
|
+
entryFile: string;
|
|
27
|
+
/**
|
|
28
|
+
* The handler string to use for mono build mode
|
|
29
|
+
*/
|
|
30
|
+
handler: string;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a build output path represents a mono build.
|
|
33
|
+
* This is useful when you have a build result and need to determine its type.
|
|
34
|
+
*/
|
|
35
|
+
isMonoBuildPath(buildOut: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get the pool key for a function based on mono build status.
|
|
38
|
+
* For mono build: shared key (all functions share workers)
|
|
39
|
+
* For non-mono build: per-function key
|
|
40
|
+
*/
|
|
41
|
+
getPoolKey(functionID: string, runtime: string, buildOut: string): {
|
|
42
|
+
key: string;
|
|
43
|
+
isShared: boolean;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Quick check for mono build mode without full config initialization.
|
|
48
|
+
* Use this for simple boolean checks where you don't need the full config.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isMonoBuildEnabled(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Get the mono build directory path.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getMonoBuildDir(): string;
|
|
55
|
+
/**
|
|
56
|
+
* Check if a path represents a mono build output.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isMonoBuildPath(buildOut: string): boolean;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fsSync from "fs";
|
|
3
|
+
import { useProject } from "../project.js";
|
|
4
|
+
import { lazy } from "../util/lazy.js";
|
|
5
|
+
import { Logger } from "../logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Global mono build configuration for SST dev mode.
|
|
8
|
+
*
|
|
9
|
+
* Mono build mode bundles all Lambda handlers into a single file (.mono-build/index.mjs)
|
|
10
|
+
* instead of building each handler individually. This significantly speeds up dev mode
|
|
11
|
+
* by sharing compilation work across all handlers.
|
|
12
|
+
*
|
|
13
|
+
* Detection latches on: the first check that finds the bundle enables mono
|
|
14
|
+
* build for the rest of the session. It deliberately does NOT latch off, so a
|
|
15
|
+
* caller arriving before the bundle has been written cannot disable it.
|
|
16
|
+
*/
|
|
17
|
+
export const useMonoBuildConfig = lazy(() => {
|
|
18
|
+
const project = useProject();
|
|
19
|
+
const monoBundleDir = path.join(project.paths.root, ".mono-build");
|
|
20
|
+
const monoBundlePath = path.join(monoBundleDir, "index.mjs");
|
|
21
|
+
/**
|
|
22
|
+
* Latches on true, never on false.
|
|
23
|
+
*
|
|
24
|
+
* This was a single `existsSync` at first call, cached for the session by
|
|
25
|
+
* `lazy`. Dev start deletes `.mono-build/index.mjs` and then rebuilds it, so
|
|
26
|
+
* any caller landing in that window — typically an invocation that beat the
|
|
27
|
+
* bundle to disk — pinned this to false for the whole session. Every handler
|
|
28
|
+
* then took the per-function esbuild path, which does not carry the mono
|
|
29
|
+
* build's `external` list, and failed to resolve packages the mono bundle
|
|
30
|
+
* never opens. The bundle finishing changed nothing, because the flag had
|
|
31
|
+
* already been decided, so the session stayed broken until restarted.
|
|
32
|
+
*
|
|
33
|
+
* Re-checking until it is found costs one `existsSync` per call for the few
|
|
34
|
+
* seconds before the bundle lands, and nothing afterwards.
|
|
35
|
+
*/
|
|
36
|
+
let enabled = false;
|
|
37
|
+
const isEnabled = () => {
|
|
38
|
+
if (!enabled && fsSync.existsSync(monoBundlePath)) {
|
|
39
|
+
enabled = true;
|
|
40
|
+
Logger.debug("Mono build mode enabled:", monoBundlePath);
|
|
41
|
+
}
|
|
42
|
+
return enabled;
|
|
43
|
+
};
|
|
44
|
+
isEnabled();
|
|
45
|
+
return {
|
|
46
|
+
/**
|
|
47
|
+
* Whether mono build mode is enabled. Re-checked until the bundle is
|
|
48
|
+
* found, so a check made before it was written does not stick.
|
|
49
|
+
* When true, all Node.js handlers use the shared .mono-build bundle.
|
|
50
|
+
*/
|
|
51
|
+
get enabled() {
|
|
52
|
+
return isEnabled();
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* The mono bundle directory (.mono-build)
|
|
56
|
+
*/
|
|
57
|
+
dir: monoBundleDir,
|
|
58
|
+
/**
|
|
59
|
+
* The mono bundle entry file (.mono-build/index.mjs)
|
|
60
|
+
*/
|
|
61
|
+
entryFile: monoBundlePath,
|
|
62
|
+
/**
|
|
63
|
+
* The handler string to use for mono build mode
|
|
64
|
+
*/
|
|
65
|
+
handler: "index.handler",
|
|
66
|
+
/**
|
|
67
|
+
* Check if a build output path represents a mono build.
|
|
68
|
+
* This is useful when you have a build result and need to determine its type.
|
|
69
|
+
*/
|
|
70
|
+
isMonoBuildPath(buildOut) {
|
|
71
|
+
return isEnabled() && buildOut.includes(".mono-build");
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Get the pool key for a function based on mono build status.
|
|
75
|
+
* For mono build: shared key (all functions share workers)
|
|
76
|
+
* For non-mono build: per-function key
|
|
77
|
+
*/
|
|
78
|
+
getPoolKey(functionID, runtime, buildOut) {
|
|
79
|
+
if (isEnabled() && buildOut.includes(".mono-build")) {
|
|
80
|
+
return { key: `${runtime}:mono-build`, isShared: true };
|
|
81
|
+
}
|
|
82
|
+
return { key: `${runtime}:${functionID}`, isShared: false };
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Quick check for mono build mode without full config initialization.
|
|
88
|
+
* Use this for simple boolean checks where you don't need the full config.
|
|
89
|
+
*/
|
|
90
|
+
export function isMonoBuildEnabled() {
|
|
91
|
+
return useMonoBuildConfig().enabled;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the mono build directory path.
|
|
95
|
+
*/
|
|
96
|
+
export function getMonoBuildDir() {
|
|
97
|
+
return useMonoBuildConfig().dir;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a path represents a mono build output.
|
|
101
|
+
*/
|
|
102
|
+
export function isMonoBuildPath(buildOut) {
|
|
103
|
+
return useMonoBuildConfig().isMonoBuildPath(buildOut);
|
|
104
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts a request path from a Lambda event for logging purposes.
|
|
3
|
+
* Handles API Gateway v1, v2, and other event formats.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getRequestPath(event: unknown): string;
|
|
6
|
+
/**
|
|
7
|
+
* Extracts correlation ID from headers for request tracing.
|
|
8
|
+
* Checks common correlation header names.
|
|
9
|
+
*
|
|
10
|
+
* Frontend can send: X-Correlation-ID, X-Request-ID, or X-Trace-ID
|
|
11
|
+
*/
|
|
12
|
+
export declare function getCorrelationId(event: unknown): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Extracts API Gateway Request ID from requestContext.
|
|
15
|
+
* This ID is visible in browser dev tools (x-amzn-requestid response header).
|
|
16
|
+
*/
|
|
17
|
+
export declare function getApiGatewayRequestId(event: unknown): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Extracts HTTP method from event
|
|
20
|
+
*/
|
|
21
|
+
export declare function getHttpMethod(event: unknown): string | undefined;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts a request path from a Lambda event for logging purposes.
|
|
3
|
+
* Handles API Gateway v1, v2, and other event formats.
|
|
4
|
+
*/
|
|
5
|
+
export function getRequestPath(event) {
|
|
6
|
+
if (!event || typeof event !== "object") {
|
|
7
|
+
return "[unknown]";
|
|
8
|
+
}
|
|
9
|
+
const evt = event;
|
|
10
|
+
// API Gateway v2 (HTTP API)
|
|
11
|
+
if (typeof evt.rawPath === "string") {
|
|
12
|
+
return evt.rawPath;
|
|
13
|
+
}
|
|
14
|
+
// API Gateway v1 (REST API)
|
|
15
|
+
if (typeof evt.path === "string") {
|
|
16
|
+
return evt.path;
|
|
17
|
+
}
|
|
18
|
+
// Warmup requests
|
|
19
|
+
if ("ding" in evt || "warmer" in evt || evt.__sst_warmup === true) {
|
|
20
|
+
return "[warmup]";
|
|
21
|
+
}
|
|
22
|
+
// SQS, SNS, or other event types - no path
|
|
23
|
+
return "[event]";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extracts correlation ID from headers for request tracing.
|
|
27
|
+
* Checks common correlation header names.
|
|
28
|
+
*
|
|
29
|
+
* Frontend can send: X-Correlation-ID, X-Request-ID, or X-Trace-ID
|
|
30
|
+
*/
|
|
31
|
+
export function getCorrelationId(event) {
|
|
32
|
+
if (!event || typeof event !== "object") {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const evt = event;
|
|
36
|
+
// Get headers - handle both v1 and v2 API Gateway formats
|
|
37
|
+
let headers;
|
|
38
|
+
// API Gateway v2 uses lowercase headers
|
|
39
|
+
if (evt.headers && typeof evt.headers === "object") {
|
|
40
|
+
headers = evt.headers;
|
|
41
|
+
}
|
|
42
|
+
if (!headers)
|
|
43
|
+
return undefined;
|
|
44
|
+
// Check common correlation header names (case-insensitive)
|
|
45
|
+
const correlationHeaders = [
|
|
46
|
+
"x-correlation-id",
|
|
47
|
+
"x-request-id",
|
|
48
|
+
"x-trace-id",
|
|
49
|
+
"correlation-id",
|
|
50
|
+
"request-id",
|
|
51
|
+
];
|
|
52
|
+
// Normalize header keys to lowercase for comparison
|
|
53
|
+
const normalizedHeaders = {};
|
|
54
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
55
|
+
normalizedHeaders[key.toLowerCase()] = value;
|
|
56
|
+
}
|
|
57
|
+
for (const headerName of correlationHeaders) {
|
|
58
|
+
const value = normalizedHeaders[headerName];
|
|
59
|
+
if (value) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extracts API Gateway Request ID from requestContext.
|
|
67
|
+
* This ID is visible in browser dev tools (x-amzn-requestid response header).
|
|
68
|
+
*/
|
|
69
|
+
export function getApiGatewayRequestId(event) {
|
|
70
|
+
if (!event || typeof event !== "object") {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const evt = event;
|
|
74
|
+
// Check requestContext.requestId (both v1 and v2)
|
|
75
|
+
const requestContext = evt.requestContext;
|
|
76
|
+
if (requestContext?.requestId && typeof requestContext.requestId === "string") {
|
|
77
|
+
return requestContext.requestId;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extracts HTTP method from event
|
|
83
|
+
*/
|
|
84
|
+
export function getHttpMethod(event) {
|
|
85
|
+
if (!event || typeof event !== "object") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const evt = event;
|
|
89
|
+
// API Gateway v2
|
|
90
|
+
const requestContext = evt.requestContext;
|
|
91
|
+
if (requestContext?.http && typeof requestContext.http === "object") {
|
|
92
|
+
const http = requestContext.http;
|
|
93
|
+
if (typeof http.method === "string") {
|
|
94
|
+
return http.method;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// API Gateway v1
|
|
98
|
+
if (typeof evt.httpMethod === "string") {
|
|
99
|
+
return evt.httpMethod;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
package/runtime/runtime.d.ts
CHANGED
package/runtime/server.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Events } from "../bus.js";
|
|
1
2
|
export declare const useRuntimeServerConfig: () => Promise<{
|
|
2
3
|
API_VERSION: string;
|
|
3
4
|
port: number;
|
|
4
5
|
url: string;
|
|
5
6
|
}>;
|
|
6
|
-
export declare const useRuntimeServer: () => Promise<
|
|
7
|
+
export declare const useRuntimeServer: () => Promise<{
|
|
8
|
+
routeInvocation: (targetWorkerID: string, invocation: Events["function.invoked"]) => void;
|
|
9
|
+
}>;
|
package/runtime/server.js
CHANGED
|
@@ -2,9 +2,13 @@ import express from "express";
|
|
|
2
2
|
import { useBus } from "../bus.js";
|
|
3
3
|
import { Logger } from "../logger.js";
|
|
4
4
|
import { useRuntimeWorkers } from "./workers.js";
|
|
5
|
+
import { useFunctions } from "../constructs/Function.js";
|
|
5
6
|
import https from "https";
|
|
6
7
|
import getPort from "get-port";
|
|
7
8
|
import { lazy } from "../util/lazy.js";
|
|
9
|
+
import { getRequestPath } from "./request-utils.js";
|
|
10
|
+
import { logServer } from "./debug-bridge-logging.js";
|
|
11
|
+
import { logEventTrace } from "./event-trace-logging.js";
|
|
8
12
|
export const useRuntimeServerConfig = lazy(async () => {
|
|
9
13
|
const port = await getPort({
|
|
10
14
|
port: 12557,
|
|
@@ -31,54 +35,100 @@ export const useRuntimeServer = lazy(async () => {
|
|
|
31
35
|
workersWaiting.set(workerID, resolve);
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
|
-
workers.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
workersWaiting.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
workersWaiting.delete(evt.properties.workerID);
|
|
44
|
-
worker(evt.properties);
|
|
38
|
+
// Route an invocation to a specific workerID (used by workers.ts for pooled workers)
|
|
39
|
+
function routeInvocation(targetWorkerID, invocation) {
|
|
40
|
+
const requestPath = getRequestPath(invocation.event);
|
|
41
|
+
const requestID = invocation.requestID;
|
|
42
|
+
const waiting = workersWaiting.get(targetWorkerID);
|
|
43
|
+
if (waiting) {
|
|
44
|
+
workersWaiting.delete(targetWorkerID);
|
|
45
|
+
logServer(`path=${requestPath} reqId=${requestID.slice(0, 8)} Worker ${targetWorkerID.slice(0, 8)} was waiting, delivering immediately`);
|
|
46
|
+
waiting(invocation);
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
|
-
let arr = invocationsQueued.get(
|
|
49
|
+
let arr = invocationsQueued.get(targetWorkerID);
|
|
48
50
|
if (!arr) {
|
|
49
51
|
arr = [];
|
|
50
|
-
invocationsQueued.set(
|
|
52
|
+
invocationsQueued.set(targetWorkerID, arr);
|
|
51
53
|
}
|
|
52
|
-
arr.push(
|
|
54
|
+
arr.push(invocation);
|
|
55
|
+
logServer(`path=${requestPath} reqId=${requestID.slice(0, 8)} Worker ${targetWorkerID.slice(0, 8)} not waiting, QUEUED (queueSize=${arr.length})`);
|
|
56
|
+
}
|
|
57
|
+
workers.subscribe("worker.exited", async (evt) => {
|
|
58
|
+
const waiting = workersWaiting.get(evt.properties.workerID);
|
|
59
|
+
if (!waiting)
|
|
60
|
+
return;
|
|
61
|
+
workersWaiting.delete(evt.properties.workerID);
|
|
53
62
|
});
|
|
63
|
+
// Note: function.invoked routing is handled by workers.ts via routeInvocation()
|
|
64
|
+
// This ensures correct routing for both pooled and non-pooled workers
|
|
54
65
|
app.post(`/:workerID/${cfg.API_VERSION}/runtime/init/error`, express.json({
|
|
55
66
|
strict: false,
|
|
56
67
|
type: ["application/json", "application/*+json"],
|
|
57
68
|
limit: "10mb",
|
|
58
69
|
}), async (req, res) => {
|
|
59
|
-
const
|
|
70
|
+
const pooledWorkerID = req.params.workerID;
|
|
71
|
+
const worker = workers.fromID(pooledWorkerID);
|
|
72
|
+
if (!worker) {
|
|
73
|
+
res.status(404).send();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Get AWS workerID for IoT routing (if pooled)
|
|
77
|
+
const awsWorkerID = workers.isPooled(pooledWorkerID)
|
|
78
|
+
? workers.getAwsWorkerID(pooledWorkerID) || pooledWorkerID
|
|
79
|
+
: pooledWorkerID;
|
|
80
|
+
const requestID = workers.getCurrentRequestID(pooledWorkerID);
|
|
81
|
+
logEventTrace("WORKER_END", {
|
|
82
|
+
requestID: requestID || "unknown",
|
|
83
|
+
functionID: worker.functionID,
|
|
84
|
+
workerID: pooledWorkerID,
|
|
85
|
+
status: "error",
|
|
86
|
+
errorType: req.body?.errorType || "init_error",
|
|
87
|
+
});
|
|
60
88
|
bus.publish("function.error", {
|
|
61
|
-
requestID
|
|
62
|
-
workerID:
|
|
89
|
+
requestID,
|
|
90
|
+
workerID: awsWorkerID,
|
|
63
91
|
functionID: worker.functionID,
|
|
64
92
|
...req.body,
|
|
65
93
|
});
|
|
94
|
+
// Return pooled worker to pool
|
|
95
|
+
workers.onResponse(pooledWorkerID);
|
|
66
96
|
res.json("ok");
|
|
67
97
|
});
|
|
68
98
|
app.get(`/:workerID/${cfg.API_VERSION}/runtime/invocation/next`, async (req, res) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Logger.debug("Worker",
|
|
99
|
+
const workerID = req.params.workerID;
|
|
100
|
+
logServer(`Worker ${workerID.slice(0, 8)} requesting /next`);
|
|
101
|
+
Logger.debug("Worker", workerID, "is waiting for next invocation");
|
|
102
|
+
const waitStart = Date.now();
|
|
103
|
+
const payload = await next(workerID);
|
|
104
|
+
const requestPath = getRequestPath(payload.event);
|
|
105
|
+
const requestID = payload.context.awsRequestId;
|
|
106
|
+
logServer(`path=${requestPath} reqId=${requestID.slice(0, 8)} Worker ${workerID.slice(0, 8)} received payload after waiting ${Date.now() - waitStart}ms`);
|
|
107
|
+
Logger.debug("Worker", workerID, "sending next payload");
|
|
108
|
+
// Get function properties for per-invocation context
|
|
109
|
+
// This is essential for mono-build shared workers that serve multiple functions
|
|
110
|
+
const funcProps = useFunctions().fromID(payload.functionID);
|
|
72
111
|
res.set({
|
|
73
|
-
"Lambda-Runtime-Aws-Request-Id":
|
|
112
|
+
"Lambda-Runtime-Aws-Request-Id": requestID,
|
|
74
113
|
"Lambda-Runtime-Deadline-Ms": Date.now() + payload.deadline,
|
|
75
114
|
"Lambda-Runtime-Invoked-Function-Arn": payload.context.invokedFunctionArn,
|
|
76
115
|
"Lambda-Runtime-Client-Context": JSON.stringify(payload.context.clientContext || null),
|
|
77
116
|
"Lambda-Runtime-Cognito-Identity": JSON.stringify(payload.context.identity || null),
|
|
78
117
|
"Lambda-Runtime-Log-Group-Name": payload.context.logGroupName,
|
|
79
118
|
"Lambda-Runtime-Log-Stream-Name": payload.context.logStreamName,
|
|
119
|
+
// Pass function ID for mono-build shared pool: allows per-invocation dispatch
|
|
120
|
+
"Lambda-Runtime-Sst-Function-Id": payload.functionID,
|
|
121
|
+
// Per-invocation function context for mono-build shared workers
|
|
122
|
+
"Lambda-Runtime-Function-Name": funcProps?.functionName || "",
|
|
123
|
+
"Lambda-Runtime-Function-Version": "$LATEST",
|
|
124
|
+
"Lambda-Runtime-Function-Memory-Size": String(funcProps?.memorySize || 1024),
|
|
125
|
+
});
|
|
126
|
+
// Wrap event with env for per-invocation environment variable application
|
|
127
|
+
// This prevents env leakage when workers are reused across different functions
|
|
128
|
+
res.json({
|
|
129
|
+
event: payload.event,
|
|
130
|
+
env: payload.env,
|
|
80
131
|
});
|
|
81
|
-
res.json(payload.event);
|
|
82
132
|
});
|
|
83
133
|
app.post(`/:workerID/${cfg.API_VERSION}/runtime/invocation/:awsRequestId/response`, express.json({
|
|
84
134
|
strict: false,
|
|
@@ -87,14 +137,35 @@ export const useRuntimeServer = lazy(async () => {
|
|
|
87
137
|
},
|
|
88
138
|
limit: "10mb",
|
|
89
139
|
}), (req, res) => {
|
|
90
|
-
|
|
91
|
-
const
|
|
140
|
+
const pooledWorkerID = req.params.workerID;
|
|
141
|
+
const requestID = req.params.awsRequestId;
|
|
142
|
+
logServer(`reqId=${requestID.slice(0, 8)} Worker ${pooledWorkerID.slice(0, 8)} posting /response`);
|
|
143
|
+
Logger.debug("Worker", pooledWorkerID, "got response", req.body);
|
|
144
|
+
const worker = workers.fromID(pooledWorkerID);
|
|
145
|
+
if (!worker) {
|
|
146
|
+
logServer(`reqId=${requestID.slice(0, 8)} ERROR: Worker ${pooledWorkerID.slice(0, 8)} not found`);
|
|
147
|
+
res.status(404).send();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Get AWS workerID for IoT routing (if pooled)
|
|
151
|
+
const awsWorkerID = workers.isPooled(pooledWorkerID)
|
|
152
|
+
? workers.getAwsWorkerID(pooledWorkerID) || pooledWorkerID
|
|
153
|
+
: pooledWorkerID;
|
|
154
|
+
logServer(`reqId=${requestID.slice(0, 8)} Publishing function.success awsWorkerID=${awsWorkerID.slice(0, 8)}`);
|
|
155
|
+
logEventTrace("WORKER_END", {
|
|
156
|
+
requestID,
|
|
157
|
+
functionID: worker.functionID,
|
|
158
|
+
workerID: pooledWorkerID,
|
|
159
|
+
status: "success",
|
|
160
|
+
});
|
|
92
161
|
bus.publish("function.success", {
|
|
93
|
-
workerID:
|
|
162
|
+
workerID: awsWorkerID,
|
|
94
163
|
functionID: worker.functionID,
|
|
95
|
-
requestID:
|
|
164
|
+
requestID: requestID,
|
|
96
165
|
body: req.body,
|
|
97
166
|
});
|
|
167
|
+
// Return pooled worker to pool
|
|
168
|
+
workers.onResponse(pooledWorkerID);
|
|
98
169
|
res.status(202).send();
|
|
99
170
|
});
|
|
100
171
|
app.all(`/proxy*`, express.raw({
|
|
@@ -135,16 +206,38 @@ export const useRuntimeServer = lazy(async () => {
|
|
|
135
206
|
type: ["application/json", "application/*+json"],
|
|
136
207
|
limit: "10mb",
|
|
137
208
|
}), (req, res) => {
|
|
138
|
-
const
|
|
209
|
+
const pooledWorkerID = req.params.workerID;
|
|
210
|
+
const worker = workers.fromID(pooledWorkerID);
|
|
211
|
+
if (!worker) {
|
|
212
|
+
res.status(404).send();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Get AWS workerID for IoT routing (if pooled)
|
|
216
|
+
const awsWorkerID = workers.isPooled(pooledWorkerID)
|
|
217
|
+
? workers.getAwsWorkerID(pooledWorkerID) || pooledWorkerID
|
|
218
|
+
: pooledWorkerID;
|
|
219
|
+
const requestID = req.params.awsRequestId;
|
|
220
|
+
logEventTrace("WORKER_END", {
|
|
221
|
+
requestID,
|
|
222
|
+
functionID: worker.functionID,
|
|
223
|
+
workerID: pooledWorkerID,
|
|
224
|
+
status: "error",
|
|
225
|
+
errorType: req.body.errorType,
|
|
226
|
+
});
|
|
139
227
|
bus.publish("function.error", {
|
|
140
|
-
workerID:
|
|
228
|
+
workerID: awsWorkerID,
|
|
141
229
|
functionID: worker.functionID,
|
|
142
230
|
errorType: req.body.errorType,
|
|
143
231
|
errorMessage: req.body.errorMessage,
|
|
144
|
-
requestID
|
|
232
|
+
requestID,
|
|
145
233
|
trace: req.body.trace,
|
|
146
234
|
});
|
|
235
|
+
// Return pooled worker to pool
|
|
236
|
+
workers.onResponse(pooledWorkerID);
|
|
147
237
|
res.status(202).send();
|
|
148
238
|
});
|
|
149
239
|
app.listen(cfg.port);
|
|
240
|
+
return {
|
|
241
|
+
routeInvocation,
|
|
242
|
+
};
|
|
150
243
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const POOL_SIZE: number;
|
|
2
|
+
export declare const IDLE_TIMEOUT: number;
|
|
3
|
+
/**
|
|
4
|
+
* Set the function name resolver callback
|
|
5
|
+
* Called by workers.ts to provide access to useFunctions()
|
|
6
|
+
*/
|
|
7
|
+
export declare function setFunctionNameResolver(resolver: (functionID: string) => string): void;
|
|
8
|
+
/**
|
|
9
|
+
* Pool debug logging helper - writes to file with bottleneck detection
|
|
10
|
+
*/
|
|
11
|
+
export declare function logPool(action: string, details?: Record<string, any>): void;
|
|
12
|
+
/**
|
|
13
|
+
* Track request lifecycle for metrics - called at request start
|
|
14
|
+
*/
|
|
15
|
+
export declare function trackRequestStart(functionID: string, isPoolHit: boolean): void;
|
|
16
|
+
/**
|
|
17
|
+
* Track request lifecycle for metrics - called at request end
|
|
18
|
+
*/
|
|
19
|
+
export declare function trackRequestEnd(functionID: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Write session end summary and close log stream
|
|
22
|
+
* Called from workers.ts on process exit
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeSessionEndSummary(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Log invocation trace events to .sst/invoke-trace.log
|
|
27
|
+
* Always enabled for debugging invocation flow
|
|
28
|
+
*/
|
|
29
|
+
export declare function logInvokeTrace(stage: string, requestID: string, details?: string): void;
|