SagaHttp.Tcp
Thin TCP wrappers around Erlang's gen_tcp, used by the HTTP server.
Sockets are opaque handles whose owning process is tracked by the
runtime — a socket closes automatically when its owner dies unless
ownership is transferred via controlling_process. Socket is an
accepted connection; ListenSocket is the listening endpoint that
produces them.
Types
Socket
opaque type SocketListenSocket
opaque type ListenSocketFunctions
listen
fun listen : (port: Int) -> (backlog: Int) -> Result ListenSocket StringOpen a listening socket on port. backlog is the OS-level pending-connection
queue size (passed through to gen_tcp:listen). Returns the listener or a
gen_tcp error reason atom (e.g. "eaddrinuse").
accept
fun accept : (socket: ListenSocket) -> Result Socket StringBlock until a client connects, then return the accepted connection socket.
The caller becomes the socket's controlling process — transfer ownership
with controlling_process if a different process should outlive the caller.
Returns Err "closed" when the listener is closed underneath the call (this
is how graceful shutdown unblocks an accept loop).
connect
fun connect : (host: String) -> (port: Int) -> (timeout: Int) -> Result Socket StringOpen a client connection to host:port, giving up after timeout ms.
Useful for tests and any client work; the server side does not use it.
local_port
fun local_port : (socket: ListenSocket) -> Result Int StringReturns the actual OS-assigned port for a listener. Useful when listen
was called with port 0 to let the kernel pick a free port (common in tests).
recv
fun recv : (socket: Socket) -> (length: Int) -> (timeout: Int) -> Result BitString StringRead bytes from a connected socket. length == 0 means "give me whatever
is available" (one TCP chunk); a positive length means "read exactly
this many bytes, blocking until they arrive or timeout ms elapses".
On timeout, returns Err "timeout"; on peer close, Err "closed".
send
fun send : (socket: Socket) -> (data: BitString) -> Result Unit StringWrite data to the socket. gen_tcp:send is synchronous from the BEAM's
point of view — it returns after the data has been handed to the kernel
socket buffer, not after the peer has acked.
close
fun close : (socket: Socket) -> UnitClose an accepted connection socket. Idempotent and infallible from our perspective; further sends/recvs on the socket will fail.
close_listener
fun close_listener : (socket: ListenSocket) -> UnitSame backend as close. Exposed separately so the type system distinguishes
closing an accepted connection from closing the listening socket itself
(the latter unblocks accept callers with an error, which is how graceful
shutdown stops accepting new connections).
peername
fun peername : (socket: Socket) -> Result (String, Int) StringReturns (ip, port) of the connected peer. The IP is a dotted-quad / colon
notation string. Fails if the socket has already been closed or never
completed a connection.
listener_controlling_process
fun listener_controlling_process : (socket: ListenSocket) -> (pid: Pid msg) -> Result Unit StringTransfer ownership of a listening socket to another process. Required when the listener was created by a short-lived process and we want it to keep accepting after that process exits — without transfer, the listener is closed automatically when its creator dies.
controlling_process
fun controlling_process : (socket: Socket) -> (pid: Pid msg) -> Result Unit StringTransfer ownership of an accepted connection socket. Same reason as the
listener variant: an accepted socket's controlling process is whoever
called accept, and the kernel closes the socket when that process dies.
To let connections outlive the acceptor (so a graceful shutdown can close
the listener without dropping in-flight requests), transfer to the
connection-handler process before the acceptor exits.