diff options
| -rw-r--r-- | opentracker.c | 42 | ||||
| -rw-r--r-- | ot_event.c | 2 | ||||
| -rw-r--r-- | ot_fdstate.c | 7 | ||||
| -rw-r--r-- | ot_fdstate.h | 3 | ||||
| -rw-r--r-- | ot_http.c | 29 | ||||
| -rw-r--r-- | ot_http.h | 7 | ||||
| -rw-r--r-- | 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) { | |||
| 190 | http_close(sock); | 190 | http_close(sock); |
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | /* The fast path, assuming that the complete request has usually arrived by the time a | ||
| 194 | connection is accepted, so it is answered here outside the event loop. The more load | ||
| 195 | a tracker has, the more likely it is that by the time it accepts, the roundtrip to | ||
| 196 | the peer has completed. | ||
| 197 | If the request could not be completely handled, the stack cookie is promoted to heap | ||
| 198 | and the connection handed over to the event loop to finish */ | ||
| 193 | static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) { | 199 | static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) { |
| 194 | struct http_data *cookie; | 200 | int64_t sock; |
| 195 | int64_t sock; | 201 | ot_ip6 ip; |
| 196 | ot_ip6 ip; | 202 | uint16_t port; |
| 197 | uint16_t port; | ||
| 198 | 203 | ||
| 199 | while ((sock = socket_accept(serversocket, ip, &port)) != -1) { | 204 | while ((sock = socket_accept(serversocket, ip, &port)) != -1) { |
| 205 | struct http_data cookie; | ||
| 206 | uint32_t generation; | ||
| 200 | 207 | ||
| 201 | if (!(cookie = (struct http_data *)calloc(1, sizeof(struct http_data)))) { | 208 | memset(&cookie, 0, sizeof(cookie)); |
| 202 | close((int)sock); | 209 | memcpy(cookie.ip, ip, sizeof(ot_ip6)); |
| 203 | continue; | 210 | cookie.interest = EVENT_IN; |
| 204 | } | 211 | cookie.flag = STRUCT_HTTP_FLAG_ONSTACK; |
| 205 | memcpy(cookie->ip, ip, sizeof(ot_ip6)); | ||
| 206 | cookie->interest = EVENT_IN; | ||
| 207 | 212 | ||
| 208 | if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, time_future_seconds(OT_CLIENT_TIMEOUT))) == -1) { | 213 | generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT)); |
| 214 | if (!fdstate_claim((int)sock, generation)) { | ||
| 209 | fdstate_close(sock); | 215 | fdstate_close(sock); |
| 210 | free(cookie); | 216 | event_close((int)sock); |
| 211 | close((int)sock); | ||
| 212 | continue; | 217 | continue; |
| 213 | } | 218 | } |
| 214 | 219 | ||
| 215 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); | 220 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); |
| 221 | |||
| 222 | handle_read(sock, ws); | ||
| 223 | |||
| 224 | /* Gone already if it was answered and closed inline */ | ||
| 225 | if (fdstate_generation((int)sock) != generation) | ||
| 226 | continue; | ||
| 227 | |||
| 228 | if (http_adopt(sock, &cookie) == -1) | ||
| 229 | http_close(sock); | ||
| 230 | else | ||
| 231 | fdstate_release((int)sock); | ||
| 216 | } | 232 | } |
| 217 | } | 233 | } |
| 218 | 234 | ||
| @@ -171,7 +171,7 @@ static int event_change(int fd, int what, uint32_t generation, int deleting) { | |||
| 171 | return 0; | 171 | return 0; |
| 172 | } | 172 | } |
| 173 | 173 | ||
| 174 | int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | 174 | int event_add(int fd, int what, uint32_t generation) { return what ? event_change(fd, what, generation, 0) : 0; } |
| 175 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | 175 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } |
| 176 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } | 176 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } |
| 177 | 177 | ||
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) { | |||
| 186 | return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; | 186 | return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; |
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | void fdstate_set_cookie(int fd, void *cookie) { | ||
| 190 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 191 | |||
| 192 | if (slot) | ||
| 193 | atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed); | ||
| 194 | } | ||
| 195 | |||
| 189 | void fdstate_set_expiry(int fd, ot_time expiry) { | 196 | void fdstate_set_expiry(int fd, ot_time expiry) { |
| 190 | ot_fdslot *slot = fdstate_slot(fd); | 197 | ot_fdslot *slot = fdstate_slot(fd); |
| 191 | 198 | ||
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); | |||
| 56 | 56 | ||
| 57 | /* Both only valid while the caller holds the descriptor. */ | 57 | /* Both only valid while the caller holds the descriptor. */ |
| 58 | void *fdstate_cookie(int fd); | 58 | void *fdstate_cookie(int fd); |
| 59 | |||
| 60 | /* Hand the slot a different cookie, for a connection whose state has moved */ | ||
| 61 | void fdstate_set_cookie(int fd, void *cookie); | ||
| 59 | void fdstate_set_expiry(int fd, ot_time expiry); | 62 | void fdstate_set_expiry(int fd, ot_time expiry); |
| 60 | 63 | ||
| 61 | #endif | 64 | #endif |
| @@ -39,9 +39,25 @@ ssize_t g_stats_path_len; | |||
| 39 | 39 | ||
| 40 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; | 40 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; |
| 41 | 41 | ||
| 42 | /* Give up a connection: the one place that knows everything hanging off it. | 42 | int http_adopt(const int64_t sock, struct http_data *cookie) { |
| 43 | The slot is released before anything is freed, so that a late event or the | 43 | struct http_data *heap = malloc(sizeof(*heap)); |
| 44 | expiry sweep finds a closed descriptor rather than a freed cookie. */ | 44 | |
| 45 | if (!heap) | ||
| 46 | return -1; | ||
| 47 | |||
| 48 | *heap = *cookie; | ||
| 49 | heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; | ||
| 50 | fdstate_set_cookie((int)sock, heap); | ||
| 51 | |||
| 52 | if (event_add((int)sock, heap->interest, fdstate_generation((int)sock)) == -1) { | ||
| 53 | fdstate_set_cookie((int)sock, cookie); | ||
| 54 | free(heap); | ||
| 55 | return -1; | ||
| 56 | } | ||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | /* Give up a connection: the one place that knows everything hanging off it. */ | ||
| 45 | void http_close(const int64_t sock) { | 61 | void http_close(const int64_t sock) { |
| 46 | struct http_data *cookie = fdstate_cookie(sock); | 62 | struct http_data *cookie = fdstate_cookie(sock); |
| 47 | 63 | ||
| @@ -54,7 +70,8 @@ void http_close(const int64_t sock) { | |||
| 54 | buf_reset(&cookie->request); | 70 | buf_reset(&cookie->request); |
| 55 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) | 71 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) |
| 56 | mutex_workqueue_canceltask(sock); | 72 | mutex_workqueue_canceltask(sock); |
| 57 | free(cookie); | 73 | if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) |
| 74 | free(cookie); | ||
| 58 | } | 75 | } |
| 59 | 76 | ||
| 60 | event_close((int)sock); | 77 | event_close((int)sock); |
| @@ -140,6 +157,10 @@ void http_interest_set(const int64_t sock, struct http_data *cookie, int what) { | |||
| 140 | if (cookie->interest == what) | 157 | if (cookie->interest == what) |
| 141 | return; | 158 | return; |
| 142 | cookie->interest = what; | 159 | cookie->interest = what; |
| 160 | /* Still on a stack, so not in the event loop yet: whoever adopts this | ||
| 161 | connection registers whatever it asked for last */ | ||
| 162 | if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK) | ||
| 163 | return; | ||
| 143 | event_modify((int)sock, what, fdstate_generation((int)sock)); | 164 | event_modify((int)sock, what, fdstate_generation((int)sock)); |
| 144 | } | 165 | } |
| 145 | 166 | ||
| @@ -22,7 +22,8 @@ typedef enum { | |||
| 22 | STRUCT_HTTP_FLAG_ZSTD = 8, | 22 | STRUCT_HTTP_FLAG_ZSTD = 8, |
| 23 | STRUCT_HTTP_FLAG_CHUNKED = 16, | 23 | STRUCT_HTTP_FLAG_CHUNKED = 16, |
| 24 | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, | 24 | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, |
| 25 | STRUCT_HTTP_FLAG_KEEPALIVE = 64 | 25 | STRUCT_HTTP_FLAG_KEEPALIVE = 64, |
| 26 | STRUCT_HTTP_FLAG_ONSTACK = 128 | ||
| 26 | } STRUCT_HTTP_FLAG; | 27 | } STRUCT_HTTP_FLAG; |
| 27 | 28 | ||
| 28 | struct http_data { | 29 | struct http_data { |
| @@ -39,6 +40,10 @@ void http_interest_set(const int64_t s, struct http_data *cookie, int | |||
| 39 | /* Close a connection and release everything that belongs to it */ | 40 | /* Close a connection and release everything that belongs to it */ |
| 40 | void http_close(const int64_t s); | 41 | void http_close(const int64_t s); |
| 41 | 42 | ||
| 43 | /* Promote a stack cookie to a heap one for persistence and handing it over | ||
| 44 | to the event loop. */ | ||
| 45 | int http_adopt(const int64_t s, struct http_data *cookie); | ||
| 46 | |||
| 42 | ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); | 47 | ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); |
| 43 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); | 48 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); |
| 44 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); | 49 | 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) { | |||
| 241 | pair_close(); | 241 | pair_close(); |
| 242 | } | 242 | } |
| 243 | 243 | ||
| 244 | /* A descriptor can be taken on before anybody waits for anything on it: the | ||
| 245 | full scrape parks a connection while a worker thread builds the reply. The | ||
| 246 | mechanisms disagree about what registering nothing means, so what is | ||
| 247 | checked is that asking for a filter afterwards still works. */ | ||
| 248 | static void case_add_nothing_then_ask(void) { | ||
| 249 | test_case("a descriptor added with no interest can be given some later"); | ||
| 250 | pair_open(); | ||
| 251 | CHECK(sv[0] != -1); | ||
| 252 | CHECK_INT(event_add(sv[0], 0, 7), 0); | ||
| 253 | CHECK_INT((int)write(sv[1], "x", 1), 1); | ||
| 254 | CHECK_INT(wait_for(sv[0], EVENT_IN, 200, NULL), 0); | ||
| 255 | |||
| 256 | CHECK_INT(event_modify(sv[0], EVENT_IN, 7), 0); | ||
| 257 | { | ||
| 258 | uint32_t generation = 0; | ||
| 259 | CHECK_INT(wait_for(sv[0], EVENT_IN, 500, &generation), 1); | ||
| 260 | CHECK_INT((int)generation, 7); | ||
| 261 | } | ||
| 262 | event_del(sv[0]); | ||
| 263 | pair_close(); | ||
| 264 | } | ||
| 265 | |||
| 244 | void test_event(void) { | 266 | void test_event(void) { |
| 245 | CHECK_INT(event_init(), 0); | 267 | CHECK_INT(event_init(), 0); |
| 246 | CHECK(event_mechanism() != NULL); | 268 | CHECK(event_mechanism() != NULL); |
| @@ -254,6 +276,7 @@ void test_event(void) { | |||
| 254 | case_timeout(); | 276 | case_timeout(); |
| 255 | case_unknown_descriptor(); | 277 | case_unknown_descriptor(); |
| 256 | case_close_takes_it_out(); | 278 | case_close_takes_it_out(); |
| 279 | case_add_nothing_then_ask(); | ||
| 257 | 280 | ||
| 258 | event_deinit(); | 281 | event_deinit(); |
| 259 | } | 282 | } |
