|
12 | 12 | //! The runner hosts as many concurrent actors as the engine places on it, |
13 | 13 | //! each with its own child process on its own port; the pool's request |
14 | 14 | //! concurrency decides how many that is (1 in the recommended game-server |
15 | | -//! setup). When the last actor stops the process exits so the platform reaps |
16 | | -//! the instance. |
| 15 | +//! setup). The instance stays warm after its last actor stops and never |
| 16 | +//! self-exits; the engine reaps it by draining the `/start` connection once |
| 17 | +//! the request lifespan elapses, or the platform sends a SIGTERM. |
17 | 18 |
|
18 | 19 | mod actor; |
19 | 20 | mod child; |
@@ -167,12 +168,12 @@ pub fn effective_stop_grace() -> Duration { |
167 | 168 | } |
168 | 169 | } |
169 | 170 |
|
170 | | -/// End the process. Called when the LAST actor on this instance is gone (or a |
171 | | -/// failed start poisoned an otherwise idle instance): the instance drains |
172 | | -/// instead of lingering for the next placement. The runner is PID 1 in the |
| 171 | +/// End the process. Only the platform shutdown signal drives this now: actors |
| 172 | +/// stopping or failing to start no longer exit the instance, so it stays warm |
| 173 | +/// and reusable and its logs have time to drain. The runner is PID 1 in the |
173 | 174 | /// image, so exiting stops the container and the platform reaps the instance. |
174 | 175 | pub fn request_exit(actor_id: &str, reason: &str) { |
175 | | - tracing::info!(actor_id = %actor_id, reason, "actor finished, exiting container"); |
| 176 | + tracing::info!(actor_id = %actor_id, reason, "shutting down container"); |
176 | 177 | EXIT.cancel(); |
177 | 178 | } |
178 | 179 |
|
@@ -346,18 +347,21 @@ async fn async_main() -> Result<()> { |
346 | 347 | )); |
347 | 348 | tracing::info!(port, "container-runner serverless front door listening"); |
348 | 349 |
|
349 | | - // Wait for an exit request, then tear down. Two orders depending on why: |
| 350 | + // Wait for an exit request, then tear down. Only the signal path is live |
| 351 | + // today: nothing calls `request_exit` except `spawn_signal_handler`, which |
| 352 | + // sets `SIGNAL_SHUTDOWN` before cancelling `EXIT`, so the `else` branch is |
| 353 | + // currently unreachable and kept only as a fallback for a future |
| 354 | + // actor-driven exit. |
350 | 355 | // |
351 | 356 | // Signal (platform is reclaiming the instance): tell the engine FIRST so |
352 | 357 | // it can start re-placing actors immediately. Its per-actor stops run our |
353 | 358 | // on_destroy hooks, which SIGTERM children with the capped signal grace. |
354 | 359 | // The drain is bounded so an unreachable engine cannot eat the whole |
355 | 360 | // platform budget; the sweep then catches any child whose hooks never ran. |
356 | 361 | // |
357 | | - // Actor-driven exit (last actor stopped or a failed start poisoned an |
358 | | - // idle instance): no platform deadline. Children are already reaped by |
359 | | - // the hooks (the sweep is a no-op backstop), and the runtime drains |
360 | | - // unbounded so the /start SSE flushes its stopping frame cleanly. |
| 362 | + // Fallback actor-driven exit (unreachable today): no platform deadline. |
| 363 | + // Children are already reaped by the hooks (the sweep is a no-op backstop), |
| 364 | + // and the runtime drains unbounded so the /start SSE flushes cleanly. |
361 | 365 | EXIT.cancelled().await; |
362 | 366 | if SIGNAL_SHUTDOWN.load(Ordering::Acquire) { |
363 | 367 | if tokio::time::timeout(signal_drain_timeout(), runtime.shutdown()) |
|
0 commit comments