From 15f9f81b5a8398fefca023e90d8d1c168ecef620 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 23:11:41 +0100 Subject: Introduce a fast path assuming that a tcp connection's payload already has arrived at accept time --- opentracker.c | 42 +++++++++++++++++++++++++++++------------- ot_event.c | 2 +- ot_fdstate.c | 7 +++++++ ot_fdstate.h | 3 +++ ot_http.c | 29 +++++++++++++++++++++++++---- ot_http.h | 7 ++++++- tests/t_event.c | 23 +++++++++++++++++++++++ 7 files changed, 94 insertions(+), 19 deletions(-) diff --git a/opentracker.c b/opentracker.c index ec92fe0..b47759e 100644 --- a/opentracker.c +++ b/opentracker.c @@ -190,29 +190,45 @@ static void handle_write(const int64_t sock) { http_close(sock); } +/* The fast path, assuming that the complete request has usually arrived by the time a + connection is accepted, so it is answered here outside the event loop. The more load + a tracker has, the more likely it is that by the time it accepts, the roundtrip to + the peer has completed. + If the request could not be completely handled, the stack cookie is promoted to heap + and the connection handed over to the event loop to finish */ static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) { - struct http_data *cookie; - int64_t sock; - ot_ip6 ip; - uint16_t port; + int64_t sock; + ot_ip6 ip; + uint16_t port; while ((sock = socket_accept(serversocket, ip, &port)) != -1) { + struct http_data cookie; + uint32_t generation; - if (!(cookie = (struct http_data *)calloc(1, sizeof(struct http_data)))) { - close((int)sock); - continue; - } - memcpy(cookie->ip, ip, sizeof(ot_ip6)); - cookie->interest = EVENT_IN; + memset(&cookie, 0, sizeof(cookie)); + memcpy(cookie.ip, ip, sizeof(ot_ip6)); + cookie.interest = EVENT_IN; + cookie.flag = STRUCT_HTTP_FLAG_ONSTACK; - if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, time_future_seconds(OT_CLIENT_TIMEOUT))) == -1) { + generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT)); + if (!fdstate_claim((int)sock, generation)) { fdstate_close(sock); - free(cookie); - close((int)sock); + event_close((int)sock); continue; } stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); + + handle_read(sock, ws); + + /* Gone already if it was answered and closed inline */ + if (fdstate_generation((int)sock) != generation) + continue; + + if (http_adopt(sock, &cookie) == -1) + http_close(sock); + else + fdstate_release((int)sock); } } diff --git a/ot_event.c b/ot_event.c index 8f55817..b4c4a55 100644 --- a/ot_event.c +++ b/ot_event.c @@ -171,7 +171,7 @@ static int event_change(int fd, int what, uint32_t generation, int deleting) { return 0; } -int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } +int event_add(int fd, int what, uint32_t generation) { return what ? event_change(fd, what, generation, 0) : 0; } int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } int event_del(int fd) { return event_change(fd, 0, 0, 1); } diff --git a/ot_fdstate.c b/ot_fdstate.c index 537bcb0..7d742da 100644 --- a/ot_fdstate.c +++ b/ot_fdstate.c @@ -186,6 +186,13 @@ void *fdstate_cookie(int fd) { return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; } +void fdstate_set_cookie(int fd, void *cookie) { + ot_fdslot *slot = fdstate_slot(fd); + + if (slot) + atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed); +} + void fdstate_set_expiry(int fd, ot_time expiry) { ot_fdslot *slot = fdstate_slot(fd); diff --git a/ot_fdstate.h b/ot_fdstate.h index c09f018..93890d1 100644 --- a/ot_fdstate.h +++ b/ot_fdstate.h @@ -56,6 +56,9 @@ int fdstate_next_expired(ot_time now, int *cursor); /* Both only valid while the caller holds the descriptor. */ void *fdstate_cookie(int fd); + +/* Hand the slot a different cookie, for a connection whose state has moved */ +void fdstate_set_cookie(int fd, void *cookie); void fdstate_set_expiry(int fd, ot_time expiry); #endif diff --git a/ot_http.c b/ot_http.c index 66f0102..bb6ff24 100644 --- a/ot_http.c +++ b/ot_http.c @@ -39,9 +39,25 @@ ssize_t g_stats_path_len; enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; -/* Give up a connection: the one place that knows everything hanging off it. - The slot is released before anything is freed, so that a late event or the - expiry sweep finds a closed descriptor rather than a freed cookie. */ +int http_adopt(const int64_t sock, struct http_data *cookie) { + struct http_data *heap = malloc(sizeof(*heap)); + + if (!heap) + return -1; + + *heap = *cookie; + heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; + fdstate_set_cookie((int)sock, heap); + + if (event_add((int)sock, heap->interest, fdstate_generation((int)sock)) == -1) { + fdstate_set_cookie((int)sock, cookie); + free(heap); + return -1; + } + return 0; +} + +/* Give up a connection: the one place that knows everything hanging off it. */ void http_close(const int64_t sock) { struct http_data *cookie = fdstate_cookie(sock); @@ -54,7 +70,8 @@ void http_close(const int64_t sock) { buf_reset(&cookie->request); if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) mutex_workqueue_canceltask(sock); - free(cookie); + if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) + free(cookie); } event_close((int)sock); @@ -140,6 +157,10 @@ void http_interest_set(const int64_t sock, struct http_data *cookie, int what) { if (cookie->interest == what) return; cookie->interest = what; + /* Still on a stack, so not in the event loop yet: whoever adopts this + connection registers whatever it asked for last */ + if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK) + return; event_modify((int)sock, what, fdstate_generation((int)sock)); } diff --git a/ot_http.h b/ot_http.h index ba16de4..82d645b 100644 --- a/ot_http.h +++ b/ot_http.h @@ -22,7 +22,8 @@ typedef enum { STRUCT_HTTP_FLAG_ZSTD = 8, STRUCT_HTTP_FLAG_CHUNKED = 16, STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, - STRUCT_HTTP_FLAG_KEEPALIVE = 64 + STRUCT_HTTP_FLAG_KEEPALIVE = 64, + STRUCT_HTTP_FLAG_ONSTACK = 128 } STRUCT_HTTP_FLAG; struct http_data { @@ -39,6 +40,10 @@ void http_interest_set(const int64_t s, struct http_data *cookie, int /* Close a connection and release everything that belongs to it */ void http_close(const int64_t s); +/* Promote a stack cookie to a heap one for persistence and handing it over + to the event loop. */ +int http_adopt(const int64_t s, struct http_data *cookie); + ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); diff --git a/tests/t_event.c b/tests/t_event.c index ad3948f..706a285 100644 --- a/tests/t_event.c +++ b/tests/t_event.c @@ -241,6 +241,28 @@ static void case_close_takes_it_out(void) { pair_close(); } +/* A descriptor can be taken on before anybody waits for anything on it: the + full scrape parks a connection while a worker thread builds the reply. The + mechanisms disagree about what registering nothing means, so what is + checked is that asking for a filter afterwards still works. */ +static void case_add_nothing_then_ask(void) { + test_case("a descriptor added with no interest can be given some later"); + pair_open(); + CHECK(sv[0] != -1); + CHECK_INT(event_add(sv[0], 0, 7), 0); + CHECK_INT((int)write(sv[1], "x", 1), 1); + CHECK_INT(wait_for(sv[0], EVENT_IN, 200, NULL), 0); + + CHECK_INT(event_modify(sv[0], EVENT_IN, 7), 0); + { + uint32_t generation = 0; + CHECK_INT(wait_for(sv[0], EVENT_IN, 500, &generation), 1); + CHECK_INT((int)generation, 7); + } + event_del(sv[0]); + pair_close(); +} + void test_event(void) { CHECK_INT(event_init(), 0); CHECK(event_mechanism() != NULL); @@ -254,6 +276,7 @@ void test_event(void) { case_timeout(); case_unknown_descriptor(); case_close_takes_it_out(); + case_add_nothing_then_ask(); event_deinit(); } -- cgit v1.3