Skip to content

Commit f8a3523

Browse files
committed
feat: timeout optimizations
1 parent 8d0a139 commit f8a3523

File tree

2 files changed

+35
-27
lines changed

2 files changed

+35
-27
lines changed

packages/core/__benchmarks__/index.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Bench } from 'tinybench'
22
import middy from '../index.js'
3+
// import middyNext from '../index.next.js'
34

45
const bench = new Bench({ time: 1_000 })
56

@@ -45,7 +46,7 @@ middlewaresAsync.fill(middlewareAsync())
4546
const warmAsyncMiddlewareHandler = middy()
4647
.use(middlewaresAsync)
4748
.handler(baseHandler)
48-
const warmTimeoutHandler = middy({ timeoutEarlyInMillis: 0 }).handler(
49+
const warmDisableTimeoutHandler = middy({ timeoutEarlyInMillis: 0 }).handler(
4950
baseHandler
5051
)
5152

@@ -60,40 +61,41 @@ const warmTimeoutHandler = middy({ timeoutEarlyInMillis: 0 }).handler(
6061
// baseHandler
6162
// )
6263

64+
const event = {}
6365
await bench
64-
.add('Cold Invocation', async (event = {}) => {
66+
.add('Cold Invocation', async () => {
6567
const coldHandler = middy().handler(baseHandler)
6668
await coldHandler(event, context)
6769
})
68-
.add('Cold Invocation with middleware', async (event = {}) => {
70+
.add('Cold Invocation with middleware', async () => {
6971
const middlewares = new Array(25)
7072
middlewares.fill(middleware())
7173
const coldHandler = middy().use(middlewares).handler(baseHandler)
7274
await coldHandler(event, context)
7375
})
74-
.add('Warm Invocation', async (event = {}) => {
76+
.add('Warm Invocation', async () => {
7577
await warmHandler(event, context)
7678
})
7779
// .add('Warm Invocation * next', async (event = {}) => {
7880
// await warmNextHandler(event, context)
7981
// })
80-
.add('Warm Async Invocation', async (event = {}) => {
82+
.add('Warm Async Invocation', async () => {
8183
await warmAsyncHandler(event, context)
8284
})
83-
.add('Warm Invocation without Timeout', async (event = {}) => {
84-
await warmTimeoutHandler(event, context)
85+
.add('Warm Invocation with disabled Timeout', async () => {
86+
await warmDisableTimeoutHandler(event, context)
8587
})
86-
// .add('Warm Invocation with Timeout * next', async (event = {}) => {
88+
// .add('Warm Invocation with disabled Timeout * next', async (event = {}) => {
8789
// await warmNextTimeoutHandler(event, context)
8890
// })
8991
// TODO StreamifyResponse
90-
.add('Warm Invocation with middleware', async (event = {}) => {
92+
.add('Warm Invocation with middleware', async () => {
9193
await warmMiddlewareHandler(event, context)
9294
})
9395
// .add('Warm Invocation with middleware * next', async (event = {}) => {
9496
// await warmNextMiddlewareHandler(event, context)
9597
// })
96-
.add('Warm Invocation with async middleware', async (event = {}) => {
98+
.add('Warm Invocation with async middleware', async () => {
9799
await warmAsyncMiddlewareHandler(event, context)
98100
})
99101

packages/core/index.js

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* global awslambda */
22
import { Readable } from 'node:stream'
33
import { pipeline } from 'node:stream/promises'
4-
import { setTimeout } from 'node:timers/promises'
4+
import { setTimeout } from 'node:timers'
55

66
const defaultLambdaHandler = () => {}
77
const defaultPlugin = {
@@ -137,7 +137,7 @@ const runRequest = async (
137137
onErrorMiddlewares,
138138
plugin
139139
) => {
140-
let timeoutAbort
140+
let timeoutID
141141
// context.getRemainingTimeInMillis checked for when AWS context missing (tests, containers)
142142
const timeoutEarly =
143143
plugin.timeoutEarly && request.context.getRemainingTimeInMillis
@@ -149,39 +149,45 @@ const runRequest = async (
149149
if (typeof request.response === 'undefined') {
150150
plugin.beforeHandler?.()
151151

152-
// Note: signal.abort is slow ~6000ns
152+
// Can't manually abort and timeout with same AbortSignal
153+
// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
153154
const handlerAbort = new AbortController()
154155
const promises = [
155156
lambdaHandler(request.event, request.context, {
156157
signal: handlerAbort.signal
157158
})
158159
]
159160

160-
// Can't manually abort and timeout with same AbortSignal
161-
// https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
161+
// clearTimeout pattern is 10x faster than using AbortController
162+
// Note: signal.abort is slow ~6000ns
162163
if (timeoutEarly) {
163-
timeoutAbort = new AbortController()
164-
promises.push(
165-
setTimeout(
166-
request.context.getRemainingTimeInMillis() -
167-
plugin.timeoutEarlyInMillis,
168-
undefined,
169-
{ signal: timeoutAbort.signal }
170-
).then(() => {
164+
let timeoutResolve
165+
const timeoutPromise = new Promise((resolve, reject) => {
166+
timeoutResolve = () => {
171167
handlerAbort.abort()
172-
return plugin.timeoutEarlyResponse()
173-
})
168+
try {
169+
resolve(plugin.timeoutEarlyResponse())
170+
} catch (e) {
171+
reject(e)
172+
}
173+
}
174+
})
175+
timeoutID = setTimeout(
176+
timeoutResolve,
177+
request.context.getRemainingTimeInMillis() -
178+
plugin.timeoutEarlyInMillis
174179
)
180+
promises.push(timeoutPromise)
175181
}
176182
request.response = await Promise.race(promises)
177-
timeoutAbort?.abort() // lambdaHandler may not support .then()
183+
clearTimeout(timeoutID)
178184

179185
plugin.afterHandler?.()
180186
await runMiddlewares(request, afterMiddlewares, plugin)
181187
}
182188
} catch (e) {
183189
// timeout should be aborted when errors happen in handler
184-
timeoutAbort?.abort()
190+
clearTimeout(timeoutID)
185191

186192
// Reset response changes made by after stack before error thrown
187193
request.response = undefined

0 commit comments

Comments
 (0)