Skip to content

Commit 64c9644

Browse files
joe4devclaude
andcommitted
fix(init): emit INIT_REPORT(phase=init) for failed on-demand cold-start init
AWS performs a suppressed double init when an on-demand function's cold-start init fails: it emits INIT_REPORT(phase=init, status=error) for the failed cold-start init and INIT_REPORT(phase=invoke, status=error) for the retried init folded into the first invocation. The RIE only emitted the phase=invoke line. Add ReportInitPhaseError() to emit the missing phase=init line and call it on the on-demand init-failure path (onDemand && ErrInitDoneFailed), mirroring the existing ReportInitTimeout() / phase=invoke emission. This makes test_lambda_runtime_exit_segfault pass against LocalStack. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 867829a commit 64c9644

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

cmd/localstack/custom_interop.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,25 @@ func (c *CustomInteropServer) ReportInitTimeout() {
428428
fprintInitReport(c.logCollector, millisSince(c.initStart), "init", "timeout", "")
429429
}
430430

431+
// ReportInitPhaseError emits the AWS-style INIT_REPORT(phase=init, status=error) line for an
432+
// on-demand cold-start init that failed (e.g. a runtime crash or exit during module load).
433+
// AWS performs a suppressed double init: the failed cold-start init reports Phase: init here,
434+
// and the retried init folded into the first invocation reports Phase: invoke (see the invoke
435+
// handler). It is a no-op when no init error was recorded. The duration is rapid's measurement
436+
// of the Init phase when available, falling back to wall-clock for inits that died before
437+
// emitting their INIT_REPORT lifecycle event.
438+
func (c *CustomInteropServer) ReportInitPhaseError() {
439+
errType, _ := c.initErrorType.Load().(string)
440+
if errType == "" {
441+
return
442+
}
443+
initTimeMS, ok := c.eventsAPI.InitDurationMS()
444+
if !ok {
445+
initTimeMS = millisSince(c.initStart)
446+
}
447+
fprintInitReport(c.logCollector, initTimeMS, "init", "error", errType)
448+
}
449+
431450
// millisSince returns the wall-clock milliseconds elapsed since start.
432451
func millisSince(start time.Time) float64 {
433452
return float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond)

cmd/localstack/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,10 @@ func main() {
336336
// recorded the runtime-reported type).
337337
log.Debugln("Init failed; deferring to first invocation (on-demand suppressed init).")
338338
interopServer.RecordInitError(initResp.InitErrorType)
339+
// Emit the failed cold-start init's INIT_REPORT(phase=init) line. AWS performs a
340+
// suppressed double init, so the first invocation later emits a second
341+
// INIT_REPORT(phase=invoke) line for the retried (folded-in) init.
342+
interopServer.ReportInitPhaseError()
339343
case err != nil:
340344
// PC/SnapStart/MI, or an init-phase reset: report the failure now and exit. When the
341345
// runtime reported its own error via /init/error, SendInitErrorResponse already

0 commit comments

Comments
 (0)