193 lines
6.5 KiB
Plaintext
193 lines
6.5 KiB
Plaintext
|
2019-09-03
|
||
|
|
||
|
u8 fd.state;
|
||
|
u8 fd.ev;
|
||
|
|
||
|
|
||
|
ev = one of :
|
||
|
#define FD_POLL_IN 0x01
|
||
|
#define FD_POLL_PRI 0x02
|
||
|
#define FD_POLL_OUT 0x04
|
||
|
#define FD_POLL_ERR 0x08
|
||
|
#define FD_POLL_HUP 0x10
|
||
|
|
||
|
Could we instead have :
|
||
|
|
||
|
FD_WAIT_IN 0x01
|
||
|
FD_WAIT_OUT 0x02
|
||
|
FD_WAIT_PRI 0x04
|
||
|
FD_SEEN_HUP 0x08
|
||
|
FD_SEEN_HUP 0x10
|
||
|
FD_WAIT_CON 0x20 <<= shouldn't this be in the connection itself in fact ?
|
||
|
|
||
|
=> not needed, covered by the state instead.
|
||
|
|
||
|
What is missing though is :
|
||
|
- FD_DATA_PENDING -- overlaps with READY_R, OK if passed by pollers only
|
||
|
- FD_EOI_PENDING
|
||
|
- FD_ERR_PENDING
|
||
|
- FD_EOI
|
||
|
- FD_SHW
|
||
|
- FD_ERR
|
||
|
|
||
|
fd_update_events() could do that :
|
||
|
|
||
|
if ((fd_data_pending|fd_eoi_pending|fd_err_pending) && !(fd_err|fd_eoi))
|
||
|
may_recv()
|
||
|
|
||
|
if (fd_send_ok && !(fd_err|fd_shw))
|
||
|
may_send()
|
||
|
|
||
|
if (fd_err)
|
||
|
wake()
|
||
|
|
||
|
the poller could do that :
|
||
|
HUP+OUT => always indicates a failed connect(), it should not lack ERR. Is this err_pending ?
|
||
|
|
||
|
ERR HUP OUT IN
|
||
|
0 0 0 0 => nothing
|
||
|
0 0 0 1 => FD_DATA_PENDING
|
||
|
0 0 1 0 => FD_SEND_OK
|
||
|
0 0 1 1 => FD_DATA_PENDING|FD_SEND_OK
|
||
|
0 1 0 0 => FD_EOI (|FD_SHW)
|
||
|
0 1 0 1 => FD_DATA_PENDING|FD_EOI_PENDING (|FD_SHW)
|
||
|
0 1 1 0 => FD_EOI |FD_ERR (|FD_SHW)
|
||
|
0 1 1 1 => FD_EOI_PENDING (|FD_ERR_PENDING) |FD_DATA_PENDING (|FD_SHW)
|
||
|
1 X 0 0 => FD_ERR | FD_EOI (|FD_SHW)
|
||
|
1 X X 1 => FD_ERR_PENDING | FD_EOI_PENDING | FD_DATA_PENDING (|FD_SHW)
|
||
|
1 X 1 0 => FD_ERR | FD_EOI (|FD_SHW)
|
||
|
|
||
|
OUT+HUP,OUT+HUP+ERR => FD_ERR
|
||
|
|
||
|
This reorders to:
|
||
|
|
||
|
IN ERR HUP OUT
|
||
|
0 0 0 0 => nothing
|
||
|
0 0 0 1 => FD_SEND_OK
|
||
|
0 0 1 0 => FD_EOI (|FD_SHW)
|
||
|
|
||
|
0 X 1 1 => FD_ERR | FD_EOI (|FD_SHW)
|
||
|
0 1 X 0 => FD_ERR | FD_EOI (|FD_SHW)
|
||
|
0 1 X 1 => FD_ERR | FD_EOI (|FD_SHW)
|
||
|
|
||
|
1 0 0 0 => FD_DATA_PENDING
|
||
|
1 0 0 1 => FD_DATA_PENDING|FD_SEND_OK
|
||
|
1 0 1 0 => FD_DATA_PENDING|FD_EOI_PENDING (|FD_SHW)
|
||
|
1 0 1 1 => FD_EOI_PENDING (|FD_ERR_PENDING) |FD_DATA_PENDING (|FD_SHW)
|
||
|
1 1 X X => FD_ERR_PENDING | FD_EOI_PENDING | FD_DATA_PENDING (|FD_SHW)
|
||
|
|
||
|
Regarding "|SHW", it's normally useless since it will already have been done,
|
||
|
except on connect() error where this indicates there's no need for SHW.
|
||
|
|
||
|
FD_EOI and FD_SHW could be part of the state (FD_EV_SHUT_R, FD_EV_SHUT_W).
|
||
|
Then all states having these bit and another one would be transient and need
|
||
|
to resync. We could then have "fd_shut_recv" and "fd_shut_send" to turn these
|
||
|
states.
|
||
|
|
||
|
The FD's ev then only needs to update EOI_PENDING, ERR_PENDING, ERR, DATA_PENDING.
|
||
|
With this said, these are not exactly polling states either, as err/eoi/shw are
|
||
|
orthogonal to the other states and are required to update them so that the polling
|
||
|
state really is DISABLED in the end. So we need more of an operational status for
|
||
|
the FD containing EOI_PENDING, EOI, ERR_PENDING, ERR, SHW, CLO?. These could be
|
||
|
classified in 3 categories: read:(OPEN, EOI_PENDING, EOI); write:(OPEN,SHW),
|
||
|
ctrl:(OPEN,ERR_PENDING,ERR,CLO). That would be 2 bits for R, 1 for W, 2 for ctrl
|
||
|
or total 5 vs 6 for individual ones, but would be harder to manipulate.
|
||
|
|
||
|
Proposal:
|
||
|
- rename fdtab[].state to "polling_state"
|
||
|
- rename fdtab[].ev to "status"
|
||
|
|
||
|
Note: POLLHUP is also reported is a listen() socket has gone in shutdown()
|
||
|
TEMPORARILY! Thus we may not always consider this as a final error.
|
||
|
|
||
|
|
||
|
Work hypothesis:
|
||
|
|
||
|
SHUT RDY ACT
|
||
|
0 0 0 => disabled
|
||
|
0 0 1 => active
|
||
|
0 1 0 => stopped
|
||
|
0 1 1 => ready
|
||
|
1 0 0 => final shut
|
||
|
1 0 1 => shut pending without data
|
||
|
1 1 0 => shut pending, stopped
|
||
|
1 1 1 => shut pending
|
||
|
|
||
|
PB: we can land into final shut if one thread disables the FD while another
|
||
|
one that was waiting on it reports it as shut. Theorically it should be
|
||
|
implicitly ready though, since reported. But if no data is reported, it
|
||
|
will be reportedly shut only. And no event will be reported then. This
|
||
|
might still make sense since it's not active, thus we don't want events.
|
||
|
But it will not be enabled later either in this case so the shut really
|
||
|
risks not to be properly reported. The issue is that there's no difference
|
||
|
between a shut coming from the bottom and a shut coming from the top, and
|
||
|
we need an event to report activity here. Or we may consider that a poller
|
||
|
never leaves a final shut by itself (100) and always reports it as
|
||
|
shut+stop (thus ready) if it was not active. Alternately, if active is
|
||
|
disabled, shut should possibly be ignored, then a poller cannot report
|
||
|
shut. But shut+stopped seems the most suitable as it corresponds to
|
||
|
disabled->stopped transition.
|
||
|
|
||
|
Now let's add ERR. ERR necessarily implies SHUT as there doesn't seem to be a
|
||
|
valid case of ERR pending without shut pending.
|
||
|
|
||
|
ERR SHUT RDY ACT
|
||
|
0 0 0 0 => disabled
|
||
|
0 0 0 1 => active
|
||
|
0 0 1 0 => stopped
|
||
|
0 0 1 1 => ready
|
||
|
|
||
|
0 1 0 0 => final shut, no error
|
||
|
0 1 0 1 => shut pending without data
|
||
|
0 1 1 0 => shut pending, stopped
|
||
|
0 1 1 1 => shut pending
|
||
|
|
||
|
1 0 X X => invalid
|
||
|
|
||
|
1 1 0 0 => final shut, error encountered
|
||
|
1 1 0 1 => error pending without data
|
||
|
1 1 1 0 => error pending after data, stopped
|
||
|
1 1 1 1 => error pending
|
||
|
|
||
|
So the algorithm for the poller is:
|
||
|
- if (shutdown_pending or error) reported and ACT==0,
|
||
|
report SHUT|RDY or SHUT|ERR|RDY
|
||
|
|
||
|
For read handlers :
|
||
|
- if (!(flags & (RDY|ACT)))
|
||
|
return
|
||
|
- if (ready)
|
||
|
try_to_read
|
||
|
- if (err)
|
||
|
report error
|
||
|
- if (shut)
|
||
|
read0
|
||
|
|
||
|
For write handlers:
|
||
|
- if (!(flags & (RDY|ACT)))
|
||
|
return
|
||
|
- if (err||shut)
|
||
|
report error
|
||
|
- if (ready)
|
||
|
try_to_write
|
||
|
|
||
|
For listeners:
|
||
|
- if (!(flags & (RDY|ACT)))
|
||
|
return
|
||
|
- if (err||shut)
|
||
|
pause
|
||
|
- if (ready)
|
||
|
try_to_accept
|
||
|
|
||
|
Kqueue reports events differently, it says EV_EOF() on READ or WRITE, that
|
||
|
we currently map to FD_POLL_HUP and FD_POLL_ERR. Thus kqueue reports only
|
||
|
POLLRDHUP and not POLLHUP, so for now a direct mapping of POLLHUP to
|
||
|
FD_POLL_HUP does NOT imply write closed with kqueue while it does for others.
|
||
|
|
||
|
Other approach, use the {RD,WR}_{ERR,SHUT,RDY} flags to build a composite
|
||
|
status in each poller and pass this to fd_update_events(). We normally
|
||
|
have enough to be precise, and this latter will rework the events.
|
||
|
|
||
|
FIXME: Normally on KQUEUE we're supposed to look at kev[].fflags to get the error
|
||
|
on EV_EOF() on read or write.
|