@@ -227,6 +227,11 @@ export class Container<Env = unknown> extends DurableObject<Env> {
227227 // The container won't get a SIGKILL if this threshold is triggered.
228228 sleepAfter : string | number = DEFAULT_SLEEP_AFTER ;
229229
230+ // Hard timeout after which the container will be forcefully killed
231+ // This timeout is absolute from container start time, regardless of activity
232+ // When this timeout expires, the container is sent a SIGKILL signal
233+ hardTimeout ?: string | number ;
234+
230235 // Container configuration properties
231236 // Set these properties directly in your container instance
232237 envVars : ContainerStartOptions [ 'env' ] = { } ;
@@ -261,6 +266,7 @@ export class Container<Env = unknown> extends DurableObject<Env> {
261266 if ( options ) {
262267 if ( options . defaultPort !== undefined ) this . defaultPort = options . defaultPort ;
263268 if ( options . sleepAfter !== undefined ) this . sleepAfter = options . sleepAfter ;
269+ if ( options . hardTimeout !== undefined ) this . hardTimeout = options . hardTimeout ;
264270 }
265271
266272 // Create schedules table if it doesn't exist
@@ -577,6 +583,24 @@ export class Container<Env = unknown> extends DurableObject<Env> {
577583 await this . stop ( ) ;
578584 }
579585
586+ /**
587+ * Lifecycle method called when the container hard timeout expires.
588+ *
589+ * This timeout is absolute from container start time, regardless of activity.
590+ * When this timeout expires, the container will be forcefully killed with SIGKILL.
591+ *
592+ * Override this method in subclasses to handle hard timeout events.
593+ * By default, this method calls `this.destroy()` to forcefully kill the container.
594+ */
595+ public async onHardTimeoutExpired ( ) : Promise < void > {
596+ if ( ! this . container . running ) {
597+ return ;
598+ }
599+
600+ console . log ( `Container hard timeout expired after ${ this . hardTimeout } . Forcefully killing container.` ) ;
601+ await this . destroy ( ) ;
602+ }
603+
580604 /**
581605 * Error handler for container errors
582606 * Override this method in subclasses to handle container errors
@@ -598,6 +622,18 @@ export class Container<Env = unknown> extends DurableObject<Env> {
598622 this . sleepAfterMs = Date . now ( ) + timeoutInMs ;
599623 }
600624
625+ /**
626+ * Set up the hard timeout when the container starts
627+ * This is called internally when the container starts
628+ */
629+ private setupHardTimeout ( ) {
630+ if ( this . hardTimeout ) {
631+ const hardTimeoutMs = parseTimeExpression ( this . hardTimeout ) * 1000 ;
632+ this . containerStartTime = Date . now ( ) ;
633+ this . hardTimeoutMs = this . containerStartTime + hardTimeoutMs ;
634+ }
635+ }
636+
601637 // ==================
602638 // SCHEDULING
603639 // ==================
@@ -798,6 +834,8 @@ export class Container<Env = unknown> extends DurableObject<Env> {
798834 private monitorSetup = false ;
799835
800836 private sleepAfterMs = 0 ;
837+ private hardTimeoutMs ?: number ;
838+ private containerStartTime ?: number ;
801839
802840 // ==========================
803841 // GENERAL HELPERS
@@ -946,6 +984,9 @@ export class Container<Env = unknown> extends DurableObject<Env> {
946984 await this . scheduleNextAlarm ( ) ;
947985 this . container . start ( startConfig ) ;
948986 this . monitor = this . container . monitor ( ) ;
987+
988+ // Set up hard timeout when container starts
989+ this . setupHardTimeout ( ) ;
949990 } else {
950991 await this . scheduleNextAlarm ( ) ;
951992 }
@@ -1147,15 +1188,24 @@ export class Container<Env = unknown> extends DurableObject<Env> {
11471188 return ;
11481189 }
11491190
1191+ // Check hard timeout first (takes priority over activity timeout)
1192+ if ( this . isHardTimeoutExpired ( ) ) {
1193+ await this . onHardTimeoutExpired ( ) ;
1194+ return ;
1195+ }
1196+
11501197 if ( this . isActivityExpired ( ) ) {
11511198 await this . onActivityExpired ( ) ;
11521199 // renewActivityTimeout makes sure we don't spam calls here
11531200 this . renewActivityTimeout ( ) ;
11541201 return ;
11551202 }
11561203
1157- // Math.min(3m or maxTime, sleepTimeout)
1204+ // Math.min(3m or maxTime, sleepTimeout, hardTimeout )
11581205 minTime = Math . min ( minTimeFromSchedules , minTime , this . sleepAfterMs ) ;
1206+ if ( this . hardTimeoutMs ) {
1207+ minTime = Math . min ( minTime , this . hardTimeoutMs ) ;
1208+ }
11591209 const timeout = Math . max ( 0 , minTime - Date . now ( ) ) ;
11601210
11611211 // await a sleep for maxTime to keep the DO alive for
@@ -1292,4 +1342,8 @@ export class Container<Env = unknown> extends DurableObject<Env> {
12921342 private isActivityExpired ( ) : boolean {
12931343 return this . sleepAfterMs <= Date . now ( ) ;
12941344 }
1345+
1346+ private isHardTimeoutExpired ( ) : boolean {
1347+ return this . hardTimeoutMs !== undefined && this . hardTimeoutMs <= Date . now ( ) ;
1348+ }
12951349}
0 commit comments