1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
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.
|