summaryrefslogtreecommitdiff
path: root/ot_tcp.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-31 15:59:41 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-31 15:59:41 +0100
commit4d030871c004c917458b5e1c296edcfc689a2e66 (patch)
treec6c057e47a17d8b03ea28a611ff029d950acfa8c /ot_tcp.c
parent440cfeee71829308fa1cc9b5aef63beb7ab756dc (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_tcp.c')
-rw-r--r--ot_tcp.c83
1 files changed, 82 insertions, 1 deletions
diff --git a/ot_tcp.c b/ot_tcp.c
index 8647a47..47697ca 100644
--- a/ot_tcp.c
+++ b/ot_tcp.c
@@ -5,6 +5,10 @@
5 5
6/* System */ 6/* System */
7#include <errno.h> 7#include <errno.h>
8#include <pthread.h>
9#include <stdatomic.h>
10#include <stdio.h>
11#include <stdlib.h>
8#include <string.h> 12#include <string.h>
9#include <unistd.h> 13#include <unistd.h>
10 14
@@ -149,9 +153,86 @@ void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) {
149 if (fdstate_generation((int)sock) != generation) 153 if (fdstate_generation((int)sock) != generation)
150 continue; 154 continue;
151 155
152 if (http_adopt(sock, &cookie) == -1) 156 /* Handling it may already have given it a heap home and handed it to
157 another thread, and then it is no longer this frame's to touch */
158 if (fdstate_cookie((int)sock) != &cookie)
159 fdstate_release((int)sock);
160 else if (!http_adopt(sock))
153 http_close(sock); 161 http_close(sock);
154 else 162 else
155 fdstate_release((int)sock); 163 fdstate_release((int)sock);
156 } 164 }
157} 165}
166
167/* One of these per worker, each allocated on its own so that growing the
168 array below never moves the one a running worker holds. */
169struct tcp_worker {
170 pthread_t thread;
171 int64_t sock;
172 _Atomic int left;
173};
174
175static struct tcp_worker **g_workers;
176static size_t g_worker_count;
177
178/* A worker blocks in accept and nowhere else: the connection it is handed is
179 non blocking, so a client that stops talking is given to the event loop
180 rather than keeping this thread. */
181static void *tcp_worker(void *args) {
182 struct tcp_worker *me = args;
183 struct ot_workstruct ws;
184
185 if (workstruct_init(&ws) == -1) {
186 fprintf(stderr, "Warning: a tcp worker could not get its buffers and will not run.\n");
187 atomic_store(&me->left, 1);
188 return NULL;
189 }
190
191 while (g_opentracker_running)
192 tcp_accept(me->sock, &ws);
193
194 workstruct_deinit(&ws);
195 atomic_store(&me->left, 1);
196 return NULL;
197}
198
199void tcp_worker_install(int64_t sock) {
200 struct tcp_worker **grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers));
201 struct tcp_worker *worker;
202
203 if (!grown || !(worker = calloc(1, sizeof(*worker)))) {
204 fprintf(stderr, "Warning: out of memory installing a tcp worker, this socket stays with the workers already running.\n");
205 g_workers = grown ? grown : g_workers;
206 return;
207 }
208 g_workers = grown;
209 worker->sock = sock;
210
211 socket_set_blocking((int)sock, 1);
212 socket_defer_accept((int)sock);
213
214 if (pthread_create(&worker->thread, NULL, tcp_worker, worker))
215 free(worker);
216 else
217 g_workers[g_worker_count++] = worker;
218}
219
220void tcp_workers_deinit(void) {
221 size_t i;
222
223 for (i = 0; i < g_worker_count; ++i) {
224 struct tcp_worker *worker = g_workers[i];
225
226 /* Repeated because a signal only interrupts a call already under way */
227 while (!atomic_load(&worker->left)) {
228 pthread_kill(worker->thread, OT_SIGNAL_WAKE);
229 usleep(1000);
230 }
231 pthread_join(worker->thread, NULL);
232 free(worker);
233 }
234
235 free(g_workers);
236 g_workers = NULL;
237 g_worker_count = 0;
238}