]> Sergey Matveev's repositories - public-inbox.git/blob - Documentation/technical/ds.txt
5a1655a1450e2c60018ec487a8c036778ed727e6
[public-inbox.git] / Documentation / technical / ds.txt
1 PublicInbox::DS - event loop and async I/O base class
2
3 Our PublicInbox::DS event loop which powers public-inbox-nntpd
4 and public-inbox-httpd diverges significantly from the
5 unmaintained Danga::Socket package we forked from.  In fact,
6 it's probably different from most other event loops out there.
7
8 Most notably:
9
10 * There is one and only one callback: ->event_step.  Unlike other
11   event loops, there are no separate callbacks for read, write,
12   error or hangup events.  In fact, we never care which kevent
13   filter or poll/epoll event flag (e.g. POLLIN/POLLOUT/POLLHUP)
14   triggers a call.
15
16   The lack of read/write callback distinction is driven by the
17   fact TLS libraries (e.g. OpenSSL via IO::Socket::SSL) may
18   declare SSL_WANT_READ on SSL_write(), and SSL_WANT_READ on
19   SSL_read().  So we end up having to let each user object decide
20   whether it wants to make read or write calls depending on its
21   internal state, completely independent of the event loop.
22
23   Error and hangup (POLLERR and POLLHUP) callbacks are redundant and
24   only triggered in rare cases.  They're redundant because the
25   result of every read and write call in ->event_step must be
26   checked, anyways.  At best, callbacks for POLLHUP and POLLERR can
27   save one syscall per socket lifetime and not worth the extra code
28   it imposes.
29
30   Reducing the user-supplied code down to a single callback allows
31   subclasses to keep their logic self-contained.  The combination
32   of this change and one-shot wakeups (see below) for bidirectional
33   data flows make asynchronous code easier to reason about.
34
35 Other divergences:
36
37 * ->write buffering uses temporary files whereas Danga::Socket used
38   the heap.  The rationale for this is the kernel already provides
39   ample (and configurable) space for socket buffers.  Modern kernels
40   also cache FS operations aggressively, so systems with ample RAM
41   are unlikely to notice degradation, while small systems are less
42   likely to suffer unpredictable heap fragmentation, swap and OOM
43   penalties.
44
45   In the future, we may introduce sendfile and mmap+SSL_write to
46   reduce data copies, and use FALLOC_FL_PUNCH_HOLE on Linux to
47   release space after the buffer is partially cleared.
48
49 Augmented features:
50
51 * obj->write(CODEREF) passes the object itself to the CODEREF
52   Being able to enqueue subroutine calls is a powerful feature in
53   Danga::Socket for keeping linear logic in an asynchronous environment.
54   Unfortunately, each subroutine takes several kilobytes of memory.
55   One small change to Danga::Socket is to pass the receiver object
56   (aka "$self") to the CODEREF.  $self can store any necessary
57   state it needs for a normal (named) subroutine.  This allows us to
58   put the same sub into multiple queues without paying a large
59   memory penalty for each one.
60
61   This idea is also more easily ported to C or other languages which
62   lack anonymous subroutines (aka "closures").
63
64 * ->requeue support.  An optimization of the AddTimer(0, ...) idiom
65   for immediately dispatching code at the next event loop iteration.
66   public-inbox uses this for fairly generating large responses
67   iteratively (see PublicInbox::NNTP::long_response or ibx_async_cat
68   for blob retrievals).
69
70 New features
71
72 * One-shot wakeups allowed via EPOLLONESHOT or EV_DISPATCH.  These
73   flags allow us to simplify code in ->event_step callbacks for
74   bidirectional sockets (NNTP and HTTP).  Instead of merely reacting
75   to events, control is handed over at ->event_step in one-shot scenarios.
76   The event_step caller (NNTP || HTTP) then becomes proactive in declaring
77   which (if any) events it's interested in for the next loop iteration.
78
79 * Edge-triggering available via EPOLLET or EV_CLEAR.  These reduce wakeups
80   for unidirectional classes when throughput is more important than fairness.
81
82 * IO::Socket::SSL support (for NNTPS, STARTTLS+NNTP, HTTPS)
83
84 * dwaitpid (waitpid wrapper) support for reaping dead children
85
86 * reliable signal wakeups are supported via signalfd on Linux,
87   EVFILT_SIGNAL on *BSDs via IO::KQueue.
88
89 Removed features
90
91 * Many fields removed or moved to subclasses, so the underlying
92   hash is smaller and suitable for FDs other than stream sockets.
93   Some fields we enforce (e.g. wbuf, wbuf_off) are autovivified
94   on an as-needed basis to save memory when they're not needed.
95
96 * TCP_CORK support removed, instead we use MSG_MORE on non-TLS sockets
97   and we may use vectored I/O support via GnuTLS in the future
98   for TLS sockets.
99
100 * per-FD PLCMap (post-loop callback) removed, we got ->requeue
101   support where no extra hash lookups or assignments are necessary.
102
103 * read push backs removed.  Some subclasses use a read buffer ({rbuf})
104   but they control it, not this event loop.
105
106 * Profiling and debug logging removed.  Perl and OS-specific tracers
107   and profilers are sufficient.
108
109 * ->AddOtherFds support removed, everything watched is a subclass of
110   PublicInbox::DS, but we've slimmed down the fields to eliminate
111   the memory penalty for objects.