@intelligems/sst 2.49.6-ig.2 → 2.49.6-ig.4
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/iot.js +5 -8
- package/package.json +1 -1
- package/package.json.bak +1 -1
- package/runtime/handlers/node.js +44 -34
- package/runtime/handlers.js +10 -4
- package/runtime/iot.js +5 -1
- package/runtime/server.d.ts +4 -1
- package/runtime/server.js +69 -23
- package/runtime/worker-pool-logging.d.ts +29 -0
- package/runtime/worker-pool-logging.js +190 -0
- package/runtime/workers.d.ts +13 -7
- package/runtime/workers.js +632 -36
- package/support/bridge/live-lambda.mjs +37 -37
- package/support/nodejs-runtime/index.mjs +10 -1
- package/support/python-runtime/runtime.py +131 -60
|
@@ -108,6 +108,15 @@ while (true) {
|
|
|
108
108
|
method: "GET",
|
|
109
109
|
headers: {}
|
|
110
110
|
});
|
|
111
|
+
const sstFunctionId = result.headers["lambda-runtime-sst-function-id"];
|
|
112
|
+
if (sstFunctionId) {
|
|
113
|
+
process.env.SST_FUNCTION_ID = sstFunctionId;
|
|
114
|
+
}
|
|
115
|
+
const parsed = JSON.parse(result.body);
|
|
116
|
+
const invocationEnv = parsed.env;
|
|
117
|
+
if (invocationEnv && typeof invocationEnv === "object") {
|
|
118
|
+
Object.assign(process.env, invocationEnv);
|
|
119
|
+
}
|
|
111
120
|
context = {
|
|
112
121
|
awsRequestId: result.headers["lambda-runtime-aws-request-id"],
|
|
113
122
|
invokedFunctionArn: result.headers["lambda-runtime-invoked-function-arn"],
|
|
@@ -150,7 +159,7 @@ while (true) {
|
|
|
150
159
|
);
|
|
151
160
|
}
|
|
152
161
|
};
|
|
153
|
-
request =
|
|
162
|
+
request = parsed.event;
|
|
154
163
|
} catch {
|
|
155
164
|
continue;
|
|
156
165
|
}
|
|
@@ -4,8 +4,9 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
import argparse
|
|
6
6
|
import traceback
|
|
7
|
-
|
|
8
|
-
from
|
|
7
|
+
import signal
|
|
8
|
+
from urllib import request
|
|
9
|
+
from time import time
|
|
9
10
|
from importlib import import_module
|
|
10
11
|
|
|
11
12
|
class Identity(object):
|
|
@@ -22,15 +23,15 @@ class ClientContext(object):
|
|
|
22
23
|
|
|
23
24
|
class Context(object):
|
|
24
25
|
def __init__(self, invoked_function_arn, aws_request_id, deadline_ms, identity, client_context, log_group_name, log_stream_name):
|
|
25
|
-
self.function_name = os.environ
|
|
26
|
+
self.function_name = os.environ.get('AWS_LAMBDA_FUNCTION_NAME', 'local')
|
|
26
27
|
self.invoked_function_arn = invoked_function_arn
|
|
27
28
|
self.aws_request_id = aws_request_id
|
|
28
|
-
self.memory_limit_in_mb = os.environ
|
|
29
|
+
self.memory_limit_in_mb = os.environ.get('AWS_LAMBDA_FUNCTION_MEMORY_SIZE', '128')
|
|
29
30
|
self.deadline_ms = deadline_ms
|
|
30
31
|
# If identity is null, we want to mimick AWS behavior and return an object with None values
|
|
31
|
-
self.identity = Identity(**json.loads(identity)) if identity != 'null' else Identity(cognito_identity_id
|
|
32
|
+
self.identity = Identity(**json.loads(identity)) if identity and identity != 'null' else Identity(cognito_identity_id=None, cognito_identity_pool_id=None)
|
|
32
33
|
# If client_context is null, we want to mimick AWS behavior and return None
|
|
33
|
-
self.client_context = ClientContext(**json.loads(client_context)) if client_context != 'null' else None
|
|
34
|
+
self.client_context = ClientContext(**json.loads(client_context)) if client_context and client_context != 'null' else None
|
|
34
35
|
self.log_group_name = log_group_name
|
|
35
36
|
self.log_stream_name = log_stream_name
|
|
36
37
|
|
|
@@ -42,15 +43,17 @@ class Context(object):
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def handleUnserializable(obj):
|
|
45
|
-
print(
|
|
46
|
-
"Unserializable {}: {} when returning result {!r}".format(
|
|
47
|
-
type(obj), repr(obj), result
|
|
48
|
-
)
|
|
49
|
-
)
|
|
50
|
-
|
|
51
46
|
raise TypeError("Unserializable {}: {!r}".format(type(obj), obj))
|
|
52
47
|
|
|
53
48
|
|
|
49
|
+
# Idle timeout handler
|
|
50
|
+
class IdleTimeoutError(Exception):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def idle_timeout_handler(signum, frame):
|
|
54
|
+
raise IdleTimeoutError("Worker idle timeout")
|
|
55
|
+
|
|
56
|
+
|
|
54
57
|
logging.basicConfig()
|
|
55
58
|
|
|
56
59
|
parser = argparse.ArgumentParser(
|
|
@@ -64,64 +67,132 @@ parser.add_argument('handler_module',
|
|
|
64
67
|
parser.add_argument('src_path', help='SrcPath of the handler function')
|
|
65
68
|
parser.add_argument('handler_name', help='Name of the handler function')
|
|
66
69
|
|
|
70
|
+
# Idle timeout in seconds (15 minutes, matching Node.js runtime)
|
|
71
|
+
IDLE_TIMEOUT = 15 * 60
|
|
72
|
+
|
|
67
73
|
if __name__ == '__main__':
|
|
68
74
|
args = parser.parse_args()
|
|
69
75
|
|
|
70
76
|
# this is needed because you need to import from where you've executed sst
|
|
71
77
|
sys.path.append('.')
|
|
72
78
|
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
event = json.loads(r.read())
|
|
77
|
-
context = Context(
|
|
78
|
-
r.getheader('Lambda-Runtime-Invoked-Function-Arn'),
|
|
79
|
-
r.getheader('Lambda-Runtime-Aws-Request-Id'),
|
|
80
|
-
r.getheader('Lambda-Runtime-Deadline-Ms'),
|
|
81
|
-
r.getheader('Lambda-Runtime-Cognito-Identity'),
|
|
82
|
-
r.getheader('Lambda-Runtime-Client-Context'),
|
|
83
|
-
r.getheader('Lambda-Runtime-Log-Group-Name'),
|
|
84
|
-
r.getheader('Lambda-Runtime-Log-Stream-Name')
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
# invoke handler
|
|
88
|
-
has_error = False
|
|
89
|
-
try:
|
|
90
|
-
# set the sys.path to the src_path. Other wise importing a local file
|
|
91
|
-
# would fail with error ModuleNotFoundError
|
|
92
|
-
sys.path.append(args.src_path)
|
|
79
|
+
# set the sys.path to the src_path. Otherwise importing a local file
|
|
80
|
+
# would fail with error ModuleNotFoundError
|
|
81
|
+
sys.path.append(args.src_path)
|
|
93
82
|
|
|
83
|
+
# Import handler module once at startup (warm start optimization)
|
|
84
|
+
handler = None
|
|
85
|
+
try:
|
|
94
86
|
# remove leading zeros for relative imports
|
|
95
87
|
if args.handler_module.startswith('.'):
|
|
96
|
-
|
|
97
|
-
else:
|
|
98
|
-
|
|
88
|
+
module = import_module(args.handler_module[1:])
|
|
89
|
+
else:
|
|
90
|
+
module = import_module(args.handler_module)
|
|
99
91
|
|
|
100
92
|
handler = getattr(module, args.handler_name)
|
|
101
|
-
result = handler(event, context)
|
|
102
|
-
data = json.dumps(result, default=handleUnserializable).encode("utf-8")
|
|
103
|
-
|
|
104
93
|
except Exception as e:
|
|
105
|
-
|
|
106
|
-
# print error in bootstrap because we won't be able to print the Python
|
|
107
|
-
# stack trace in the correct format in NodeJS
|
|
94
|
+
# Report init error and exit
|
|
108
95
|
traceback.print_exc()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
96
|
+
try:
|
|
97
|
+
init_error_url = "http://{}/2018-06-01/runtime/init/error".format(
|
|
98
|
+
os.environ['AWS_LAMBDA_RUNTIME_API']
|
|
99
|
+
)
|
|
100
|
+
ex_type, ex_value, ex_traceback = sys.exc_info()
|
|
101
|
+
error_data = json.dumps({
|
|
102
|
+
"errorType": ex_type.__name__ if ex_type else "ImportError",
|
|
103
|
+
"errorMessage": str(ex_value),
|
|
104
|
+
"trace": traceback.format_tb(ex_traceback) if ex_traceback else [],
|
|
105
|
+
}).encode("utf-8")
|
|
106
|
+
req = request.Request(init_error_url, method="POST", data=error_data)
|
|
107
|
+
req.add_header('Content-Type', 'application/json')
|
|
108
|
+
request.urlopen(req)
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
# Main event loop - handle multiple invocations
|
|
114
|
+
while True:
|
|
115
|
+
context = None
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
# Set up idle timeout using SIGALRM (Unix only)
|
|
119
|
+
if hasattr(signal, 'SIGALRM'):
|
|
120
|
+
signal.signal(signal.SIGALRM, idle_timeout_handler)
|
|
121
|
+
signal.alarm(IDLE_TIMEOUT)
|
|
122
|
+
|
|
123
|
+
# Fetch next invocation (blocks until one is available)
|
|
124
|
+
next_url = "http://{}/2018-06-01/runtime/invocation/next".format(
|
|
125
|
+
os.environ['AWS_LAMBDA_RUNTIME_API']
|
|
126
|
+
)
|
|
127
|
+
r = request.urlopen(next_url)
|
|
128
|
+
|
|
129
|
+
# Cancel idle timeout once we have work
|
|
130
|
+
if hasattr(signal, 'SIGALRM'):
|
|
131
|
+
signal.alarm(0)
|
|
132
|
+
|
|
133
|
+
event = json.loads(r.read())
|
|
134
|
+
context = Context(
|
|
135
|
+
r.getheader('Lambda-Runtime-Invoked-Function-Arn'),
|
|
136
|
+
r.getheader('Lambda-Runtime-Aws-Request-Id'),
|
|
137
|
+
r.getheader('Lambda-Runtime-Deadline-Ms'),
|
|
138
|
+
r.getheader('Lambda-Runtime-Cognito-Identity'),
|
|
139
|
+
r.getheader('Lambda-Runtime-Client-Context'),
|
|
140
|
+
r.getheader('Lambda-Runtime-Log-Group-Name'),
|
|
141
|
+
r.getheader('Lambda-Runtime-Log-Stream-Name')
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Invoke handler
|
|
145
|
+
try:
|
|
146
|
+
result = handler(event, context)
|
|
147
|
+
data = json.dumps(result, default=handleUnserializable).encode("utf-8")
|
|
148
|
+
url_destination = '/response'
|
|
149
|
+
except Exception as e:
|
|
150
|
+
# Handler error - report but keep worker alive
|
|
151
|
+
traceback.print_exc()
|
|
152
|
+
ex_type, ex_value, ex_traceback = sys.exc_info()
|
|
153
|
+
error_result = {
|
|
154
|
+
"errorType": ex_type.__name__ if ex_type else "Error",
|
|
155
|
+
"errorMessage": str(ex_value),
|
|
156
|
+
"trace": traceback.format_tb(ex_traceback) if ex_traceback else [],
|
|
157
|
+
}
|
|
158
|
+
data = json.dumps(error_result).encode("utf-8")
|
|
159
|
+
url_destination = '/error'
|
|
160
|
+
|
|
161
|
+
# Send response
|
|
162
|
+
response_url = "http://{}/2018-06-01/runtime/invocation/{}{}".format(
|
|
163
|
+
os.environ['AWS_LAMBDA_RUNTIME_API'],
|
|
164
|
+
context.aws_request_id,
|
|
165
|
+
url_destination
|
|
166
|
+
)
|
|
167
|
+
req = request.Request(response_url, method="POST", data=data)
|
|
168
|
+
req.add_header('Content-Type', 'application/json')
|
|
169
|
+
|
|
170
|
+
# Retry sending response (matching Node.js behavior)
|
|
171
|
+
max_retries = 3
|
|
172
|
+
for attempt in range(max_retries):
|
|
173
|
+
try:
|
|
174
|
+
request.urlopen(req)
|
|
175
|
+
break
|
|
176
|
+
except Exception as e:
|
|
177
|
+
if attempt < max_retries - 1:
|
|
178
|
+
import time as time_module
|
|
179
|
+
time_module.sleep(0.5)
|
|
180
|
+
else:
|
|
181
|
+
print(f"Failed to send response after {max_retries} attempts: {e}", file=sys.stderr)
|
|
182
|
+
|
|
183
|
+
except IdleTimeoutError:
|
|
184
|
+
# Idle timeout - exit gracefully
|
|
185
|
+
print("Worker idle timeout, exiting", file=sys.stderr)
|
|
186
|
+
sys.exit(0)
|
|
187
|
+
|
|
188
|
+
except KeyboardInterrupt:
|
|
189
|
+
# Graceful shutdown
|
|
190
|
+
print("Worker interrupted, exiting", file=sys.stderr)
|
|
191
|
+
sys.exit(0)
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
# Unexpected error in the runtime loop itself
|
|
195
|
+
print(f"Runtime error: {e}", file=sys.stderr)
|
|
196
|
+
traceback.print_exc()
|
|
197
|
+
# Continue to next iteration - don't crash the worker
|
|
198
|
+
continue
|