From b10e55f04f8edd699c01baf60df02dfda2244709 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 28 Jun 2026 17:53:08 +0000 Subject: [PATCH] erlang: send_after to registered name + gen_server timeout returns (T5+T6, 771/771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T5 — send_after addresses a registered atom name; the delayed message lands in that process's mailbox (destination resolved at fire time, dead/unregistered targets drop silently). T6 — gen_server loop now handles the {reply,R,S,T} / {noreply,S,T} timeout-bearing callback returns by scheduling {timeout} to itself via send_after; handle_info({timeout}, S) fires when no other message arrives first. Sanity-checks the library hookup. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/erlang/runtime.sx | 13 ++++++++++ lib/erlang/scoreboard.json | 6 ++--- lib/erlang/scoreboard.md | 4 ++-- lib/erlang/tests/send_after.sx | 43 ++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/lib/erlang/runtime.sx b/lib/erlang/runtime.sx index 6cd8be20..41a507b7 100644 --- a/lib/erlang/runtime.sx +++ b/lib/erlang/runtime.sx @@ -1265,8 +1265,15 @@ {reply, Reply, NewState} -> From ! {Ref, Reply}, gen_server:loop(Mod, NewState); + {reply, Reply, NewState, Timeout} -> + From ! {Ref, Reply}, + erlang:send_after(Timeout, self(), {timeout}), + gen_server:loop(Mod, NewState); {noreply, NewState} -> gen_server:loop(Mod, NewState); + {noreply, NewState, Timeout} -> + erlang:send_after(Timeout, self(), {timeout}), + gen_server:loop(Mod, NewState); {stop, Reason, Reply, NewState} -> From ! {Ref, Reply}, exit(Reason) @@ -1274,11 +1281,17 @@ {'$gen_cast', Msg} -> case Mod:handle_cast(Msg, State) of {noreply, NewState} -> gen_server:loop(Mod, NewState); + {noreply, NewState, Timeout} -> + erlang:send_after(Timeout, self(), {timeout}), + gen_server:loop(Mod, NewState); {stop, Reason, NewState} -> exit(Reason) end; Other -> case Mod:handle_info(Other, State) of {noreply, NewState} -> gen_server:loop(Mod, NewState); + {noreply, NewState, Timeout} -> + erlang:send_after(Timeout, self(), {timeout}), + gen_server:loop(Mod, NewState); {stop, Reason, NewState} -> exit(Reason) end end.") diff --git a/lib/erlang/scoreboard.json b/lib/erlang/scoreboard.json index ede12189..614cd84c 100644 --- a/lib/erlang/scoreboard.json +++ b/lib/erlang/scoreboard.json @@ -1,7 +1,7 @@ { "language": "erlang", - "total_pass": 769, - "total": 769, + "total_pass": 771, + "total": 771, "suites": [ {"name":"tokenize","pass":62,"total":62,"status":"ok"}, {"name":"parse","pass":52,"total":52,"status":"ok"}, @@ -14,6 +14,6 @@ {"name":"fib","pass":8,"total":8,"status":"ok"}, {"name":"ffi","pass":37,"total":37,"status":"ok"}, {"name":"vm","pass":78,"total":78,"status":"ok"}, - {"name":"send_after","pass":8,"total":8,"status":"ok"} + {"name":"send_after","pass":10,"total":10,"status":"ok"} ] } diff --git a/lib/erlang/scoreboard.md b/lib/erlang/scoreboard.md index 7afac274..a5daa145 100644 --- a/lib/erlang/scoreboard.md +++ b/lib/erlang/scoreboard.md @@ -1,6 +1,6 @@ # Erlang-on-SX Scoreboard -**Total: 769 / 769 tests passing** +**Total: 771 / 771 tests passing** | | Suite | Pass | Total | |---|---|---|---| @@ -15,7 +15,7 @@ | ✅ | fib | 8 | 8 | | ✅ | ffi | 37 | 37 | | ✅ | vm | 78 | 78 | -| ✅ | send_after | 8 | 8 | +| ✅ | send_after | 10 | 10 | Generated by `lib/erlang/conformance.sh`. diff --git a/lib/erlang/tests/send_after.sx b/lib/erlang/tests/send_after.sx index 7b150f18..98810709 100644 --- a/lib/erlang/tests/send_after.sx +++ b/lib/erlang/tests/send_after.sx @@ -118,3 +118,46 @@ erlang:cancel_timer(Ref)") :name) "false") + +;; ── T5 — send_after to a registered atom name ────────────────────── +;; A second process registers itself as `srv`; the timer addresses it +;; by name, and the delayed message lands in that process's mailbox. +;; The server forwards what it got back to the parent for inspection. +(er-sa-test + "T5 timer delivers to registered name" + (get + (sa-ev + "Me = self(), + Pid = spawn(fun () -> receive M -> Me ! {got, M} end end), + register(srv, Pid), + erlang:send_after(20, srv, ping), + receive {got, X} -> X end") + :name) + "ping") + +;; ── T6 — gen_server {noreply, State, Timeout} hookup ─────────────── +;; A gen_server that, on the `arm` cast, returns {noreply, S, 100}. +;; The library schedules {timeout} to itself via send_after; when no +;; other message arrives first, handle_info({timeout}, S) fires. The +;; handler signals the parent so we can confirm the timeout landed. +(do + (er-load-gen-server!) + (erlang-load-module + "-module(sa_tmo). + init(Me) -> {ok, Me}. + handle_call(_R, _F, S) -> {reply, ok, S}. + handle_cast(arm, Me) -> {noreply, Me, 100}. + handle_info({timeout}, Me) -> Me ! fired, {noreply, Me}; + handle_info(_M, S) -> {noreply, S}.") + nil) + +(er-sa-test + "T6 gen_server timeout fires handle_info" + (get + (sa-ev + "Me = self(), + P = gen_server:start_link(sa_tmo, Me), + gen_server:cast(P, arm), + receive fired -> ok after 5000 -> timeout end") + :name) + "ok")