From 856a3ffd3331c90d9730dedec635ee39bf9bd97b Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 3 Nov 2025 11:36:39 -0800 Subject: [PATCH] fix(runner): close active requests after onActorStop has finished running --- engine/sdks/typescript/runner/src/mod.ts | 10 ++++++++-- engine/sdks/typescript/runner/src/tunnel.ts | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/engine/sdks/typescript/runner/src/mod.ts b/engine/sdks/typescript/runner/src/mod.ts index 9a8180a85a..9dfa78fe20 100644 --- a/engine/sdks/typescript/runner/src/mod.ts +++ b/engine/sdks/typescript/runner/src/mod.ts @@ -164,12 +164,19 @@ export class Runner { if (!actor) return; // If onActorStop times out, Pegboard will handle this timeout with ACTOR_STOP_THRESHOLD_DURATION_MS + // + // If we receive a request while onActorStop is running, a Service + // Unavailable error will be returned to Guard and the request will be + // retried try { await this.#config.onActorStop(actorId, actor.generation); } catch (err) { console.error(`Error in onActorStop for actor ${actorId}:`, err); } + // Close requests after onActorStop so you can send messages over the tunnel + this.#tunnel?.closeActiveRequests(actor); + this.#sendActorStateUpdate(actorId, actor.generation, "stopped"); } @@ -217,6 +224,7 @@ export class Runner { ); } + // IMPORTANT: Make sure to call stopActiveRequests if calling #removeActor #removeActor( actorId: string, generation?: number, @@ -242,8 +250,6 @@ export class Runner { this.#actors.delete(actorId); - // Unregister actor from tunnel - this.#tunnel?.unregisterActor(actor); return actor; } diff --git a/engine/sdks/typescript/runner/src/tunnel.ts b/engine/sdks/typescript/runner/src/tunnel.ts index d617a795fc..5c93445392 100644 --- a/engine/sdks/typescript/runner/src/tunnel.ts +++ b/engine/sdks/typescript/runner/src/tunnel.ts @@ -183,7 +183,7 @@ export class Tunnel { } } - unregisterActor(actor: ActorInstance) { + closeActiveRequests(actor: ActorInstance) { const actorId = actor.actorId; // Terminate all requests for this actor @@ -469,7 +469,12 @@ export class Tunnel { msg: "ignoring websocket for unknown actor", actorId: open.actorId, }); - // Send close immediately + + // NOTE: Closing a WebSocket before open is equivalent to a Service + // Unavailable error and will cause Guard to retry the request + // + // See + // https://github.com/rivet-dev/rivet/blob/222dae87e3efccaffa2b503de40ecf8afd4e31eb/engine/packages/pegboard-gateway/src/lib.rs#L238 this.#sendMessage(requestId, { tag: "ToServerWebSocketClose", val: {