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. Theoretically 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.
