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 /opentracker.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 'opentracker.c')
| -rw-r--r-- | opentracker.c | 64 |
1 files changed, 40 insertions, 24 deletions
diff --git a/opentracker.c b/opentracker.c index 2eaf988..e5efff5 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -109,20 +109,9 @@ static void *server_mainloop(void *args) { | |||
| 109 | 109 | ||
| 110 | (void)args; | 110 | (void)args; |
| 111 | 111 | ||
| 112 | /* Initialize our "thread local storage" */ | 112 | if (workstruct_init(&ws) == -1) |
| 113 | memset(&ws, 0, sizeof(ws)); | ||
| 114 | stats_counters_register(&ws); | ||
| 115 | ws.inbuf = malloc(G_INBUF_SIZE); | ||
| 116 | ws.outbuf = malloc(G_OUTBUF_SIZE); | ||
| 117 | #ifdef _DEBUG_HTTPERROR | ||
| 118 | ws.debugbuf = malloc(G_DEBUGBUF_SIZE); | ||
| 119 | #endif | ||
| 120 | |||
| 121 | if (!ws.inbuf || !ws.outbuf) | ||
| 122 | panic("Initializing worker failed"); | 113 | panic("Initializing worker failed"); |
| 123 | 114 | ||
| 124 | random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); | ||
| 125 | |||
| 126 | while (g_opentracker_running) { | 115 | while (g_opentracker_running) { |
| 127 | ot_event ready[OT_EVENT_BATCH]; | 116 | ot_event ready[OT_EVENT_BATCH]; |
| 128 | int64_t sock; | 117 | int64_t sock; |
| @@ -173,13 +162,7 @@ static void *server_mainloop(void *args) { | |||
| 173 | 162 | ||
| 174 | /* This work struct is about to go out of scope, so hand its tally over | 163 | /* This work struct is about to go out of scope, so hand its tally over |
| 175 | before anything can read it there */ | 164 | before anything can read it there */ |
| 176 | stats_counters_retire(&ws); | 165 | workstruct_deinit(&ws); |
| 177 | |||
| 178 | free(ws.inbuf); | ||
| 179 | free(ws.outbuf); | ||
| 180 | #ifdef _DEBUG_HTTPERROR | ||
| 181 | free(ws.debugbuf); | ||
| 182 | #endif | ||
| 183 | return 0; | 166 | return 0; |
| 184 | } | 167 | } |
| 185 | 168 | ||
| @@ -188,7 +171,7 @@ static void *server_mainloop(void *args) { | |||
| 188 | member asked for it before binding, the first one included, so whether | 171 | member asked for it before binding, the first one included, so whether |
| 189 | this entry will have workers has to be known here. */ | 172 | this entry will have workers has to be known here. */ |
| 190 | static socket_sharing listen_sharing_for(const ot_listen *entry) { | 173 | static socket_sharing listen_sharing_for(const ot_listen *entry) { |
| 191 | if (entry->proto == FLAG_UDP && entry->workers > 1 && socket_can_balance()) | 174 | if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance()) |
| 192 | return SOCKET_BALANCED; | 175 | return SOCKET_BALANCED; |
| 193 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | 176 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; |
| 194 | } | 177 | } |
| @@ -244,10 +227,41 @@ static void listen_bind_all(void) { | |||
| 244 | listen_bind_one(entries + i); | 227 | listen_bind_one(entries + i); |
| 245 | } | 228 | } |
| 246 | 229 | ||
| 247 | /* The socket the event loop was using becomes the first worker's. Where the | 230 | /* The socket the event loop was using is passed to the first worker. |
| 248 | kernel spreads datagrams, every further worker binds one of its own, so | 231 | If start up tests show that the kernel can spread connections over |
| 249 | none of them share a receive queue. | 232 | a group bound to one address, every worker binds one of its own. |
| 250 | All others read from the first */ | 233 | If not, all workers call accept on the first. */ |
| 234 | static void listen_start_tcp_workers(void) { | ||
| 235 | size_t count, i; | ||
| 236 | ot_listen *entries = config_listen_entries(&count); | ||
| 237 | |||
| 238 | for (i = 0; i < count; ++i) { | ||
| 239 | ot_listen *entry = entries + i; | ||
| 240 | int balance = listen_sharing_for(entry) == SOCKET_BALANCED; | ||
| 241 | int worker; | ||
| 242 | |||
| 243 | if (entry->proto != FLAG_TCP || entry->workers <= 0) | ||
| 244 | continue; | ||
| 245 | |||
| 246 | event_del((int)entry->sock); | ||
| 247 | tcp_worker_install(entry->sock); | ||
| 248 | |||
| 249 | for (worker = 1; worker < entry->workers; ++worker) { | ||
| 250 | int64_t sock = entry->sock; | ||
| 251 | |||
| 252 | if (balance) { | ||
| 253 | sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED); | ||
| 254 | if (sock == -1) { | ||
| 255 | fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker, | ||
| 256 | strerror(errno)); | ||
| 257 | sock = entry->sock; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | tcp_worker_install(sock); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 251 | static void listen_start_udp_workers(void) { | 265 | static void listen_start_udp_workers(void) { |
| 252 | size_t count, i; | 266 | size_t count, i; |
| 253 | ot_listen *entries = config_listen_entries(&count); | 267 | ot_listen *entries = config_listen_entries(&count); |
| @@ -408,9 +422,11 @@ int main(int argc, char **argv) { | |||
| 408 | 422 | ||
| 409 | install_signal_handlers(); | 423 | install_signal_handlers(); |
| 410 | 424 | ||
| 425 | listen_start_tcp_workers(); | ||
| 411 | listen_start_udp_workers(); | 426 | listen_start_udp_workers(); |
| 412 | 427 | ||
| 413 | server_mainloop(0); | 428 | server_mainloop(0); |
| 429 | tcp_workers_deinit(); | ||
| 414 | udp_workers_deinit(); | 430 | udp_workers_deinit(); |
| 415 | 431 | ||
| 416 | /* No thread is serving any more, so the teardown can run where a debugger | 432 | /* No thread is serving any more, so the teardown can run where a debugger |
