summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-28 17:08:17 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-28 17:08:17 +0100
commitf3f640b6bcf10ba463fc81cec97cfa212c520cfe (patch)
tree94341752815b6b2d4c3a931be9b90c2c0f435baf
parent18fcb27ec91f27789270550a2a005cb773bf2fe0 (diff)
Performance improvement, where it works: Let different udp worker threads have their own socket
-rw-r--r--opentracker.c35
-rw-r--r--ot_socket.c26
-rw-r--r--ot_socket.h14
-rw-r--r--ot_udp.c7
-rw-r--r--ot_udp.h3
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. */
326static 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
323static void listen_bind_one(ot_listen *entry) { 332static 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 */
373static void listen_start_udp_workers(void) { 386static 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
88int 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 */
79static void socket_set_sharing(int s, socket_sharing sharing) { 97static 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.
20typedef 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. */
25typedef 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. */
30int 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 */
diff --git a/ot_udp.c b/ot_udp.c
index 14210c3..87741da 100644
--- a/ot_udp.c
+++ b/ot_udp.c
@@ -235,13 +235,12 @@ void udp_init(void) {
235 udp_generate_rijndael_round_key(); 235 udp_generate_rijndael_round_key();
236} 236}
237 237
238void udp_workers_install(int64_t sock, unsigned int worker_count) { 238void 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
diff --git a/ot_udp.h b/ot_udp.h
index a8ab9fc..7d4432f 100644
--- a/ot_udp.h
+++ b/ot_udp.h
@@ -11,7 +11,8 @@
11#include "trackerlogic.h" 11#include "trackerlogic.h"
12 12
13void udp_init(void); 13void udp_init(void);
14void udp_workers_install(int64_t sock, unsigned int worker_count); 14/* One thread reading one socket. Called once per worker. */
15void udp_worker_install(int64_t sock);
15int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); 16int handle_udp6(int64_t serversocket, struct ot_workstruct *ws);
16 17
17#endif 18#endif