Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions container-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ tokio-tungstenite.workspace = true
tokio-util = { workspace = true, features = ["rt"] }
tracing.workspace = true
tracing-subscriber.workspace = true

[dev-dependencies]
ciborium.workspace = true
6 changes: 3 additions & 3 deletions container-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ There are two working local paths:
2. Run the built Unity server behind `container-runner`, create a Rivet actor locally,
and connect a FishNet client through the local Rivet guard URL.

A production image for **Rivet Compute** (the Cloud Run serverless model) is provided
see [Rivet Compute](#rivet-compute) below.
A production image for **Rivet Compute** (the serverless model) is provided. See
[Rivet Compute](#rivet-compute) below.

## Project Layout

Expand Down Expand Up @@ -207,7 +207,7 @@ Knobs: `LOAD_COUNT` (default 25), `LOAD_CONCURRENCY` (default 64).

**Running the full 1000:** 1000 local instances is ~2000 processes (a Rust runner + Node
child each) and needs a beefy host plus a raised `ulimit -n`. For a true 1000-container run,
point `load-test.mjs` at **Rivet Cloud** instead — Cloud Run scales the containers, no local
point `load-test.mjs` at **Rivet Cloud** instead, which scales the containers with no local
limit. Set the engine env and let a single pool auto-scale:

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const body = {
// Seconds the engine holds the /start request before draining to a fresh one.
request_lifespan: 900,
drain_grace_period: 30,
// 1:1 actor<->container mapping (Cloud Run concurrency=1 model).
// 1:1 actor<->container mapping (serverless concurrency=1 model).
slots_per_runner: 1,
max_runners: 1,
max_concurrent_actors: 1,
Expand Down
2 changes: 1 addition & 1 deletion container-runner/examples/e2e-test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Local end-to-end: self-hosted Rivet engine + the game container (container-runner
# wrapping the Node test server). All x86_64/amd64 to mirror Cloud Run.
# wrapping the Node test server). All x86_64/amd64 to mirror the serverless platform.
#
# Flow:
# create actor (POST /actors) -> engine POSTs /api/rivet/start to game:8080
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# server), then drive a WebSocket ping-pong through the guard to every one.
#
# Each container-runner instance is one "container": its own front-door port, its own child
# port, and its own serverless runner pool (load-<i>). This mirrors the Cloud Run 1:1
# port, and its own serverless runner pool (load-<i>). This mirrors the serverless 1:1
# actor<->container model locally, so `LOAD_COUNT` instances == that many containers.
#
# engine (:7420 guard, :7421 api)
Expand Down
21 changes: 21 additions & 0 deletions container-runner/examples/e2e-test/reject-clean.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Clean reject-second-start test. Checks destroy_ts (the real delete signal),
// NOT sleep_ts (which an explicit sleep would set). Single wake, long poll.
import { encode } from "cbor-x";
const NS=process.env.NS, TOK=process.env.TOK, origin="https://api.rivet.dev";
const H={authorization:`Bearer ${TOK}`,"content-type":"application/json"};
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const now=()=>new Date().toISOString().slice(11,19);
const KEY="rj-"+Date.now();
const input=Buffer.from(encode({port:7770})).toString("base64");
let r=await fetch(`${origin}/actors?namespace=${NS}`,{method:"POST",headers:H,body:JSON.stringify({name:"game",key:KEY,input,runner_name_selector:"default",crash_policy:"destroy"})});
const id=(await r.json()).actor?.actor_id; console.log(now(),"created",id);
async function gw(){try{const x=await fetch(`${origin}/gateway/${id}@${TOK}/`);return x.status;}catch(e){return 0;}}
async function st(){const s=await fetch(`${origin}/actors?actor_ids=${id}&namespace=${NS}`,{headers:H});const a=(await s.json()).actors?.[0];return a?{sleep_ts:a.sleep_ts,destroy_ts:a.destroy_ts}:{missing:true};}
for(let i=0;i<15;i++){if(await gw()===200)break;await sleep(700);}
console.log(now(),"started; wait 4s so started_once persists (grace=3s)"); await sleep(4000);
console.log(now(),"explicit sleep to force the second-start scenario"); await fetch(`${origin}/actors/${id}/sleep?namespace=${NS}`,{method:"POST",headers:H,body:"{}"});
await sleep(8000);
console.log(now(),"single wake -> second start -> reject -> ctx.destroy(); polling destroy_ts 60s...");
await gw();
for(let i=0;i<30;i++){await sleep(2000);const s=await st();console.log(now(),JSON.stringify(s));if(s.destroy_ts){console.log(now(),"=> DELETED (reject ctx.destroy works)");break;}if(s.missing){console.log(now(),"=> GONE");break;}}
console.log(now(),"DONE id="+id);
4 changes: 2 additions & 2 deletions container-runner/examples/test-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Build from the RIVET REPO ROOT so the workspace and examples are in context.
# Arch-agnostic: builds for the host/requested platform.
# Local (native, e.g. arm64 Mac): docker build -f container-runner/examples/test-server/Dockerfile -t game:latest .
# Cloud Run (x86_64): docker build --platform linux/amd64 -f container-runner/examples/test-server/Dockerfile -t game:amd64 .
# Serverless (x86_64): docker build --platform linux/amd64 -f container-runner/examples/test-server/Dockerfile -t game:amd64 .
#
# Production pattern (per spec) — instead of building from source, curl a released
# binary into an existing image:
Expand All @@ -33,7 +33,7 @@ COPY --from=builder /usr/local/bin/rivet-container-runner /usr/local/bin/rivet-c
COPY container-runner/examples/test-server/ /app/test-server/
RUN cd /app/test-server && npm install --omit=dev

# Cloud Run sets $PORT (the serverless front door). The child listens on CHILD_PORT.
# The serverless platform sets $PORT (the front door). The child listens on CHILD_PORT.
ENV PORT=8080 \
CHILD_PORT=7770 \
RIVET_ACTOR_NAME=game
Expand Down
2 changes: 1 addition & 1 deletion container-runner/examples/test-server/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This is the child process that container-runner (rivet-container-runner) spawns inside
// the container. It stands in for a real Unity FishNet dedicated server while we validate
// the Rivet -> Cloud Run -> container pipeline.
// the Rivet -> serverless -> container pipeline.
//
// Normal behavior:
// - Binds HTTP+WebSocket on $PORT (default 7770) on 0.0.0.0.
Expand Down
Loading
Loading