diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-31 15:59:41 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-31 15:59:41 +0100 |
| commit | 4d030871c004c917458b5e1c296edcfc689a2e66 (patch) | |
| tree | c6c057e47a17d8b03ea28a611ff029d950acfa8c /ot_http.c | |
| parent | 440cfeee71829308fa1cc9b5aef63beb7ab756dc (diff) | |
Make tcp worker multithreaded, too
This shamelessly steals the model established in the UDP workers, i.e.
if the kernel is able to bind multiple listening sockets to the same
endpoint, we will share them across threads. If it can't, all threads
will accept on the same socket.
The fast path handles most of the connections, only the rare case of
no data yet on the socket is offloaded to the slow path (which used to
be the mainloop) for completion. This scales nicely, as the higher the
load is, the higher chances are that an accept happens only after the
network roundtrip to the peer completes, leading to more connections
that already have data waiting for us.
We also consolidate the workstruct init and deinit code, preparation
for later unifying all thread state structs for all protocols (mca,
udc and tcp).
The config file gains a per tcp connection worker count and some docs
explaining what this knob buys and costs, nudging the user to either
keep it 0 or dial it up to at least 2.
Diffstat (limited to 'ot_http.c')
| -rw-r--r-- | ot_http.c | 38 |
1 files changed, 28 insertions, 10 deletions
| @@ -39,22 +39,30 @@ 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 | int http_adopt(const int64_t sock, struct http_data *cookie) { | 42 | struct http_data *http_adopt(const int64_t sock) { |
| 43 | struct http_data *heap = malloc(sizeof(*heap)); | 43 | struct http_data *cookie = fdstate_cookie(sock), *heap; |
| 44 | int interest; | ||
| 44 | 45 | ||
| 45 | if (!heap) | 46 | if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) |
| 46 | return -1; | 47 | return cookie; |
| 48 | |||
| 49 | if (!(heap = malloc(sizeof(*heap)))) | ||
| 50 | return NULL; | ||
| 47 | 51 | ||
| 48 | *heap = *cookie; | 52 | *heap = *cookie; |
| 49 | heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; | 53 | heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; |
| 54 | |||
| 55 | /* Publishing the cookie is what lets another thread reach this connection, | ||
| 56 | so nothing may touch it afterwards, not even to read what it asked for */ | ||
| 57 | interest = heap->interest; | ||
| 50 | fdstate_set_cookie((int)sock, heap); | 58 | fdstate_set_cookie((int)sock, heap); |
| 51 | 59 | ||
| 52 | if (event_add((int)sock, heap->interest, fdstate_generation((int)sock)) == -1) { | 60 | if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) { |
| 53 | fdstate_set_cookie((int)sock, cookie); | 61 | fdstate_set_cookie((int)sock, cookie); |
| 54 | free(heap); | 62 | free(heap); |
| 55 | return -1; | 63 | return NULL; |
| 56 | } | 64 | } |
| 57 | return 0; | 65 | return heap; |
| 58 | } | 66 | } |
| 59 | 67 | ||
| 60 | /* Give up a connection: the one place that knows everything hanging off it. */ | 68 | /* Give up a connection: the one place that knows everything hanging off it. */ |
| @@ -348,13 +356,17 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c | |||
| 348 | } | 356 | } |
| 349 | #endif | 357 | #endif |
| 350 | #endif | 358 | #endif |
| 351 | /* Pass this task to the worker thread */ | ||
| 352 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; | 359 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; |
| 353 | 360 | ||
| 354 | /* Clients waiting for us should not easily timeout */ | 361 | /* Clients waiting for us should not easily timeout */ |
| 355 | fdstate_set_expiry(sock, 0); | 362 | fdstate_set_expiry(sock, 0); |
| 356 | fullscrape_deliver(sock, format); | ||
| 357 | http_interest_set(sock, cookie, 0); | 363 | http_interest_set(sock, cookie, 0); |
| 364 | |||
| 365 | /* Everything this connection needs has been written, so it can be given | ||
| 366 | a home of its own and only then be made reachable by a worker thread */ | ||
| 367 | if (!http_adopt(sock)) | ||
| 368 | HTTPERROR_500; | ||
| 369 | fullscrape_deliver(sock, format); | ||
| 358 | return ws->reply_size = -2; | 370 | return ws->reply_size = -2; |
| 359 | } | 371 | } |
| 360 | #endif | 372 | #endif |
| @@ -366,6 +378,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c | |||
| 366 | 378 | ||
| 367 | /* Complex stats also include expensive memory debugging tools */ | 379 | /* Complex stats also include expensive memory debugging tools */ |
| 368 | fdstate_set_expiry(sock, 0); | 380 | fdstate_set_expiry(sock, 0); |
| 381 | |||
| 382 | if (!http_adopt(sock)) | ||
| 383 | HTTPERROR_500; | ||
| 369 | stats_deliver(sock, mode); | 384 | stats_deliver(sock, mode); |
| 370 | return ws->reply_size = -2; | 385 | return ws->reply_size = -2; |
| 371 | } | 386 | } |
| @@ -448,8 +463,11 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct * | |||
| 448 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; | 463 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; |
| 449 | /* Clients waiting for us should not easily timeout */ | 464 | /* Clients waiting for us should not easily timeout */ |
| 450 | fdstate_set_expiry(sock, 0); | 465 | fdstate_set_expiry(sock, 0); |
| 451 | fullscrape_deliver(sock, format); | ||
| 452 | http_interest_set(sock, cookie, 0); | 466 | http_interest_set(sock, cookie, 0); |
| 467 | |||
| 468 | if (!http_adopt(sock)) | ||
| 469 | HTTPERROR_500; | ||
| 470 | fullscrape_deliver(sock, format); | ||
| 453 | return ws->reply_size = -2; | 471 | return ws->reply_size = -2; |
| 454 | } | 472 | } |
| 455 | #endif | 473 | #endif |
