Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take coldstart delays out of Lambda timeout path #726

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

---

## [5.9.0] 2023-10-24
## [5.9.0 - 5.9.1] 2023-10-24

### Added

Expand Down
45 changes: 20 additions & 25 deletions src/invoke-lambda/exec/spawn.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = function spawnChild (params, callback) {

// Let's go!
let pid = 'init'
let child, error, closed
let child, error, closed, to, check
function start () {
child = spawn(command, args, options)
pid = child.pid
Expand All @@ -28,33 +28,28 @@ module.exports = function spawnChild (params, callback) {
update.debug.status(`[${requestID}] Emitted 'close' (pid ${pid}, code '${code}', signal '${signal}')`)
shutdown('child process closure')
})

// Set an execution timeout
to = setTimeout(function () {
timedOut = true
let duration = `${timeout / 1000}s`
update.warn(`[${requestID}] Timed out after hitting its ${duration} timeout!`)
shutdown(`${duration} timeout`)
}, timeout)

// Terminate once we find a result from the runtime API
// 25ms is arbitrary, but hopefully it should be solid enough
check = setInterval(function () {
if (invocations[requestID].response ||
invocations[requestID].initError ||
invocations[requestID].error) {
shutdown('runtime API completion check')
}
}, 25)
}
if (coldstart) {
if (coldstart < timeout) setTimeout(start, coldstart)
else {
update.debug.status(`[${requestID}] Coldstart simulator: coldstart of ${coldstart}ms exceeds timeout of ${timeout}ms, not spawning the Lambda`)
}
}
if (coldstart) setTimeout(start, coldstart)
else start()

// Set an execution timeout
let to = setTimeout(function () {
timedOut = true
let duration = `${timeout / 1000}s`
update.warn(`[${requestID}] Timed out after hitting its ${duration} timeout!`)
shutdown(`${duration} timeout`)
}, timeout)

// Terminate once we find a result from the runtime API
// 25ms is arbitrary, but hopefully it should be solid enough
let check = setInterval(function () {
if (invocations[requestID].response ||
invocations[requestID].initError ||
invocations[requestID].error) {
shutdown('runtime API completion check')
}
}, 25)

// Ensure we don't have dangling processes due to open connections, etc. before we wrap up
function shutdown (event) {
// Immediately shut down all timeouts and intervals
Expand Down
8 changes: 7 additions & 1 deletion src/sandbox/maybe-hydrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ let { chars } = require('@architect/utils')
* Checks for the existence of supported dependency manifests, and auto-hydrates dependencies as necessary
* Supported manifests: `package.json`, `requirements.txt`, `Gemfile`
*/
module.exports = function maybeHydrate ({ cwd, inventory, quiet, deleteVendor }, callback) {
module.exports = function maybeHydrate ({ cwd, inventory, quiet, deleteVendor, update }, callback) {
let { inv } = inventory
if (!inv.lambdaSrcDirs || !inv.lambdaSrcDirs.length) {
callback()
}
else {
/**
* Coldstart simulator status
*/
let coldstart = inv._project?.preferences?.sandbox?.coldstart || false
if (coldstart) {
update.done('Started with Lambda coldstart simulator')
}

// Enable vendor dir deletion by default
let del = deleteVendor === undefined ? true : deleteVendor
Expand Down
31 changes: 18 additions & 13 deletions test/integration/http/misc-http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ function runTests (runType, t) {
t.plan(1)
let file = join(process.cwd(), 'test', 'mock', 'coldstart', 'src', 'http', 'get-chonk', 'chonky.txt')
let MB = 1024 * 1024
let size = MB * 115
let size = MB * 50
if (existsSync(file)) t.equal(statSync(file).size, size, 'Found coldstart enchonkinator')
else {
let start = Date.now()
Expand All @@ -312,29 +312,34 @@ function runTests (runType, t) {
}
})

t.test(`[Misc / ${runType}] No coldstart timeout`, t => {
t.plan(1)
t.test(`[Misc / ${runType}] No coldstart delay`, t => {
t.plan(2)
let start = Date.now()
tiny.get({
url: url + '/smol'
}, function _got (err, result) {
if (err) t.end(err)
else t.deepEqual(result.body, { ok: true }, 'Lambda did not timeout from a coldstart')
else {
t.deepEqual(result.body, { ok: true }, 'Lambda did not timeout from a coldstart')
let time = Date.now() - start
// 450 is probably extremely conservative, but sometimes CI can be super slow
t.ok(time < 450, `Response returned quickly (${time}ms)`)
}
})
})

t.test(`[Misc / ${runType}] Coldstart timeout`, t => {
t.plan(3)
t.test(`[Misc / ${runType}] Coldstart delay`, t => {
t.plan(2)
let start = Date.now()
tiny.get({
url: url + '/chonk'
}, function _got (err, result) {
if (err) {
let message = 'Timeout error'
let time = '1 second'
t.equal(err.statusCode, 500, 'Errors with 500')
t.match(err.body, new RegExp(message), `Errors with message: '${message}'`)
t.match(err.body, new RegExp(time), `Timed out set to ${time}`)
if (err) t.end(err)
else {
t.deepEqual(result.body, { ok: true }, 'Lambda did not timeout from a coldstart')
let time = Date.now() - start
t.ok(time > 450, `Response returned slowly (${time}ms)`)
}
else t.end(result)
})
})

Expand Down
Loading