blackbull.protocol.rsock¶
blackbull.protocol.rsock
¶
adopt_inherited_sockets()
¶
Build :class:socket.socket objects from fds inherited across exec.
Returns None when no inherited fds are advertised (the normal
cold-start path). Returns a list of bound, listening sockets when
the master process has re-exec'd itself for an auto-reload — the
fds were marked inheritable, the env var BB_INHERIT_FDS was set
to a comma-separated fd list, and they survived the exec.
Callers MUST NOT bind/listen on the returned sockets — they are already in the listening state from before exec.
The env var is cleared after adoption so child workers forked from this process do not also try to adopt the same fds.
adopt_listening_fd(fd)
¶
Build a :class:socket.socket from an already-bound listening fd.
Used for systemd socket-activation (--bind fd://N) and any other
out-of-process socket hand-off where the supervisor binds and listens
on the user's behalf.
Validates the sd_listen_fds(3) contract when $LISTEN_PID and
$LISTEN_FDS are present:
LISTEN_PIDMUST equal our pid — if it points at someone else we raise rather than steal another process's fds.LISTEN_FDSdefines the inclusive window[SD_LISTEN_FDS_START, SD_LISTEN_FDS_START + LISTEN_FDS)— we reject fds outside that window.
When neither env var is set we trust the caller and build the socket object anyway (useful for non-systemd handoff and for tests).
The fd is not unset CLOEXEC — workers inherit it via fork, the multiworker path already handles set_inheritable as needed.
create_dual_stack_sockets(port, backlog=_DEFAULT_BACKLOG, reuseport=False, sndbuf=0, rcvbuf=0, keepalive=True, user_timeout_ms=0)
¶
Create one IPv4 socket (0.0.0.0) and one IPv6 socket (::),
both listening on port.
Using two explicit sockets — each with IPV6_V6ONLY set on the IPv6
one — is the most portable way to accept both IPv4 and IPv6 connections
on all major platforms (Linux, macOS, Windows).
When port is 0 (let the OS pick a free port), the IPv4 socket is bound first to obtain the assigned port number, then the IPv6 socket is bound to that same port. This guarantees both sockets share a single port, which is what callers expect.
Returns a list that contains whichever sockets were successfully bound (typically two, but may be one if the platform lacks IPv6 support).
create_socket(address, backlog=_DEFAULT_BACKLOG)
¶
Create a single socket (legacy helper).
The host in address determines the address family:
an IPv6 literal (e.g. '::') opens an AF_INET6 socket;
anything else opens an AF_INET socket.
Prefer :func:create_dual_stack_sockets for new code.
create_unix_socket(path, backlog=_DEFAULT_BACKLOG, sndbuf=0, rcvbuf=0, mode=432)
¶
Bind and listen on an AF_UNIX socket at path.
Returns the bound socket on success, None on any bind/listen failure.
path is taken verbatim — no expanduser, no normalisation; the caller is responsible for placing the socket where their reverse proxy expects it.
mode sets the socket-file permissions after bind (chmod on the
inode). Defaults to 0o660 so a reverse proxy (nginx) running in
the same group can connect; pass None to skip the chmod and
inherit umask behaviour.
A stale leftover socket file at path is unlinked before bind (matching the systemd / hypercorn / nginx behaviour). We refuse to unlink if path is a regular file or directory — the user almost certainly didn't mean to point us at one.
TCP-only socket options (SO_REUSEPORT, TCP_USER_TIMEOUT,
IPV6_V6ONLY) are deliberately skipped — AF_UNIX doesn't carry
them. SO_SNDBUF / SO_RCVBUF are honoured when supplied.