diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b3238ee3e..0a34e6ce2 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -41,6 +41,7 @@ set(SOURCES sd-daemon.c timeout.c sha1.c + lwan-dq.c ) if (HAVE_LUA) diff --git a/src/lib/lwan-dq.c b/src/lib/lwan-dq.c new file mode 100644 index 000000000..8aa810a06 --- /dev/null +++ b/src/lib/lwan-dq.c @@ -0,0 +1,128 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include + +#include "lwan-private.h" +#include "lwan-dq.h" + +static inline int death_queue_node_to_idx(struct death_queue *dq, + struct lwan_connection *conn) +{ + return (conn == &dq->head) ? -1 : (int)(ptrdiff_t)(conn - dq->conns); +} + +static inline struct lwan_connection * +death_queue_idx_to_node(struct death_queue *dq, int idx) +{ + return (idx < 0) ? &dq->head : &dq->conns[idx]; +} + +void death_queue_insert(struct death_queue *dq, + struct lwan_connection *new_node) +{ + new_node->next = -1; + new_node->prev = dq->head.prev; + struct lwan_connection *prev = death_queue_idx_to_node(dq, dq->head.prev); + dq->head.prev = prev->next = death_queue_node_to_idx(dq, new_node); +} + +static void death_queue_remove(struct death_queue *dq, + struct lwan_connection *node) +{ + struct lwan_connection *prev = death_queue_idx_to_node(dq, node->prev); + struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); + + next->prev = node->prev; + prev->next = node->next; + + node->next = node->prev = -1; +} + +bool death_queue_empty(struct death_queue *dq) { return dq->head.next < 0; } + +void death_queue_move_to_last(struct death_queue *dq, + struct lwan_connection *conn) +{ + /* + * If the connection isn't keep alive, it might have a coroutine that + * should be resumed. If that's the case, schedule for this request to + * die according to the keep alive timeout. + * + * If it's not a keep alive connection, or the coroutine shouldn't be + * resumed -- then just mark it to be reaped right away. + */ + conn->time_to_die = dq->time; + if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) + conn->time_to_die += dq->keep_alive_timeout; + + death_queue_remove(dq, conn); + death_queue_insert(dq, conn); +} + +void death_queue_init(struct death_queue *dq, const struct lwan *lwan) +{ + dq->lwan = lwan; + dq->conns = lwan->conns; + dq->time = 0; + dq->keep_alive_timeout = lwan->config.keep_alive_timeout; + dq->head.next = dq->head.prev = -1; + dq->timeout = (struct timeout){}; +} + +void death_queue_kill(struct death_queue *dq, struct lwan_connection *conn) +{ + death_queue_remove(dq, conn); + if (LIKELY(conn->coro)) { + coro_free(conn->coro); + conn->coro = NULL; + } + if (conn->flags & CONN_IS_ALIVE) { + conn->flags &= ~CONN_IS_ALIVE; + close(lwan_connection_get_fd(dq->lwan, conn)); + } +} + +void death_queue_kill_waiting(struct death_queue *dq) +{ + dq->time++; + + while (!death_queue_empty(dq)) { + struct lwan_connection *conn = + death_queue_idx_to_node(dq, dq->head.next); + + if (conn->time_to_die > dq->time) + return; + + death_queue_kill(dq, conn); + } + + /* Death queue exhausted: reset epoch */ + dq->time = 0; +} + +void death_queue_kill_all(struct death_queue *dq) +{ + while (!death_queue_empty(dq)) { + struct lwan_connection *conn = + death_queue_idx_to_node(dq, dq->head.next); + death_queue_kill(dq, conn); + } +} diff --git a/src/lib/lwan-dq.h b/src/lib/lwan-dq.h new file mode 100644 index 000000000..895f97520 --- /dev/null +++ b/src/lib/lwan-dq.h @@ -0,0 +1,44 @@ +/* + * lwan - simple web server + * Copyright (c) 2019 Leandro A. F. Pereira + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#pragma once + +struct death_queue { + const struct lwan *lwan; + struct lwan_connection *conns; + struct lwan_connection head; + struct timeout timeout; + unsigned time; + unsigned short keep_alive_timeout; +}; + +void death_queue_init(struct death_queue *dq, const struct lwan *l); + +void death_queue_insert(struct death_queue *dq, + struct lwan_connection *new_node); +void death_queue_kill(struct death_queue *dq, struct lwan_connection *node); +void death_queue_move_to_last(struct death_queue *dq, + struct lwan_connection *conn); + +void death_queue_kill_waiting(struct death_queue *dq); +void death_queue_kill_all(struct death_queue *dq); + +bool death_queue_empty(struct death_queue *dq); + diff --git a/src/lib/lwan-thread.c b/src/lib/lwan-thread.c index d5cb36fc7..63567fcfc 100644 --- a/src/lib/lwan-thread.c +++ b/src/lib/lwan-thread.c @@ -34,102 +34,14 @@ #endif #include "lwan-private.h" +#include "lwan-dq.h" #include "list.h" -struct death_queue { - const struct lwan *lwan; - struct lwan_connection *conns; - struct lwan_connection head; - struct timeout timeout; - unsigned time; - unsigned short keep_alive_timeout; -}; - static const uint32_t events_by_write_flag[] = { EPOLLOUT | EPOLLRDHUP | EPOLLERR, EPOLLIN | EPOLLRDHUP | EPOLLERR }; -static inline int death_queue_node_to_idx(struct death_queue *dq, - struct lwan_connection *conn) -{ - return (conn == &dq->head) ? -1 : (int)(ptrdiff_t)(conn - dq->conns); -} - -static inline struct lwan_connection * -death_queue_idx_to_node(struct death_queue *dq, int idx) -{ - return (idx < 0) ? &dq->head : &dq->conns[idx]; -} - -static void death_queue_insert(struct death_queue *dq, - struct lwan_connection *new_node) -{ - new_node->next = -1; - new_node->prev = dq->head.prev; - struct lwan_connection *prev = death_queue_idx_to_node(dq, dq->head.prev); - dq->head.prev = prev->next = death_queue_node_to_idx(dq, new_node); -} - -static void death_queue_remove(struct death_queue *dq, - struct lwan_connection *node) -{ - struct lwan_connection *prev = death_queue_idx_to_node(dq, node->prev); - struct lwan_connection *next = death_queue_idx_to_node(dq, node->next); - next->prev = node->prev; - prev->next = node->next; - - node->next = node->prev = -1; -} - -static bool death_queue_empty(struct death_queue *dq) -{ - return dq->head.next < 0; -} - -static void death_queue_move_to_last(struct death_queue *dq, - struct lwan_connection *conn) -{ - /* - * If the connection isn't keep alive, it might have a coroutine that - * should be resumed. If that's the case, schedule for this request to - * die according to the keep alive timeout. - * - * If it's not a keep alive connection, or the coroutine shouldn't be - * resumed -- then just mark it to be reaped right away. - */ - conn->time_to_die = dq->time; - if (conn->flags & (CONN_KEEP_ALIVE | CONN_SHOULD_RESUME_CORO)) - conn->time_to_die += dq->keep_alive_timeout; - - death_queue_remove(dq, conn); - death_queue_insert(dq, conn); -} - -static void death_queue_init(struct death_queue *dq, const struct lwan *lwan) -{ - dq->lwan = lwan; - dq->conns = lwan->conns; - dq->time = 0; - dq->keep_alive_timeout = lwan->config.keep_alive_timeout; - dq->head.next = dq->head.prev = -1; - dq->timeout = (struct timeout) {}; -} - -static ALWAYS_INLINE void destroy_coro(struct death_queue *dq, - struct lwan_connection *conn) -{ - death_queue_remove(dq, conn); - if (LIKELY(conn->coro)) { - coro_free(conn->coro); - conn->coro = NULL; - } - if (conn->flags & CONN_IS_ALIVE) { - conn->flags &= ~CONN_IS_ALIVE; - close(lwan_connection_get_fd(dq->lwan, conn)); - } -} - static ALWAYS_INLINE int min(const int a, const int b) { return a < b ? a : b; } #define REQUEST_FLAG(bool_, name_) \ @@ -247,7 +159,7 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, enum lwan_connection_coro_yield yield_result = coro_resume(conn->coro); /* CONN_CORO_ABORT is -1, but comparing with 0 is cheaper */ if (UNLIKELY(yield_result < CONN_CORO_MAY_RESUME)) { - destroy_coro(dq, conn); + death_queue_kill(dq, conn); return; } @@ -257,33 +169,6 @@ static ALWAYS_INLINE void resume_coro_if_needed(struct death_queue *dq, } } -static void death_queue_kill_waiting(struct death_queue *dq) -{ - dq->time++; - - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - - if (conn->time_to_die > dq->time) - return; - - destroy_coro(dq, conn); - } - - /* Death queue exhausted: reset epoch */ - dq->time = 0; -} - -static void death_queue_kill_all(struct death_queue *dq) -{ - while (!death_queue_empty(dq)) { - struct lwan_connection *conn = - death_queue_idx_to_node(dq, dq->head.next); - destroy_coro(dq, conn); - } -} - static void update_date_cache(struct lwan_thread *thread) { time_t now = time(NULL); @@ -466,7 +351,7 @@ static void *thread_io_loop(void *data) conn = event->data.ptr; if (UNLIKELY(event->events & (EPOLLRDHUP | EPOLLHUP))) { - destroy_coro(&dq, conn); + death_queue_kill(&dq, conn); continue; }