Worker: error body carries the typed code (closes gpu-busy-untyped-http)

worker_error_body is the worker's one error wire shape; GpuBusy.code
reaches the daemon as a field. GpuBusy is documented as placement only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-22 23:13:50 +02:00
commit 880f1c2dcf
3 changed files with 18 additions and 7 deletions

View file

@ -4,7 +4,6 @@ import json
import os
import shutil
import time
import traceback
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Lock
from types import SimpleNamespace
@ -394,11 +393,7 @@ class Handler(BaseHTTPRequestHandler):
payload = json.loads(self.rfile.read(length).decode("utf-8") or "{}")
write_json(self, 200, generate(payload))
except Exception as error:
write_json(self, 500, {
"ok": False,
"error": str(error),
"traceback": traceback.format_exc(),
})
write_json(self, 500, text_encoding.worker_error_body(error))
def log_message(self, fmt, *values):
print(f"{self.address_string()} - {fmt % values}", flush=True)

View file

@ -180,6 +180,12 @@ class ChildBoundary(unittest.TestCase):
""")
self.assertEqual(raised.exception.free_vram, 1234)
def test_worker_error_body_carries_the_code(self):
busy = te.worker_error_body(te.GpuBusy(GIB))
self.assertIs(busy["ok"], False)
self.assertEqual(busy["code"], "gpu-busy")
self.assertIsNone(te.worker_error_body(RuntimeError("x"))["code"])
def test_other_failures_name_the_exit_code_and_never_the_text(self):
with self.assertRaises(RuntimeError) as raised:
self.run_stub("import sys; sys.exit(1)", texts=(SECRET,))

View file

@ -12,6 +12,7 @@ import subprocess
import sys
import tempfile
import time
import traceback
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
@ -33,7 +34,11 @@ LLAMA_BASE = "meta-llama/Meta-Llama-3-8B-Instruct" # the base model the mntp ad
class GpuBusy(RuntimeError):
"""The card has too little free VRAM to host the encoder; there is no CPU fallback."""
"""Placement refusal: too little free VRAM to host the encoder, and no CPU fallback.
Whether heavy work may start at all is the daemon's tenancy gate, which sees the host; this is only
the child sizing its own placement from what torch can see.
"""
code = "gpu-busy"
@ -49,6 +54,11 @@ def plan_placement(free_vram: int, mem_available: int) -> dict:
return {0: free_vram - GPU_RESERVE, "cpu": max(mem_available - CPU_RESERVE, 0)}
def worker_error_body(error: BaseException) -> dict:
"""The worker's one error wire shape: a typed code (GpuBusy.code, else None) beside the text."""
return {"ok": False, "error": str(error), "code": getattr(error, "code", None), "traceback": traceback.format_exc()}
def read_snapshot_revision(hf_home: Path, repo_id: str) -> str:
"""The commit the mounted Hugging Face cache resolves a repo's main ref to."""
ref = Path(hf_home) / "hub" / ("models--" + repo_id.replace("/", "--")) / "refs" / "main"