Skip to content

containers: Honor subclass sleepAfter override during initial activity setup#192

Merged
gabivlj merged 1 commit intocloudflare:mainfrom
quantizor:fix/sleepafter-subclass-init-race
Apr 28, 2026
Merged

containers: Honor subclass sleepAfter override during initial activity setup#192
gabivlj merged 1 commit intocloudflare:mainfrom
quantizor:fix/sleepafter-subclass-init-race

Conversation

@quantizor
Copy link
Copy Markdown
Contributor

@quantizor quantizor commented Apr 22, 2026

Fixes #127.

Root cause

Subclass class-field initializers (sleepAfter = '2h') run after the base class constructor returns. The base constructor calls renewActivityTimeout() synchronously inside its blockConcurrencyWhile callback, at a point where this.sleepAfter is still the base default ('10m'). The initial sleepAfterMs is therefore always computed from the default, not the subclass override.

Reported timeline in #127 (sleepAfter = '2h', container sleeps after 1–2 minutes) is consistent with this: the first alarm fires inside the 10-minute base-default window before any containerFetch has had a chance to call renewActivityTimeout() with the correct value.

Fix

Swap two statements in the constructor's blockConcurrencyWhile callback so scheduleNextAlarm() runs before renewActivityTimeout():

// before
this.renewActivityTimeout();
await this.scheduleNextAlarm();

// after
await this.scheduleNextAlarm();
this.renewActivityTimeout();

The await yields a microtask, during which super() returns and subclass class-field initializers run. By the time renewActivityTimeout() executes, this.sleepAfter is the subclass-overridden value. scheduleNextAlarm doesn't depend on sleepAfterMs, so the reorder is safe.

Tests

Verified locally against a red/green cycle with a targeted Jest test that constructs a subclass with sleepAfter = '2h', awaits the constructor's blockConcurrencyWhile promise, and asserts sleepAfterMs is ~2h out. Not included here because the Jest harness in this repo currently lives alongside #191's test infrastructure — once either this PR or #191 lands, the regression can be pinned in a follow-up. Happy to include the test if you'd prefer the harness land with this PR.

@quantizor quantizor requested a review from a team as a code owner April 22, 2026 17:12
…y setup

Subclass class-field initializers (e.g. `sleepAfter = "2h"`) run after
the base class constructor returns. The base constructor calls
`renewActivityTimeout()` synchronously inside its `blockConcurrencyWhile`
callback, so `this.sleepAfter` is always the base default (`"10m"`)
when the initial `sleepAfterMs` is computed. Subclass overrides take
effect on the next `renewActivityTimeout` call (e.g. from the first
`containerFetch`), but if the first alarm fires before any fetch, the
container can be killed early — the repro in cloudflare#127.

Swap the order: `await scheduleNextAlarm()` first — the await yields a
microtask, which lets super() return and subclass class-field
initializers run — then `renewActivityTimeout()` reads the
now-overridden `this.sleepAfter`. `scheduleNextAlarm` doesn't depend on
`sleepAfterMs`, so the reorder is safe.

Fixes cloudflare#127.
@quantizor quantizor force-pushed the fix/sleepafter-subclass-init-race branch from 0a83a51 to cf41295 Compare April 22, 2026 17:17
@mikenomitch
Copy link
Copy Markdown
Collaborator

Hey! - Appreicate the PR!

We will review this shortly and hopefully get it merged/release

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 28, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/containers/@cloudflare/containers@192

commit: cf41295

@gabivlj gabivlj merged commit c661b9e into cloudflare:main Apr 28, 2026
4 checks passed
@quantizor
Copy link
Copy Markdown
Contributor Author

Thank you! @gabivlj can #191 be looked at as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"SleepAfter" is not effective

3 participants