From 86e271681c1fc436f899fb7767dfef685be8df9d Mon Sep 17 00:00:00 2001 From: Sahil Shitole Date: Tue, 18 Aug 2026 19:46:22 +0530 Subject: [PATCH] fix(engine-runner): terminate requests for missing actors --- .../packages/engine-runner/src/tunnel.ts | 27 ++++++++- .../tests/tunnel-missing-actor.test.ts | 55 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 rivetkit-typescript/packages/engine-runner/tests/tunnel-missing-actor.test.ts diff --git a/rivetkit-typescript/packages/engine-runner/src/tunnel.ts b/rivetkit-typescript/packages/engine-runner/src/tunnel.ts index 63a70860c9..3c8f03a041 100644 --- a/rivetkit-typescript/packages/engine-runner/src/tunnel.ts +++ b/rivetkit-typescript/packages/engine-runner/src/tunnel.ts @@ -677,10 +677,35 @@ export class Tunnel { const actor = await this.#runner.getAndWaitForActor(req.actorId); if (!actor) { this.log?.warn({ - msg: "actor does not exist in handleRequestStart, request will leak", + msg: "ignoring request for unknown actor", actorId: req.actorId, requestId: requestIdStr, }); + + const body = new TextEncoder().encode("Actor not found"); + + this.#runner.__sendToServer({ + tag: "ToServerTunnelMessage", + val: { + messageId: { + gatewayId, + requestId, + messageIndex: 0, + }, + messageKind: { + tag: "ToServerResponseStart", + val: { + status: 503, + headers: new Map([ + ["x-rivet-error", "runner.actor_not_found"], + ["content-length", String(body.byteLength)], + ]), + body: body.buffer, + stream: false, + }, + }, + }, + }); return; } diff --git a/rivetkit-typescript/packages/engine-runner/tests/tunnel-missing-actor.test.ts b/rivetkit-typescript/packages/engine-runner/tests/tunnel-missing-actor.test.ts new file mode 100644 index 0000000000..50ee405f29 --- /dev/null +++ b/rivetkit-typescript/packages/engine-runner/tests/tunnel-missing-actor.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; +import { Tunnel } from "../src/tunnel"; + +describe("Tunnel missing actor request handling", () => { + it("returns a retryable 503 when a request arrives for an unloaded actor", async () => { + const sent: any[] = []; + + const runner = { + log: undefined, + getAndWaitForActor: vi.fn().mockResolvedValue(undefined), + __sendToServer: vi.fn((message) => { + sent.push(message); + }), + } as any; + + const tunnel = new Tunnel(runner); + + const gatewayId = new Uint8Array(16).buffer; + const requestId = new Uint8Array(16).buffer; + + await tunnel.handleTunnelMessage({ + messageId: { + gatewayId, + requestId, + messageIndex: 0, + }, + messageKind: { + tag: "ToClientRequestStart", + val: { + actorId: "missing-actor", + method: "GET", + path: "/", + headers: new Map(), + body: null, + stream: false, + }, + }, + } as any); + + expect(sent).toHaveLength(1); + expect(sent[0].tag).toBe("ToServerTunnelMessage"); + expect(sent[0].val.messageKind.tag).toBe("ToServerResponseStart"); + expect(sent[0].val.messageKind.val.status).toBe(503); + expect(sent[0].val.messageKind.val.headers.get("x-rivet-error")).toBe( + "runner.actor_not_found", + ); + expect(sent[0].val.messageKind.val.headers.get("content-length")).toBe( + "15", + ); + expect(new TextDecoder().decode(sent[0].val.messageKind.val.body)).toBe( + "Actor not found", + ); + expect(sent[0].val.messageKind.val.stream).toBe(false); + }); +});