diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-28 17:08:17 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-28 17:08:17 +0100 |
| commit | f3f640b6bcf10ba463fc81cec97cfa212c520cfe (patch) | |
| tree | 94341752815b6b2d4c3a931be9b90c2c0f435baf | |
| parent | 18fcb27ec91f27789270550a2a005cb773bf2fe0 (diff) | |
Performance improvement, where it works: Let different udp worker threads have their own socket
| -rw-r--r-- | opentracker.c | 35 | ||||
| -rw-r--r-- | ot_socket.c | 26 | ||||
| -rw-r--r-- | ot_socket.h | 14 | ||||
| -rw-r--r-- | ot_udp.c | 7 | ||||
| -rw-r--r-- | ot_udp.h | 3 |
5 files changed, 74 insertions, 11 deletions
diff --git a/opentracker.c b/opentracker.c index a546fc5..f579b92 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -320,8 +320,17 @@ static void *server_mainloop(void *args) { | |||
| 320 | } | 320 | } |
| 321 | 321 | ||
| 322 | 322 | ||
| 323 | /* A group of sockets sharing one address only spreads datagrams if every | ||
| 324 | member asked for it before binding, the first one included, so whether | ||
| 325 | this entry will have workers has to be known here. */ | ||
| 326 | static socket_sharing listen_sharing_for(const ot_listen *entry) { | ||
| 327 | if (entry->proto == FLAG_UDP && entry->workers > 1 && socket_can_balance()) | ||
| 328 | return SOCKET_BALANCED; | ||
| 329 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | ||
| 330 | } | ||
| 331 | |||
| 323 | static void listen_bind_one(ot_listen *entry) { | 332 | static void listen_bind_one(ot_listen *entry) { |
| 324 | socket_sharing sharing = entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | 333 | socket_sharing sharing = listen_sharing_for(entry); |
| 325 | 334 | ||
| 326 | #ifdef _DEBUG | 335 | #ifdef _DEBUG |
| 327 | { | 336 | { |
| @@ -370,6 +379,10 @@ static void listen_bind_all(void) { | |||
| 370 | listen_bind_one(entries + i); | 379 | listen_bind_one(entries + i); |
| 371 | } | 380 | } |
| 372 | 381 | ||
| 382 | /* The socket the event loop was using becomes the first worker's. Where the | ||
| 383 | kernel spreads datagrams, every further worker binds one of its own, so | ||
| 384 | none of them share a receive queue. | ||
| 385 | All others read from the first */ | ||
| 373 | static void listen_start_udp_workers(void) { | 386 | static void listen_start_udp_workers(void) { |
| 374 | size_t count, i; | 387 | size_t count, i; |
| 375 | ot_listen *entries = config_listen_entries(&count); | 388 | ot_listen *entries = config_listen_entries(&count); |
| @@ -377,14 +390,30 @@ static void listen_start_udp_workers(void) { | |||
| 377 | udp_init(); | 390 | udp_init(); |
| 378 | 391 | ||
| 379 | for (i = 0; i < count; ++i) { | 392 | for (i = 0; i < count; ++i) { |
| 380 | ot_listen *entry = entries + i; | 393 | ot_listen *entry = entries + i; |
| 394 | int balance = listen_sharing_for(entry) == SOCKET_BALANCED; | ||
| 395 | int worker; | ||
| 381 | 396 | ||
| 382 | if (entry->proto != FLAG_UDP || entry->workers <= 0) | 397 | if (entry->proto != FLAG_UDP || entry->workers <= 0) |
| 383 | continue; | 398 | continue; |
| 384 | 399 | ||
| 385 | socket_set_blocking((int)entry->sock, 1); | 400 | socket_set_blocking((int)entry->sock, 1); |
| 386 | event_del((int)entry->sock); | 401 | event_del((int)entry->sock); |
| 387 | udp_workers_install(entry->sock, (unsigned int)entry->workers); | 402 | udp_worker_install(entry->sock); |
| 403 | |||
| 404 | for (worker = 1; worker < entry->workers; ++worker) { | ||
| 405 | int64_t sock = entry->sock; | ||
| 406 | |||
| 407 | if (balance) { | ||
| 408 | sock = socket_udp_bind(entry->ip, entry->port, SOCKET_BALANCED); | ||
| 409 | if (sock == -1) { | ||
| 410 | fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno)); | ||
| 411 | sock = entry->sock; | ||
| 412 | } else | ||
| 413 | socket_set_blocking((int)sock, 1); | ||
| 414 | } | ||
| 415 | udp_worker_install(sock); | ||
| 416 | } | ||
| 388 | } | 417 | } |
| 389 | } | 418 | } |
| 390 | 419 | ||
diff --git a/ot_socket.c b/ot_socket.c index 576087f..158459f 100644 --- a/ot_socket.c +++ b/ot_socket.c | |||
| @@ -75,13 +75,37 @@ static int socket_v6(int type) { | |||
| 75 | return s; | 75 | return s; |
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | /* SO_REUSEPORT means three different things. Linux spreads datagrams over | ||
| 79 | the group. FreeBSD permits the bind and spreads nothing, which is what | ||
| 80 | SO_REUSEPORT_LB was added for. Darwin hands every datagram to whichever | ||
| 81 | socket bound last, so a group there starves all but one reader. */ | ||
| 82 | #if defined(SO_REUSEPORT_LB) | ||
| 83 | #define OT_BALANCE_OPTION SO_REUSEPORT_LB | ||
| 84 | #elif defined(SO_REUSEPORT) && !defined(__APPLE__) | ||
| 85 | #define OT_BALANCE_OPTION SO_REUSEPORT | ||
| 86 | #endif | ||
| 87 | |||
| 88 | int socket_can_balance(void) { | ||
| 89 | #ifdef OT_BALANCE_OPTION | ||
| 90 | return 1; | ||
| 91 | #else | ||
| 92 | return 0; | ||
| 93 | #endif | ||
| 94 | } | ||
| 95 | |||
| 78 | /* The only place in the tree that needs to know what a kernel calls this */ | 96 | /* The only place in the tree that needs to know what a kernel calls this */ |
| 79 | static void socket_set_sharing(int s, socket_sharing sharing) { | 97 | static void socket_set_sharing(int s, socket_sharing sharing) { |
| 80 | int one = 1; | 98 | int one = 1; |
| 81 | 99 | ||
| 82 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | 100 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| 101 | #ifdef OT_BALANCE_OPTION | ||
| 102 | if (sharing == SOCKET_BALANCED) { | ||
| 103 | setsockopt(s, SOL_SOCKET, OT_BALANCE_OPTION, &one, sizeof(one)); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | #endif | ||
| 83 | #ifdef SO_REUSEPORT | 107 | #ifdef SO_REUSEPORT |
| 84 | if (sharing == SOCKET_SHARED) | 108 | if (sharing != SOCKET_EXCLUSIVE) |
| 85 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | 109 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); |
| 86 | #endif | 110 | #endif |
| 87 | } | 111 | } |
diff --git a/ot_socket.h b/ot_socket.h index aa4e2d2..f73a5c9 100644 --- a/ot_socket.h +++ b/ot_socket.h | |||
| @@ -16,8 +16,18 @@ | |||
| 16 | as the v4 mapped address */ | 16 | as the v4 mapped address */ |
| 17 | 17 | ||
| 18 | /* How an address may be shared with other sockets. Which socket option says | 18 | /* How an address may be shared with other sockets. Which socket option says |
| 19 | so differs between kernels, which is socket_set_sharing's business */ | 19 | so differs between kernels, which is socket_set_sharing's business. |
| 20 | typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED } socket_sharing; | 20 | |
| 21 | SHARED lets another socket bind the same address. BALANCED asks the kernel | ||
| 22 | to spread arriving datagrams over the sockets that did, which is a | ||
| 23 | different thing and not everywhere available: ask socket_can_balance | ||
| 24 | before binding a group that depends on it. */ | ||
| 25 | typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED, SOCKET_BALANCED } socket_sharing; | ||
| 26 | |||
| 27 | /* Whether this kernel spreads datagrams over a group of sockets bound to one | ||
| 28 | address. Where it does not, binding a group is worse than one socket with | ||
| 29 | several readers, so the caller wants to know first. */ | ||
| 30 | int socket_can_balance(void); | ||
| 21 | 31 | ||
| 22 | /* Create, bind and, for TCP, listen. The returned socket is non blocking | 32 | /* Create, bind and, for TCP, listen. The returned socket is non blocking |
| 23 | Returns -1 on failure with errno set */ | 33 | Returns -1 on failure with errno set */ |
| @@ -235,13 +235,12 @@ void udp_init(void) { | |||
| 235 | udp_generate_rijndael_round_key(); | 235 | udp_generate_rijndael_round_key(); |
| 236 | } | 236 | } |
| 237 | 237 | ||
| 238 | void udp_workers_install(int64_t sock, unsigned int worker_count) { | 238 | void udp_worker_install(int64_t sock) { |
| 239 | pthread_t thread_id; | 239 | pthread_t thread_id; |
| 240 | 240 | ||
| 241 | #ifdef _DEBUG | 241 | #ifdef _DEBUG |
| 242 | fprintf(stderr, "Installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock); | 242 | fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock); |
| 243 | #endif | 243 | #endif |
| 244 | while (worker_count--) | 244 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); |
| 245 | pthread_create(&thread_id, NULL, udp_worker, (void *)sock); | ||
| 246 | } | 245 | } |
| 247 | 246 | ||
| @@ -11,7 +11,8 @@ | |||
| 11 | #include "trackerlogic.h" | 11 | #include "trackerlogic.h" |
| 12 | 12 | ||
| 13 | void udp_init(void); | 13 | void udp_init(void); |
| 14 | void udp_workers_install(int64_t sock, unsigned int worker_count); | 14 | /* One thread reading one socket. Called once per worker. */ |
| 15 | void udp_worker_install(int64_t sock); | ||
| 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); | 16 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); |
| 16 | 17 | ||
| 17 | #endif | 18 | #endif |
