Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
Substrate fix: two-line change to lib/erlang/runtime.sx that lets
http-listen handler routes call gen_server:call without deadlocking.
1. er-sched-step-alive!: pass :pending-args (when set) to the
initial-fun call instead of always passing an empty list.
Default behavior (no field) stays (list) — drop-in safe.
2. er-bif-http-listen sx-handler: instead of er-apply-fun handler
inline (which blows up on receive's er-suspend-marker because
the connection thread has no scheduler step on its stack),
create a real er-process with :initial-fun = handler and
:pending-args = (list req-pl), then er-sched-run-all! to drain.
Any receive (e.g. gen_server:call) suspends + resumes inside
the SX scheduler frame the process owns. Read :exit-result
for the response proplist; marshal back to SX dict.
Investigation arc (see plans/fed-sx-milestone-2.md Blockers #4 +
Progress log):
- loops/fed-prims bf8d0bf2 diagnosed it as Erlang-substrate, not
OCaml mutex (Pattern A wrong, Pattern B right but sketchy).
- First Pattern B attempt failed: tried er-spawn-fun on a raw SX
lambda, hit (er-fun? fv) gate. Connection-thread bisect
pinpointed the exact line.
- Real fix: use the existing er-fun (user's handler) directly,
but feed it via :pending-args so step-alive's hardcoded
(list) doesn't drop the request arg.
Acceptance:
- new next/tests/smoke_kernel_route.sh: 6/6 over real HTTP
(welcome /, /actors/alice, /actors/alice/outbox with
gen_server-backed tip, /actors/alice/inbox, unknown-actor,
via http_server:start(P, [{kernel, nx_kernel}])).
- next/tests/http_server_tcp.sh: 5/5 (bumped wait_bound from
30s to 180s — cold boot is slow under sibling-loop CPU load
and the per-handler scheduler ramp adds a small margin).
- Erlang conformance: 761/761.
Step 12's two-instance smoke test is now unblocked — its full
Follow / Accept / Note flow can layer on top of this kernel-route
surface. m2 plan updated.
Pre-existing httpc_request.sh flakiness ("Undefined symbol:
http-request" on the live-call epochs) reproduces WITHOUT this
change — see git stash A/B in the investigation. Unrelated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
4.3 KiB
Bash
Executable File
146 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# next/tests/http_server_tcp.sh — Step 9a-tcp live TCP smoke test.
|
|
#
|
|
# Boots sx_server in the background with a script that loads
|
|
# http_server.erl and calls http_server:start/1 on a high port,
|
|
# then drives the running server with curl from this shell to
|
|
# verify the request → marshaling → route → marshaling → HTTP
|
|
# response chain end-to-end.
|
|
#
|
|
# Boot timing: ~10s for all `lib/erlang/*.sx` loads + module
|
|
# compile + spawn + Unix.bind. We hold the server's stdin open
|
|
# via `(cat file; sleep 60) | sx_server` so EOF doesn't trigger
|
|
# exit(0) before the listener finishes binding.
|
|
|
|
set -uo pipefail
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
|
|
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
if [ ! -x "$SX_SERVER" ]; then
|
|
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
|
fi
|
|
if [ ! -x "$SX_SERVER" ]; then
|
|
echo "ERROR: sx_server.exe not found." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PORT=51820
|
|
VERBOSE="${1:-}"
|
|
PASS=0; FAIL=0; ERRORS=""
|
|
|
|
EPOCH_FILE=$(mktemp)
|
|
LOG_FILE=$(mktemp)
|
|
cleanup() {
|
|
if [ -n "${SXPID:-}" ]; then
|
|
kill -KILL "$SXPID" 2>/dev/null || true
|
|
wait "$SXPID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "${HOLDPID:-}" ]; then
|
|
kill -KILL "$HOLDPID" 2>/dev/null || true
|
|
wait "$HOLDPID" 2>/dev/null || true
|
|
fi
|
|
rm -f "$EPOCH_FILE" "$LOG_FILE"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
cat > "$EPOCH_FILE" <<EPOCHS
|
|
(epoch 1)
|
|
(load "lib/erlang/tokenizer.sx")
|
|
(load "lib/erlang/parser.sx")
|
|
(load "lib/erlang/parser-core.sx")
|
|
(load "lib/erlang/parser-expr.sx")
|
|
(load "lib/erlang/parser-module.sx")
|
|
(load "lib/erlang/transpile.sx")
|
|
(load "lib/erlang/runtime.sx")
|
|
(load "lib/erlang/vm/dispatcher.sx")
|
|
(epoch 2)
|
|
(eval "(get (erlang-load-module (file-read \"next/kernel/http_server.erl\")) :name)")
|
|
(epoch 3)
|
|
(eval "(erlang-eval-ast \"http_server:start($PORT)\")")
|
|
EPOCHS
|
|
|
|
# Run sx_server with stdin held open via a long-running background
|
|
# `sleep` so EOF doesn't trigger exit(0) before the listener binds
|
|
# and the test finishes curling. Use a FIFO so we can capture both
|
|
# the holder process's PID and sx_server's PID explicitly — bash
|
|
# only captures the rightmost pipe stage with $!.
|
|
FIFO=$(mktemp -u)
|
|
mkfifo "$FIFO"
|
|
( cat "$EPOCH_FILE"; sleep 120 ) > "$FIFO" &
|
|
HOLDPID=$!
|
|
"$SX_SERVER" < "$FIFO" > "$LOG_FILE" 2>&1 &
|
|
SXPID=$!
|
|
rm -f "$FIFO" # both ends still hold open via the running procs
|
|
|
|
# Wait for the listener to bind (up to ~180s — cold boot can be slow
|
|
# under load from sibling loops, and the Blockers #4 :pending-args
|
|
# fix adds a small per-handler scheduler ramp).
|
|
BOUND=""
|
|
for i in $(seq 1 360); do
|
|
if (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then
|
|
exec 3<&-; exec 3>&-
|
|
BOUND="yes"
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
|
|
if [ -z "$BOUND" ]; then
|
|
echo "FAIL: listener never bound on port $PORT"
|
|
if [ "$VERBOSE" = "-v" ]; then
|
|
echo "--- sx_server output ---"
|
|
cat "$LOG_FILE"
|
|
echo "---"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
check_http() {
|
|
local desc="$1" method="$2" path="$3" auth="$4" expected_status="$5" expected_body_substr="$6"
|
|
local args=()
|
|
args+=(-s -o /tmp/http_body.out -w "%{http_code}")
|
|
args+=(-X "$method")
|
|
if [ -n "$auth" ]; then
|
|
args+=(-H "Authorization: $auth")
|
|
fi
|
|
if [ "$method" = "POST" ]; then
|
|
args+=(-d "")
|
|
fi
|
|
args+=("http://127.0.0.1:$PORT$path")
|
|
local code
|
|
code=$(curl "${args[@]}" 2>/dev/null || echo "000")
|
|
local body
|
|
body=$(cat /tmp/http_body.out 2>/dev/null || echo "")
|
|
local pass=1
|
|
if [ "$code" != "$expected_status" ]; then pass=0; fi
|
|
if [ -n "$expected_body_substr" ] && ! echo "$body" | grep -qF -- "$expected_body_substr"; then pass=0; fi
|
|
if [ $pass -eq 1 ]; then
|
|
PASS=$((PASS+1))
|
|
[ "$VERBOSE" = "-v" ] && echo " ok $desc ($code)"
|
|
else
|
|
FAIL=$((FAIL+1))
|
|
ERRORS+=" FAIL [$desc] code=$code body=$body
|
|
"
|
|
fi
|
|
}
|
|
|
|
check_http "GET / -> 200" GET / "" 200 ""
|
|
check_http "GET capabilities -> 200" GET /.well-known/sx-capabilities "" 200 "kernel:"
|
|
check_http "GET unknown -> 404" GET /no-such-path "" 404 ""
|
|
check_http "POST /activity no bearer -> 401" POST /activity "" 401 ""
|
|
check_http "POST /activity bad bearer -> 401" POST /activity "Bearer wrong" 401 ""
|
|
|
|
TOTAL=$((PASS+FAIL))
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo "ok $PASS/$TOTAL http_server_tcp tests passed (port $PORT)"
|
|
else
|
|
echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
|
|
echo "$ERRORS"
|
|
if [ "$VERBOSE" = "-v" ]; then
|
|
echo "--- sx_server output (last 30 lines) ---"
|
|
tail -30 "$LOG_FILE"
|
|
echo "---"
|
|
fi
|
|
fi
|
|
[ $FAIL -eq 0 ]
|