summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/connections.c58
-rw-r--r--src/fdevent.c32
-rw-r--r--src/fdevent.h3
3 files changed, 56 insertions, 37 deletions
diff --git a/src/connections.c b/src/connections.c
index 4cd21397..d933551f 100644
--- a/src/connections.c
+++ b/src/connections.c
@@ -943,43 +943,6 @@ static handler_t connection_handle_fdevent(server *srv, void *context, int reven
}
- if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
- /* looks like an error */
-
- /* FIXME: revents = 0x19 still means that we should read from the queue */
- if (revents & FDEVENT_HUP) {
- if (con->state == CON_STATE_CLOSE) {
- con->close_timeout_ts = srv->cur_ts - (HTTP_LINGER_TIMEOUT+1);
- } else {
- /* sigio reports the wrong event here
- *
- * there was no HUP at all
- */
-#ifdef USE_LINUX_SIGIO
- if (srv->ev->in_sigio == 1) {
- log_error_write(srv, __FILE__, __LINE__, "sd",
- "connection closed: poll() -> HUP", con->fd);
- } else {
- connection_set_state(srv, con, CON_STATE_ERROR);
- }
-#else
- connection_set_state(srv, con, CON_STATE_ERROR);
-#endif
-
- }
- } else if (revents & FDEVENT_ERR) {
- /* error, connection reset, whatever... we don't want to spam the logfile */
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sd",
- "connection closed: poll() -> ERR", con->fd);
-#endif
- connection_set_state(srv, con, CON_STATE_ERROR);
- } else {
- log_error_write(srv, __FILE__, __LINE__, "sd",
- "connection closed: poll() -> ???", revents);
- }
- }
-
if (con->state == CON_STATE_READ) {
connection_handle_read_state(srv, con);
}
@@ -1008,6 +971,27 @@ static handler_t connection_handle_fdevent(server *srv, void *context, int reven
}
}
+
+ /* attempt (above) to read data in kernel socket buffers
+ * prior to handling FDEVENT_HUP and FDEVENT_ERR */
+
+ if ((revents & ~(FDEVENT_IN | FDEVENT_OUT)) && con->state != CON_STATE_ERROR) {
+ if (con->state == CON_STATE_CLOSE) {
+ con->close_timeout_ts = srv->cur_ts - (HTTP_LINGER_TIMEOUT+1);
+ } else if (revents & FDEVENT_HUP) {
+ if (fdevent_is_tcp_half_closed(con->fd)) {
+ con->keep_alive = 0;
+ } else {
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ }
+ } else if (revents & FDEVENT_ERR) { /* error, connection reset */
+ connection_set_state(srv, con, CON_STATE_ERROR);
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connection closed: poll() -> ???", revents);
+ }
+ }
+
return HANDLER_FINISHED;
}
diff --git a/src/fdevent.c b/src/fdevent.c
index 6d5096c8..756c8e6a 100644
--- a/src/fdevent.c
+++ b/src/fdevent.c
@@ -250,3 +250,35 @@ int fdevent_event_next_fdndx(fdevents *ev, int ndx) {
return -1;
}
+
+#include <netinet/tcp.h>
+#if (defined(__APPLE__) && defined(__MACH__)) \
+ || defined(__FreeBSD__) || defined(__NetBSD__) \
+ || defined(__OpenBSD__) || defined(__DragonflyBSD__)
+#include <netinet/tcp_fsm.h>
+#endif
+
+/* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */
+int fdevent_is_tcp_half_closed(int fd) {
+ #ifdef TCP_CONNECTION_INFO /* Darwin */
+ struct tcp_connection_info tcpi;
+ socklen_t tlen = sizeof(tcpi);
+ return (0 == getsockopt(fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &tcpi, &tlen)
+ && tcpi.tcpi_state == TCPS_CLOSE_WAIT);
+ #elif defined(TCPS_CLOSE_WAIT) /* FreeBSD, NetBSD (not present in OpenBSD) */
+ struct tcp_info tcpi;
+ socklen_t tlen = sizeof(tcpi);
+ return (0 == getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpi, &tlen)
+ && tcpi.tcpi_state == TCPS_CLOSE_WAIT);
+ #elif defined(TCP_INFO) /* Linux */
+ struct tcp_info tcpi;
+ socklen_t tlen = sizeof(tcpi);/*SOL_TCP == IPPROTO_TCP*/
+ return (0 == getsockopt(fd, SOL_TCP, TCP_INFO, &tcpi, &tlen)
+ && tcpi.tcpi_state == TCP_CLOSE_WAIT);
+ #else
+ UNUSED(fd);
+ /*(0 != getpeername() error might indicate TCP RST, but success
+ * would not differentiate between half-close and full-close)*/
+ return 0; /* false (not half-closed) or TCP state unknown */
+ #endif
+}
diff --git a/src/fdevent.h b/src/fdevent.h
index cab04291..4dc1d3cc 100644
--- a/src/fdevent.h
+++ b/src/fdevent.h
@@ -214,4 +214,7 @@ int fdevent_solaris_port_init(fdevents *ev);
int fdevent_freebsd_kqueue_init(fdevents *ev);
int fdevent_libev_init(fdevents *ev);
+/* fd must be TCP socket (AF_INET, AF_INET6), end-of-stream recv() 0 bytes */
+int fdevent_is_tcp_half_closed(int fd);
+
#endif