Commit Graph

4 Commits

Author SHA1 Message Date
Vladimir Dubrovin
fa3807c6f1 Builds: no Open Watcom, no build without IPv6, no select()
The Win32 "lite" build with Open Watcom (Makefile.watcom, its release
workflow, the CMake branch for it) was the only one built without IPv6
(NOIPV6), without fibers and event loops, with a select() of its own
for poll() (mypoll()), and with the calling conventions of Watcom's
pragmas in the plugins. They are gone with it: every build has IPv6,
and polls with poll() on Unix and WSAPoll() on Windows, which were
what every other build asked for (WITH_POLL, WITH_WSAPOLL, the
3PROXY_USE_POLL and 3PROXY_USE_WSAPOLL options of CMake, FD_SETSIZE
for select()). getip(), the IPv4-only resolver of NOIPV6, goes too.

Plugin binary compatibility: plugins built with Open Watcom, and their
calling convention, are no longer supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:54:51 +03:00
Vladimir Dubrovin
e09e20e71c Event loops on Windows: AFD polls completed to a port
A loop waits for sockets with the polls of AFD, the driver under
Windows' sockets, as epoll and kqueue do elsewhere: each socket's poll is
given to \Device\Afd once and completes to the loop's I/O completion
port, which a message from another thread is posted to as well. WSAPoll()
stays the backend with LOOP_POLL and for Watcom and Windows CE.

A poll reports once and has to be made again. Edge-triggered sockets, the
registrations of fibers' sockets, are polled again for what an operation
has found them not to be ready for: loopagain(), called where readiness
known is found gone, a no-op with epoll and kqueue. A poll in flight for
less than wanted is cancelled and made again as it completes; one of a
socket deleted is freed as it completes.

With it the sockets of fibers stay registered on Windows too (SOCKREGS):
the Windows socket functions tell the loop of a socket found not ready
and of one closed, as the Unix ones do, and registrations are kept by the
handle divided by 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:40:42 +03:00
Vladimir Dubrovin
7b873b80b1 Event loops: sockets registered once, readiness kept until not found
A fiber waiting on a socket registered it with its loop and took it
off again on every wait: two epoll_ctl() calls a wait, some 39 for an
exchange of the mix benchmark, about an eighth of the proxy's time. A
socket is now registered once, edge-triggered for reading and writing,
until it is closed, and the loop keeps what it was reported ready for
until an operation finds it not to be: read, write, accept, connect,
splice() from a pipe and TLS tell it so through fiberblockfunc, the
close through fiberclosefunc. A wait for what a socket is known to be
ready for is answered at once, with no system call and no switch; only
with none of them ready does the fiber wait in its loop. Readiness is
trusted once: answered again with nothing reported or found since, what
was there may have been taken without its end being found, and the
system is asked with poll(). epoll and kqueue only; with poll() each
wait still registers its sockets.

What takes readability for an end rather than reading, the control
connection of a UDP association, the client waiting for a SOCKS BIND,
the server between requests of a kept-alive HTTP connection, asks
first with sockreadable(), a peek below any TLS layer.

kqueue: the end of either side, the peer's or our own shutdown, is
readable or writable, not a hang up, which poll() reports only with
both: kept, it made a wait for the answer after our shutdown return
at once, and sockmap give up with 93.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:41:47 +03:00
Vladimir Dubrovin
cc98c498fe An event loop: sockets and timers for one thread
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>
2026-10-24 21:30:13 +03:00