src/fiber.c, not used yet: a fiber runs a function on a stack of its
own, yielding back to the thread which resumed it, for a thread to
serve many clients with code written as if each had a thread.
The switch is our own on x86-64 (System V), aarch64, i386, arm (ARM or
Thumb code, with or without a floating point unit) and mips o32 (with
soft, 32 or 64 bit floating point registers, position independent or
not), Windows' fibers on Windows. It saves the registers a function
has to keep and swaps the stack. Elsewhere there are no fibers
(WITH_FIBERS is not defined), nor where x86-64 Linux checks returns
against a shadow stack, which fiberusable() tells: each client has a
thread there.
Stacks are mapped with a guard page below them. A fiber whose function
returned is started again with another, its stack kept. Switches are
annotated for AddressSanitizer.
tests/fibertest.c tests them: yielding and resuming, the integer and
floating point registers a function keeps across switches both ways, a
thousand fibers resumed at random each keeping its stack, recursion
deep in a stack, fibers sleeping on the event loop's timers. The units
case, which was the loop case, runs it with looptest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/loop.c, not used yet: one thread waiting for many sockets and
timers, calling back what waits for them, for clients to be served by
a thread each serves many of.
Sockets are waited for with epoll on Linux, kqueue on the BSDs and
macOS, poll() (WSAPoll() on Windows) elsewhere or with LOOP_POLL,
level-triggered; one with nothing to wait for is taken from the system,
or an error or a hang up would be reported all the same. One deleted
while the sockets ready are being called is not called.
Timers wait in a wheel of 1024 one-second slots. One due within a turn
waits at the head of the slot of its second, all there due in the same
second; one due later behind them, in the order of their seconds, or,
if due before the last there, at the head of the last slot of the
turn, looked at again then. Moving a timer later costs nothing while
it waits in the wheel: it is looked at when its old deadline comes and
moved then. When its slot comes a timer due within the second waits in
a heap, so it fires to the millisecond, as does one set within the
second. The clock is monotonic.
Another thread posts to the loop, waking it with an eventfd, a pipe, or
on Windows a UDP socket sending to itself.
tests/looptest.c tests it on its own, timers on a clock of its own
through several turns of the wheel: built by CMake, run by the loop
case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A UDP service now runs in its own thread alone (udploop()): it reads the
service's socket and the sockets of its sessions towards their
destinations, keeps their deadlines, and answers every client from the
service's socket. A session is a struct clientparam as a client thread's
would be, with a slot of maxchild, among the service's children and
counted on its listener, but without a thread. The services supply
handlers (struct udpservice, childdef.udp): udppm keeps a session per
client address, forwarding its datagrams, behind the headers of a
parent chain, from a socket of its own and answering what comes back,
until STRING_L of silence or, with -s, the first answer; dnspr makes a
session of each query, answered at once from the fake address, the
name cache or the resolver, or sent on to the first name server over
UDP, whose answer is waited for in the loop up to DNS_TO (a name server
over TCP is still waited for in place). Authentication stays in the
loop for now; datagrams over a bandwidth limit, or of a client without
a free slot, are dropped.
On a reload the old service goes on serving the socket until it sees
its configuration replaced, then hands its sessions to the listener;
the newest service of its kind there, which until then does not read
the socket so that no client gets two sessions, adopts them: a slot of
its own, the old one given back, its filters and limits, and
re-authentication. A session is ended with 26 if the newest service is
of another kind, 27 without a slot, or with what the authentication
says; sessions nobody is left to adopt are ended by the last service to
stop on the listener.
On Windows a send on a socket waited while another thread polled it,
which delayed udppm's answers by up to a second, and old UDP services
did not exit after a reload; with one thread per socket neither
happens, and the second socket bound to the service's address
(NO_SHARE_UDP_SOCKET) is gone from the code and the build files.
udpinit, the global udp_table, udpnext(), udppmchild() and
dnsprchild() are gone; udpsockmap() serves SOCKS5 UDP ASSOCIATE only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pop3p, imapp and smtpp are built with MAILPROXY=true, and ftppr and the
ftp:// scheme of the HTTP proxy with FTP=true; with CMake the switches are
3PROXY_USE_MAILPROXY and 3PROXY_USE_FTP. Neither is in a default build, and
the standalone binaries follow what was built.
A configuration naming one of them is still read either way. The three mail
protocols amount to a STARTTLS negotiation now that mail is carried over
TLS, so without a proxy of their own those names are tlspr speaking the
protocol: the file holds a stand-in which sets the protocol for the
connection and returns tlspr for the caller to run, which serves the
service name and a parent chain alike and leaves conf.c and the redirect
table untouched. FTP has no such fallback, so the service is known, answers
nothing and logs the refusal.
The Linux workflow builds both, so all of it is still compiled and run.