@@ -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+ // 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+ timeout ?: 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 . timeout !== undefined ) this . timeout = options . timeout ;
264270 }
265271
266272 // Create schedules table if it doesn't exist
@@ -577,6 +583,23 @@ export class Container<Env = unknown> extends DurableObject<Env> {
577583 await this . stop ( ) ;
578584 }
579585
586+ /**
587+ * Called when the timeout expires and the container needs to be forcefully killed.
588+ * This is a timeout that is absolute from container start time, regardless of activity.
589+ * When this timeout expires, the container will be forcefully killed with SIGKILL.
590+ *
591+ * Override this method in subclasses to handle timeout events.
592+ * By default, this method calls `this.destroy()` to forcefully kill the container.
593+ */
594+ public async onHardTimeoutExpired ( ) : Promise < void > {
595+ if ( ! this . container . running ) {
596+ return ;
597+ }
598+
599+ console . log ( `Container timeout expired after ${ this . timeout } . Forcefully killing container.` ) ;
600+ await this . destroy ( ) ;
601+ }
602+
580603 /**
581604 * Error handler for container errors
582605 * Override this method in subclasses to handle container errors
@@ -598,6 +621,18 @@ export class Container<Env = unknown> extends DurableObject<Env> {
598621 this . sleepAfterMs = Date . now ( ) + timeoutInMs ;
599622 }
600623
624+ /**
625+ * Set up the timeout when the container starts
626+ * This is called internally when the container starts
627+ */
628+ private setupTimeout ( ) {
629+ if ( this . timeout ) {
630+ const timeoutMs = parseTimeExpression ( this . timeout ) * 1000 ;
631+ this . containerStartTime = Date . now ( ) ;
632+ this . timeoutMs = this . containerStartTime + timeoutMs ;
633+ }
634+ }
635+
601636 // ==================
602637 // SCHEDULING
603638 // ==================
@@ -798,6 +833,8 @@ export class Container<Env = unknown> extends DurableObject<Env> {
798833 private monitorSetup = false ;
799834
800835 private sleepAfterMs = 0 ;
836+ private timeoutMs ?: number ;
837+ private containerStartTime ?: number ;
801838
802839 // ==========================
803840 // GENERAL HELPERS
@@ -946,6 +983,9 @@ export class Container<Env = unknown> extends DurableObject<Env> {
946983 await this . scheduleNextAlarm ( ) ;
947984 this . container . start ( startConfig ) ;
948985 this . monitor = this . container . monitor ( ) ;
986+
987+ // Set up timeout when container starts
988+ this . setupTimeout ( ) ;
949989 } else {
950990 await this . scheduleNextAlarm ( ) ;
951991 }
@@ -1147,15 +1187,24 @@ export class Container<Env = unknown> extends DurableObject<Env> {
11471187 return ;
11481188 }
11491189
1190+ // Check timeout first (takes priority over activity timeout)
1191+ if ( this . isTimeoutExpired ( ) ) {
1192+ await this . onHardTimeoutExpired ( ) ;
1193+ return ;
1194+ }
1195+
11501196 if ( this . isActivityExpired ( ) ) {
11511197 await this . onActivityExpired ( ) ;
11521198 // renewActivityTimeout makes sure we don't spam calls here
11531199 this . renewActivityTimeout ( ) ;
11541200 return ;
11551201 }
11561202
1157- // Math.min(3m or maxTime, sleepTimeout)
1203+ // Math.min(3m or maxTime, sleepTimeout, timeout )
11581204 minTime = Math . min ( minTimeFromSchedules , minTime , this . sleepAfterMs ) ;
1205+ if ( this . timeoutMs ) {
1206+ minTime = Math . min ( minTime , this . timeoutMs ) ;
1207+ }
11591208 const timeout = Math . max ( 0 , minTime - Date . now ( ) ) ;
11601209
11611210 // await a sleep for maxTime to keep the DO alive for
@@ -1167,7 +1216,7 @@ export class Container<Env = unknown> extends DurableObject<Env> {
11671216 return ;
11681217 }
11691218
1170- this . timeout = setTimeout ( ( ) => {
1219+ this . timeoutId = setTimeout ( ( ) => {
11711220 resolve ( ) ;
11721221 } , timeout ) ;
11731222 } ) ;
@@ -1178,7 +1227,7 @@ export class Container<Env = unknown> extends DurableObject<Env> {
11781227 // the next alarm is the one that decides if it should stop the loop.
11791228 }
11801229
1181- timeout ?: ReturnType < typeof setTimeout > ;
1230+ timeoutId ?: ReturnType < typeof setTimeout > ;
11821231 resolve ?: ( ) => void ;
11831232
11841233 // synchronises container state with the container source of truth to process events
@@ -1292,4 +1341,8 @@ export class Container<Env = unknown> extends DurableObject<Env> {
12921341 private isActivityExpired ( ) : boolean {
12931342 return this . sleepAfterMs <= Date . now ( ) ;
12941343 }
1344+
1345+ private isTimeoutExpired ( ) : boolean {
1346+ return this . timeoutMs !== undefined && this . timeoutMs <= Date . now ( ) ;
1347+ }
12951348}
0 commit comments