Commit Graph
12 Commits
Author SHA1 Message Date
smarm 07867b91f6 doc: links and tweaks 2026-06-20 12:11:16 +02:00
smarm-agent f454a9195a gen_server: docs — disambiguate client call deadline from server idle timeout (RFC 015 §7) 2026-06-18 19:20:12 +00:00
smarm-agent 1df85e2384 gen_server: drop-guard drains live timers + debug_assert no leak (RFC 015 §4.7) 2026-06-18 19:19:15 +00:00
smarm-agent 8c8af55928 gen_server: idle/receive timeout via select_timeout/recv_timeout + handle_idle (RFC 015 §4.4) 2026-06-18 19:17:20 +00:00
smarm-agent c0cfa01f37 gen_server: tick_every periodic sugar — loop-driven re-arm via factory + Sys::Tick (RFC 015 §4.3) 2026-06-18 19:13:52 +00:00
smarm-agent 401a1465d5 gen_server: TimerHandle arm_after/cancel + Sys::Timer dispatch (RFC 015 §4.3, §5) 2026-06-18 18:58:00 +00:00
smarm-agent 57eadb5c6c gen_server: type Timer + handle_timer/handle_idle; fold control into Sys channel (RFC 015 §4.5, §6) 2026-06-18 18:54:24 +00:00
smarm-agent a866e34b52 mailbox: typed-path producers, by-name gen_servers, root-exit teardown (RFC 014)
Implements the new addressing surface the examples in examples/ specify:

- spawn_addr::<A>(FnOnce(Receiver<A::Msg>)) -> Pid<A>: the typed-path
  producer. Makes the inbox, spawns the body with its receiver, and publishes
  the sender from the PARENT side (registry::install_for) before returning the
  pid, so an immediate send_to always resolves (no race on the body installing
  itself). Detached, like ServerBuilder::start.

- lookup_as / pick_as / members_as: re-type an erased pid as Pid<A> over the
  heterogeneous registry/pg stores, sharing one helper (pid::assert_type).
  Unchecked but sound: routing is by message TypeId, so a wrong A degrades to
  SendError::NoChannel on the next send, never a misdelivery.

- dispatch::<A>(group, msg) -> Result<Pid<A>, SendError<A::Msg>>: pick_as +
  send_to, with the message handed back on failure. New SendError::NoMember
  variant for the empty/all-dead group case.

- gen_server naming: ServerName<G> backed internally by the registry's existing
  typed-channel store keyed by TypeId::of::<Envelope<G>>() — Envelope stays
  private, no separate directory. Type-state NamedServerBuilder<G> keeps the
  current infallible ServerBuilder::start() untouched; its start() is fallible
  (parent-side register, NameTaken). Free call/cast/whereis_server resolve per
  use. ServerRef::shutdown (+ free shutdown) is the sys-style synchronous stop.

- root-exit teardown: the run's initial actor is recorded as root; when it
  finalizes it flags root_exited, and the scheduler's idle verdict then stops
  the parked-forever remainder (a one-shot RootDrain sweep). Deferred to the
  idle point on purpose: the run queue drains first, so actors with queued work
  finish rather than unwinding on the stop. This lets a named-server daemon (or
  any pinned actor) wind the run down instead of hanging on live_actors > 0.

request_stop is refactored to a request_stop_inner core so the sweep can drive
it from inside the runtime without re-borrowing the thread-local.

Lib + tests + examples build warning-free; full suite green.
2026-06-18 11:02:27 +00:00
Claude 24b95c99ae feat(gen_server): handle_down — runtime monitor forwarding via ServerCtx/Watcher
Monitors are created at runtime (you watch a worker you just spawned in a
handler), so down arms can't ride the static info list. init grows a
&ServerCtx parameter (breaking; no-op default) whose clonable Watcher hands
Monitors to the loop over a control arm; the loop selects the live down
arms ahead of everything else. Arm priority: downs → control → infos →
inbox. A delivered Down retires its arm (monitors are one-shot); a state
that never clones the Watcher closes the control arm after init and the
loop falls back to the plain-inbox park.
2026-06-10 14:59:44 +00:00
Claude e5d1b3b54b feat(gen_server): handle_info — static info arms selected ahead of the inbox
type Info + handle_info (no-op default) on GenServer; ServerBuilder
(with_info/under/start) so start variants don't multiply — start/start_under
stay as wrappers. The loop selects [infos.., inbox] in priority order when
info arms exist, and keeps the plain recv() park when none do. Closed info
arms are silently removed (closed-arm-is-ready-forever); a closed inbox
still means graceful shutdown.
2026-06-10 14:56:51 +00:00
smarm 90b7040504 feat(gen_server): call_timeout via recv_timeout on the reply channel
Deviates deliberately from the roadmap's monitor-based sketch
(monitor + wait reply-or-Down-or-deadline + demonitor): server death is
already observable on the reply channel itself - the reply sender drops
as the server unwinds, closing the channel and waking the parked caller.
So a bounded call is exactly recv_timeout on the reply channel, mapping
Disconnected -> ServerDown and Timeout -> Timeout. No registration
exists, so a timed-out call leaks nothing by construction - the
guarantee the monitor design had to engineer.

Semantics on timeout match Erlang: the request stays in the inbox and
is still handled; the late reply's send fails harmlessly against the
dropped reply receiver. CallTimeoutError keeps ServerDown and Timeout
distinguishable; the infallible call() is unchanged.

Tests: reply within deadline, timeout against a slow (parking) handler
with elapsed bounds, server survival across an abandoned call (late
reply discarded, bounded and unbounded calls keep working), and
ServerDown-not-Timeout for both death paths (mid-call panic and
already-gone inbox).
2026-06-09 22:57:31 +00:00
smarm-dev 55221e9e98 feat(gen_server): synchronous call / async cast over a single actor (roadmap #5)
Thin request-reply layer on channels, no runtime change. A server is an
actor owning a GenServer state; clients hold a clonable ServerRef and issue
call (sync, parks for the reply) or cast (fire-and-forget).

- Single inbox carrying an internal Envelope { Call(req, reply_tx) | Cast },
  forced by the no-select / no-unified-mailbox invariant; call makes a
  one-shot reply channel and parks on it.
- Server-down detection is pure channel closure (no monitor): send fails if
  the inbox is gone; the reply sender drops on the server's unwind so a
  parked caller wakes to Err. Both collapse to CallError/CastError::ServerDown.
- init/terminate are optional trait hooks; terminate runs via a drop guard so
  it fires on every exit path (clean close, panic, request_stop). Must stay
  non-blocking — may run mid-unwind.
- ServerRef carries pid() for monitor/request_stop/link; start + start_under.

Tests: cast-then-call roundtrip, init/terminate ordering, both server-down
paths (reply-channel close on handler panic; inbox-send failure when gone).
2026-06-07 21:51:56 +00:00