[core] defer handling FDEVENT_HUP and FDEVENT_ERR
defer handling FDEVENT_HUP and FDEVENT_ERR to after processing (connection *) in order to have a chance to read data in kernel socket bufferspersonal/stbuehler/tests-path
parent
52d9b0da88
commit
a8398e4596
|
@ -36,6 +36,8 @@ struct connection {
|
|||
signed char is_writable;
|
||||
char is_ssl_sock;
|
||||
char traffic_limit_reached;
|
||||
uint16_t revents_err;
|
||||
uint16_t proto_default_port;
|
||||
|
||||
chunkqueue *write_queue; /* a large queue for low-level write ( HTTP response ) [ file, mem ] */
|
||||
chunkqueue *read_queue; /* a small queue for low-level read ( HTTP request ) [ mem ] */
|
||||
|
@ -65,8 +67,6 @@ struct connection {
|
|||
time_t connection_start;
|
||||
uint32_t request_count; /* number of requests handled in this connection */
|
||||
int keep_alive_idle; /* remember max_keep_alive_idle from config */
|
||||
|
||||
uint16_t proto_default_port;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -107,6 +107,7 @@ static void connection_close(connection *con) {
|
|||
chunkqueue_reset(con->read_queue);
|
||||
con->request_count = 0;
|
||||
con->is_ssl_sock = 0;
|
||||
con->revents_err = 0;
|
||||
|
||||
fdevent_fdnode_event_del(srv->ev, con->fdn);
|
||||
fdevent_unregister(srv->ev, con->fd);
|
||||
|
@ -802,101 +803,27 @@ static int connection_handle_read_state(connection * const con) {
|
|||
}
|
||||
|
||||
|
||||
static void connection_state_machine_h2 (request_st *r, connection *con);
|
||||
static handler_t connection_handle_fdevent(void * const context, const int revents) {
|
||||
connection * restrict con = context;
|
||||
const int is_ssl_sock = con->is_ssl_sock;
|
||||
|
||||
static handler_t connection_handle_fdevent(void *context, int revents) {
|
||||
connection *con = context;
|
||||
joblist_append(con);
|
||||
|
||||
if (con->is_ssl_sock) {
|
||||
/* ssl may read and write for both reads and writes */
|
||||
if (revents & (FDEVENT_IN | FDEVENT_OUT)) {
|
||||
con->is_readable = 1;
|
||||
con->is_writable = 1;
|
||||
}
|
||||
} else {
|
||||
if (revents & FDEVENT_IN) {
|
||||
con->is_readable = 1;
|
||||
}
|
||||
if (revents & FDEVENT_OUT) {
|
||||
con->is_writable = 1;
|
||||
/* we don't need the event twice */
|
||||
}
|
||||
}
|
||||
if (revents & ~(FDEVENT_IN | FDEVENT_OUT))
|
||||
con->revents_err |= (revents & ~(FDEVENT_IN | FDEVENT_OUT));
|
||||
|
||||
request_st * const r = &con->request;
|
||||
if (revents & (FDEVENT_IN | FDEVENT_OUT)) {
|
||||
if (is_ssl_sock) /*(ssl may read and write for both reads and writes)*/
|
||||
con->is_readable = con->is_writable = 1;
|
||||
else {
|
||||
if (revents & FDEVENT_IN)
|
||||
con->is_readable = 1;
|
||||
if (revents & FDEVENT_OUT)
|
||||
con->is_writable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (r->http_version == HTTP_VERSION_2) {
|
||||
connection_state_machine_h2(r, con);
|
||||
if (-1 == con->fd) /*(con closed; CON_STATE_CONNECT)*/
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
else {
|
||||
joblist_append(con);
|
||||
|
||||
if (r->state == CON_STATE_READ) {
|
||||
if (!connection_handle_read_state(con)
|
||||
&& r->http_version == HTTP_VERSION_2)
|
||||
connection_transition_h2(r, con);
|
||||
}
|
||||
|
||||
if (r->state == CON_STATE_WRITE &&
|
||||
!chunkqueue_is_empty(con->write_queue)) {
|
||||
connection_handle_write(r, con);
|
||||
}
|
||||
}
|
||||
|
||||
if (r->state == CON_STATE_CLOSE) {
|
||||
/* flush the read buffers */
|
||||
connection_read_for_eos(con);
|
||||
}
|
||||
|
||||
|
||||
/* attempt (above) to read data in kernel socket buffers
|
||||
* prior to handling FDEVENT_HUP and FDEVENT_ERR */
|
||||
|
||||
if ((revents & ~(FDEVENT_IN | FDEVENT_OUT)) && r->state != CON_STATE_ERROR) {
|
||||
if (r->state == CON_STATE_CLOSE) {
|
||||
con->close_timeout_ts = log_epoch_secs - (HTTP_LINGER_TIMEOUT+1);
|
||||
} else if (revents & FDEVENT_HUP) {
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
} else if (revents & FDEVENT_RDHUP) {
|
||||
int events = fdevent_fdnode_interest(con->fdn);
|
||||
events &= ~(FDEVENT_IN|FDEVENT_RDHUP);
|
||||
r->conf.stream_request_body &= ~(FDEVENT_STREAM_REQUEST_BUFMIN|FDEVENT_STREAM_REQUEST_POLLIN);
|
||||
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLRDHUP;
|
||||
con->is_readable = 1; /*(can read 0 for end-of-stream)*/
|
||||
if (chunkqueue_is_empty(con->read_queue)) r->keep_alive = 0;
|
||||
if (r->reqbody_length < -1) { /*(transparent proxy mode; no more data to read)*/
|
||||
r->reqbody_length = r->reqbody_queue.bytes_in;
|
||||
}
|
||||
if (sock_addr_get_family(&con->dst_addr) == AF_UNIX) {
|
||||
/* future: will getpeername() on AF_UNIX properly check if still connected? */
|
||||
fdevent_fdnode_event_set(con->srv->ev, con->fdn, events);
|
||||
} else if (fdevent_is_tcp_half_closed(con->fd)) {
|
||||
/* Success of fdevent_is_tcp_half_closed() after FDEVENT_RDHUP indicates TCP FIN received,
|
||||
* but does not distinguish between client shutdown(fd, SHUT_WR) and client close(fd).
|
||||
* Remove FDEVENT_RDHUP so that we do not spin on the ready event.
|
||||
* However, a later TCP RST will not be detected until next write to socket.
|
||||
* future: might getpeername() to check for TCP RST on half-closed sockets
|
||||
* (without FDEVENT_RDHUP interest) when checking for write timeouts
|
||||
* once a second in server.c, though getpeername() on Windows might not indicate this */
|
||||
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_TCP_FIN;
|
||||
fdevent_fdnode_event_set(con->srv->ev, con->fdn, events);
|
||||
} else {
|
||||
/* Failure of fdevent_is_tcp_half_closed() indicates TCP RST
|
||||
* (or unable to tell (unsupported OS), though should not
|
||||
* be setting FDEVENT_RDHUP in that case) */
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
}
|
||||
} else if (revents & FDEVENT_ERR) { /* error, connection reset */
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
} else {
|
||||
log_error(r->conf.errh, __FILE__, __LINE__,
|
||||
"connection closed: poll() -> ??? %d", revents);
|
||||
}
|
||||
}
|
||||
|
||||
return HANDLER_FINISHED;
|
||||
return HANDLER_FINISHED;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1107,6 +1034,9 @@ connection_get_state (request_state_t state)
|
|||
}
|
||||
|
||||
|
||||
static void connection_state_machine_h2 (request_st *h2r, connection *con);
|
||||
|
||||
|
||||
static void
|
||||
connection_state_machine_loop (request_st * const r, connection * const con)
|
||||
{
|
||||
|
@ -1202,11 +1132,75 @@ connection_state_machine_loop (request_st * const r, connection * const con)
|
|||
}
|
||||
|
||||
|
||||
__attribute_cold__
|
||||
static void
|
||||
connection_revents_err (request_st * const r, connection * const con)
|
||||
{
|
||||
/* defer handling FDEVENT_HUP and FDEVENT_ERR to here in order to
|
||||
* first attempt (in callers) to read data in kernel socket buffers */
|
||||
/*assert(con->revents_err & ~(FDEVENT_IN | FDEVENT_OUT));*/
|
||||
const int revents = (int)con->revents_err;
|
||||
con->revents_err = 0;
|
||||
|
||||
if (r->state == CON_STATE_CLOSE)
|
||||
con->close_timeout_ts = log_epoch_secs - (HTTP_LINGER_TIMEOUT+1);
|
||||
else if (revents & FDEVENT_HUP)
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
else if (revents & FDEVENT_RDHUP) {
|
||||
int events = fdevent_fdnode_interest(con->fdn);
|
||||
events &= ~(FDEVENT_IN|FDEVENT_RDHUP);
|
||||
r->conf.stream_request_body &=
|
||||
~(FDEVENT_STREAM_REQUEST_BUFMIN|FDEVENT_STREAM_REQUEST_POLLIN);
|
||||
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLRDHUP;
|
||||
con->is_readable = 1; /*(can read 0 for end-of-stream)*/
|
||||
if (chunkqueue_is_empty(con->read_queue)) r->keep_alive = 0;
|
||||
if (r->reqbody_length < -1)/*(transparent proxy mode; no more rd data)*/
|
||||
r->reqbody_length = r->reqbody_queue.bytes_in;
|
||||
if (sock_addr_get_family(&con->dst_addr) == AF_UNIX) {
|
||||
/* future: will getpeername() on AF_UNIX check if still connected?*/
|
||||
fdevent_fdnode_event_set(con->srv->ev, con->fdn, events);
|
||||
}
|
||||
else if (fdevent_is_tcp_half_closed(con->fd)) {
|
||||
/* Success of fdevent_is_tcp_half_closed() after FDEVENT_RDHUP
|
||||
* indicates TCP FIN received, but does not distinguish between
|
||||
* client shutdown(fd, SHUT_WR) and client close(fd). Remove
|
||||
* FDEVENT_RDHUP so that we do not spin on ready event. However,
|
||||
* a later TCP RST will not be detected until next write to socket.
|
||||
* future: might getpeername() to check for TCP RST on half-closed
|
||||
* sockets (without FDEVENT_RDHUP interest) when checking for write
|
||||
* timeouts once a second in server.c, though getpeername() on
|
||||
* Windows might not indicate this */
|
||||
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_TCP_FIN;
|
||||
fdevent_fdnode_event_set(con->srv->ev, con->fdn, events);
|
||||
}
|
||||
else {
|
||||
/* Failure of fdevent_is_tcp_half_closed() indicates TCP RST
|
||||
* (or unable to tell (unsupported OS), though should not
|
||||
* be setting FDEVENT_RDHUP in that case) */
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
else if (revents & FDEVENT_ERR) /* error, connection reset */
|
||||
connection_set_state_error(r, CON_STATE_ERROR);
|
||||
else
|
||||
log_error(r->conf.errh, __FILE__, __LINE__,
|
||||
"connection closed: poll() -> ??? %d", revents);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
connection_set_fdevent_interest (request_st * const r, connection * const con)
|
||||
{
|
||||
if (con->fd < 0) return;
|
||||
|
||||
if (con->revents_err && r->state != CON_STATE_ERROR) {
|
||||
connection_revents_err(r, con); /* resets con->revents_err = 0 */
|
||||
connection_state_machine(con);
|
||||
return;
|
||||
/* connection_state_machine() will end up calling back into
|
||||
* connection_set_fdevent_interest(), but with 0 == con->revents_err */
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
switch(r->state) {
|
||||
case CON_STATE_READ:
|
||||
|
|
Loading…
Reference in New Issue