Each gpu-worker.lsp now listens on both wire-TCP (existing :8320) and HTTP/1.1+CORS (new :8321), sharing one handle-request dispatcher. Lets a tab on https://lumbda.com/playground/ POST to its own machine via http://localhost:8321/ — browsers permit localhost from HTTPS origins without TLS, so no proxy, no cert, no fox-owned infra required for the decentralized run-your-own-bend story. main() forks at startup: child runs http-run-loop on :8321, parent keeps existing run-loop on :8320. Adding a new op-head to handle-request exposes it over both transports automatically. Binary modes (BSHK/BCGB/BSCP/BSRT/BSB3) stay wire-only — they exist for native callers who already cache the binary locally; browser callers send S-expression recipes the worker dispatches the same way. Two latent defects fixed to make CPU-only and Python-tier hosts work: - vram-used-mib now file-exists? guards /usr/bin/nvidia-smi. Python tier's spawn-process-stdio raises FileNotFoundError on missing binary, not returning #f as the prior code expected, which crashed every worker on a CPU-only laptop. - fork-self return discriminated via (number? pid) not (eq? pid 0). Python tier's (eq? 0 #f) returns #t because == conflates int 0 with bool False; pre-existing run-loop has the same risk but C/asm tier (identity eq?) masks it for the production case. Phase 2 (server-side factory ops: compile uploaded .lsp recipes into .bin before bending — the foxhop champion-circuit workflow) deferred until authentication lands; today a worker on the public internet would let any caller occupy our GPU. Operational Caddy + DNS proposals in plans/bend-http-deploy.md cover the personal-remote-access endpoint chain (proxy.unturf.com edge → ai.foxhop.net Caddy → 3090-ai:8321) gated by trusted-IP allowlist — applied separately. Also codifies the playground "CSS Grid only, never flexbox" rule in CLAUDE.md: all www/ and wasm/ stylesheets are already grid-only; documenting the invariant so future edits don't drift. Tests: smoke-bend-http.sh — (ping)→(ok pong), unknown-op fallback, OPTIONS CORS preflight — all PASS. Wire path unchanged, verified round-trip via 8-digit-prefix framing.
105 lines
3.7 KiB
Bash
Executable file
105 lines
3.7 KiB
Bash
Executable file
#!/bin/bash
|
|
# smoke-bend-http.sh — exercise gpu-worker.lsp's HTTP/8321 path.
|
|
#
|
|
# Boots gpu-worker on a test port pair (avoids prod 8320/8321), then:
|
|
# 1. curl -X POST '(ping)' → expect "(ok pong)"
|
|
# 2. curl -X POST '(echo "hello")' → expect "(echo \"hello\")"
|
|
# (current handle-request has no 'echo head, returns unknown-op —
|
|
# so we expect (error (unknown-op echo)) instead, which still
|
|
# round-trips the wire correctly)
|
|
# 3. curl -X OPTIONS preflight → expect 204 + CORS headers
|
|
#
|
|
# Memory-discipline pattern lifted from lumbda CLAUDE.md (asm heap
|
|
# never shrinks): kernel cap, trap cleanup, wall-clock timeout,
|
|
# explicit PID kill, straggler verify.
|
|
|
|
set -e
|
|
ulimit -v 524288 # 512 MB kernel cap — process gets SIGKILL at cap
|
|
|
|
# Test ports — high so they never collide with prod 8320/8321
|
|
WIRE_PORT=18320
|
|
HTTP_PORT=18321
|
|
|
|
# Pick tier: env override > default Python (always available, has GC)
|
|
LUMBDA="${LUMBDA:-$(cd "$(dirname "$0")/../.." && pwd)/lumbda.py}"
|
|
case "$LUMBDA" in
|
|
*.py) LUMBDA_RUN="python3 $LUMBDA" ;;
|
|
*) LUMBDA_RUN="$LUMBDA" ;;
|
|
esac
|
|
|
|
trap 'kill -9 $WORKER_PID 2>/dev/null || true;
|
|
pkill -9 -u "$USER" -f "gpu-worker.lsp --port $WIRE_PORT" 2>/dev/null || true' \
|
|
EXIT INT TERM
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
echo "=== smoke-bend-http (tier: $LUMBDA) ==="
|
|
|
|
# Boot worker — script-mode .lsp that loads gpu-worker.lsp + calls (main)
|
|
cat > /tmp/smoke-bend-http-boot.lsp <<EOF
|
|
(load "gpu-worker.lsp")
|
|
(main)
|
|
EOF
|
|
|
|
# Background the worker (foreground would block the test).
|
|
timeout 30 $LUMBDA_RUN /tmp/smoke-bend-http-boot.lsp \
|
|
--port $WIRE_PORT --http-port $HTTP_PORT > /tmp/smoke-bend-http-worker.log 2>&1 &
|
|
WORKER_PID=$!
|
|
|
|
# Wait up to 5s for HTTP port to come up
|
|
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
if exec 3<>/dev/tcp/127.0.0.1/$HTTP_PORT 2>/dev/null; then
|
|
exec 3<&-; exec 3>&-
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
|
|
# ---- test 1: ping -----------------------------------------------
|
|
echo "[1/3] POST (ping) → expect (ok pong) ..."
|
|
PING_RESP=$(curl -sS -X POST -H 'Content-Type: text/plain' \
|
|
--data-binary '(ping)' "http://127.0.0.1:$HTTP_PORT/" --max-time 5)
|
|
echo " got: $PING_RESP"
|
|
if [ "$PING_RESP" != "(ok pong)" ]; then
|
|
echo " FAIL: expected (ok pong), got [$PING_RESP]"
|
|
echo " worker log:"
|
|
cat /tmp/smoke-bend-http-worker.log
|
|
exit 1
|
|
fi
|
|
echo " PASS"
|
|
|
|
# ---- test 2: unknown-op gracefully ------------------------------
|
|
echo "[2/3] POST (asdf 1 2) → expect (error (unknown-op asdf)) ..."
|
|
UNK_RESP=$(curl -sS -X POST -H 'Content-Type: text/plain' \
|
|
--data-binary '(asdf 1 2)' "http://127.0.0.1:$HTTP_PORT/" --max-time 5)
|
|
echo " got: $UNK_RESP"
|
|
case "$UNK_RESP" in
|
|
*unknown-op*) echo " PASS" ;;
|
|
*) echo " FAIL: expected unknown-op error, got [$UNK_RESP]"; exit 1 ;;
|
|
esac
|
|
|
|
# ---- test 3: OPTIONS preflight + CORS headers -------------------
|
|
echo "[3/3] OPTIONS / → expect 204 + CORS headers ..."
|
|
PREFLIGHT=$(curl -sSI -X OPTIONS "http://127.0.0.1:$HTTP_PORT/" --max-time 5)
|
|
echo "$PREFLIGHT" | head -5 | sed 's/^/ /'
|
|
case "$PREFLIGHT" in
|
|
*"204 No Content"*) ;;
|
|
*) echo " FAIL: expected 204, got [$PREFLIGHT]"; exit 1 ;;
|
|
esac
|
|
case "$PREFLIGHT" in
|
|
*"Access-Control-Allow-Origin"*) echo " PASS (CORS present)" ;;
|
|
*) echo " FAIL: missing CORS headers"; exit 1 ;;
|
|
esac
|
|
|
|
# Clean teardown
|
|
kill -9 $WORKER_PID 2>/dev/null || true
|
|
wait $WORKER_PID 2>/dev/null || true
|
|
|
|
# Straggler check — verify nothing left behind before reporting success
|
|
if pgrep -u "$USER" -f "gpu-worker.lsp --port $WIRE_PORT" >/dev/null; then
|
|
echo "STRAGGLER: gpu-worker on port $WIRE_PORT still running"
|
|
exit 1
|
|
fi
|
|
|
|
rm -f /tmp/smoke-bend-http-boot.lsp /tmp/smoke-bend-http-worker.log
|
|
echo "=== smoke-bend-http: PASS ==="
|