summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--man4/opentracker.conf.416
-rw-r--r--opentracker.c214
-rw-r--r--opentracker.conf.sample7
-rw-r--r--ot_fdstate.c9
-rw-r--r--ot_fullscrape.c25
-rw-r--r--ot_fullscrape.h1
-rw-r--r--ot_http.c41
-rw-r--r--ot_http.h7
-rw-r--r--ot_mutex.c221
-rw-r--r--ot_mutex.h57
-rw-r--r--ot_stats.c9
-rw-r--r--ot_sync.c7
-rw-r--r--ot_tcp.c238
-rw-r--r--ot_tcp.h28
-rw-r--r--ot_udp.c21
-rw-r--r--ot_workqueue.c243
-rw-r--r--ot_workqueue.h78
-rw-r--r--tests/integration/test_tcpworkers.py67
-rw-r--r--tests/runner.c2
-rw-r--r--tests/t_workqueue.c (renamed from tests/t_mutex.c)50
-rw-r--r--tests/test.h2
-rw-r--r--trackerlogic.c32
-rw-r--r--trackerlogic.h6
24 files changed, 861 insertions, 528 deletions
diff --git a/Makefile b/Makefile
index 2e52367..61eb872 100644
--- a/Makefile
+++ b/Makefile
@@ -69,8 +69,8 @@ CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS)
69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat 69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat
70 70
71BINARY =opentracker 71BINARY =opentracker
72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_tcp.h ot_workqueue.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_tcp.c ot_workqueue.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
75 75
76all: $(BINARY) $(BINARY).debug 76all: $(BINARY) $(BINARY).debug
@@ -96,8 +96,8 @@ proxy.debug: $(SOURCES_proxy) $(HEADERS)
96# has to. tests/stubs.c supplies the few symbols those modules reference but 96# has to. tests/stubs.c supplies the few symbols those modules reference but
97# the tests never reach. 97# the tests never reach.
98TESTBIN=tests/runner 98TESTBIN=tests/runner
99TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c 99TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_workqueue.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
100TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 100TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c ot_workqueue.c
101TEST_HEADERS=tests/test.h $(HEADERS) 101TEST_HEADERS=tests/test.h $(HEADERS)
102# Tests reach code that sits behind feature flags, so the units under test are 102# Tests reach code that sits behind feature flags, so the units under test are
103# built with those features enabled. A test binary is not a production binary. 103# built with those features enabled. A test binary is not a production binary.
diff --git a/man4/opentracker.conf.4 b/man4/opentracker.conf.4
index aa82e1d..1bd1c7a 100644
--- a/man4/opentracker.conf.4
+++ b/man4/opentracker.conf.4
@@ -42,6 +42,14 @@ in the thread that also serves TCP. See
42.Sx TUNING 42.Sx TUNING
43before raising it. 43before raising it.
44.Pp 44.Pp
45.It listen.tcp.workers Ar threads
46Specifies how many threads accept and answer HTTP connections, for every
47listen statement that does not carry a
48.Cm workers
49option of its own. Defaults to 0, which serves them from the event loop. See
50.Sx TUNING
51before raising it.
52.Pp
45.It listen.shared Ar boolean 53.It listen.shared Ar boolean
46Permits another process to bind the same address, for every listen statement 54Permits another process to bind the same address, for every listen statement
47that does not carry a 55that does not carry a
@@ -101,6 +109,14 @@ group only where the answer is yes.
101in the source distribution sweeps the setting against a running tracker. 109in the source distribution sweeps the setting against a running tracker.
102Measure on the machine that will carry the traffic. 110Measure on the machine that will carry the traffic.
103.Pp 111.Pp
112A TCP worker accepts a connection and answers it on its own stack, without
113the event loop seeing it at all; only a connection it cannot finish is handed
114over. One worker serves less than none, because accepting leaves the event
115loop's thread without anything gained in return, so the setting is worth
116either 0 or at least 2.
117.Pa tests/http_load.c
118in the source distribution drives the tracker over HTTP to measure it.
119.Pp
104On FreeBSD the 120On FreeBSD the
105.Xr accf_data 9 121.Xr accf_data 9
106filter has the kernel hold a new connection until data arrives on it, so the 122filter has the kernel hold a new connection until data arrives on it, so the
diff --git a/opentracker.c b/opentracker.c
index b47759e..2f2746d 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -29,12 +29,14 @@
29#include "ot_ip.h" 29#include "ot_ip.h"
30#include "ot_livesync.h" 30#include "ot_livesync.h"
31#include "ot_mutex.h" 31#include "ot_mutex.h"
32#include "ot_workqueue.h"
32#include "ot_random.h" 33#include "ot_random.h"
33#include "ot_scan.h" 34#include "ot_scan.h"
34#include "ot_event.h" 35#include "ot_event.h"
35#include "ot_fdstate.h" 36#include "ot_fdstate.h"
36#include "ot_socket.h" 37#include "ot_socket.h"
37#include "ot_stats.h" 38#include "ot_stats.h"
39#include "ot_tcp.h"
38#include "ot_udp.h" 40#include "ot_udp.h"
39#include "trackerlogic.h" 41#include "trackerlogic.h"
40 42
@@ -96,142 +98,6 @@ static void install_signal_handlers(void) {
96} 98}
97 99
98 100
99static ssize_t header_complete(char *request, ssize_t byte_count) {
100 ssize_t i = 0, state = 0;
101
102 for (i = 1; i < byte_count; i += 2)
103 if (request[i] <= 13) {
104 i--;
105 for (state = 0; i < byte_count; ++i) {
106 char c = request[i];
107 if (c == '\r' || c == '\n')
108 state = (state >> 2) | ((c << 6) & 0xc0);
109 else
110 break;
111 if (state >= 0xa0 || state == 0x99)
112 return i + 1;
113 }
114 }
115 return 0;
116}
117
118static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
119 struct http_data *cookie = fdstate_cookie(sock);
120 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
121
122 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
123 http_close(sock);
124 return;
125 }
126
127 if (byte_count == -1)
128 return;
129
130 ws->request = NULL;
131 ws->request_size = 0;
132 ws->header_size = 0;
133 ws->keep_alive = 0;
134
135 /* If we get the whole request in one packet, handle it without copying */
136 if (!cookie->request.data) {
137 if ((ws->header_size = header_complete(ws->inbuf, byte_count))) {
138 ws->request = ws->inbuf;
139 ws->request_size = byte_count;
140 http_handle_request(sock, ws);
141 } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count))
142 http_close(sock);
143 return;
144 }
145
146 if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) {
147 http_issue_error(sock, ws, CODE_HTTPERROR_500);
148 return;
149 }
150
151 while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) {
152 ws->request = cookie->request.data;
153 ws->request_size = (ssize_t)cookie->request.size;
154 http_handle_request(sock, ws);
155#ifdef WANT_KEEPALIVE
156 if (!ws->keep_alive)
157#endif
158 return;
159 }
160}
161
162static void handle_write(const int64_t sock) {
163 struct http_data *cookie = fdstate_cookie(sock);
164 int chunked = 0;
165 int keep_alive = 0;
166
167 if (cookie) {
168 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
169 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
170
171 if (cookie->batch) {
172 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
173 http_close(sock);
174 return;
175 }
176 if (batch_pending(cookie->batch))
177 return;
178 }
179 }
180
181 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
182 if (chunked)
183 http_interest_set(sock, cookie, 0);
184 else if (keep_alive) {
185 /* The reply is out and the client asked to keep the connection, so go
186 back to waiting for the next request */
187 http_interest_set(sock, cookie, EVENT_IN);
188 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT));
189 } else
190 http_close(sock);
191}
192
193/* The fast path, assuming that the complete request has usually arrived by the time a
194 connection is accepted, so it is answered here outside the event loop. The more load
195 a tracker has, the more likely it is that by the time it accepts, the roundtrip to
196 the peer has completed.
197 If the request could not be completely handled, the stack cookie is promoted to heap
198 and the connection handed over to the event loop to finish */
199static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) {
200 int64_t sock;
201 ot_ip6 ip;
202 uint16_t port;
203
204 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
205 struct http_data cookie;
206 uint32_t generation;
207
208 memset(&cookie, 0, sizeof(cookie));
209 memcpy(cookie.ip, ip, sizeof(ot_ip6));
210 cookie.interest = EVENT_IN;
211 cookie.flag = STRUCT_HTTP_FLAG_ONSTACK;
212
213 generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT));
214 if (!fdstate_claim((int)sock, generation)) {
215 fdstate_close(sock);
216 event_close((int)sock);
217 continue;
218 }
219
220 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
221
222 handle_read(sock, ws);
223
224 /* Gone already if it was answered and closed inline */
225 if (fdstate_generation((int)sock) != generation)
226 continue;
227
228 if (http_adopt(sock, &cookie) == -1)
229 http_close(sock);
230 else
231 fdstate_release((int)sock);
232 }
233}
234
235/* How many ready descriptors one wait hands back at a time */ 101/* How many ready descriptors one wait hands back at a time */
236#define OT_EVENT_BATCH 512 102#define OT_EVENT_BATCH 512
237 103
@@ -244,20 +110,9 @@ static void *server_mainloop(void *args) {
244 110
245 (void)args; 111 (void)args;
246 112
247 /* Initialize our "thread local storage" */ 113 if (workstruct_init(&ws) == -1)
248 memset(&ws, 0, sizeof(ws));
249 stats_counters_register(&ws);
250 ws.inbuf = malloc(G_INBUF_SIZE);
251 ws.outbuf = malloc(G_OUTBUF_SIZE);
252#ifdef _DEBUG_HTTPERROR
253 ws.debugbuf = malloc(G_DEBUGBUF_SIZE);
254#endif
255
256 if (!ws.inbuf || !ws.outbuf)
257 panic("Initializing worker failed"); 114 panic("Initializing worker failed");
258 115
259 random_bytes(ws.rand48_state, sizeof(ws.rand48_state));
260
261 while (g_opentracker_running) { 116 while (g_opentracker_running) {
262 ot_event ready[OT_EVENT_BATCH]; 117 ot_event ready[OT_EVENT_BATCH];
263 int64_t sock; 118 int64_t sock;
@@ -277,22 +132,24 @@ static void *server_mainloop(void *args) {
277 cookie = fdstate_cookie((int)sock); 132 cookie = fdstate_cookie((int)sock);
278 133
279 if ((intptr_t)cookie == FLAG_TCP) 134 if ((intptr_t)cookie == FLAG_TCP)
280 handle_accept(sock, &ws); 135 tcp_accept(sock, &ws);
281 else if ((intptr_t)cookie == FLAG_UDP) 136 else if ((intptr_t)cookie == FLAG_UDP)
282 handle_udp6(sock, &ws); 137 handle_udp6(sock, &ws);
283 else if ((intptr_t)cookie == FLAG_SELFPIPE) 138 else if ((intptr_t)cookie == FLAG_SELFPIPE)
284 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0) 139 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
285 ; 140 ;
286 else { 141 else if (fdstate_claim((int)sock, ready[i].generation)) {
287 if (ready[i].what & EVENT_IN) 142 if (ready[i].what & EVENT_IN)
288 handle_read(sock, &ws); 143 tcp_read(sock, &ws);
289 /* The read may have closed it, so look again before writing */ 144 /* The read may have closed it, so look again before writing */
290 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) 145 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
291 handle_write(sock); 146 tcp_write(sock);
147 if (fdstate_generation((int)sock) == ready[i].generation)
148 fdstate_release((int)sock);
292 } 149 }
293 } 150 }
294 151
295 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 152 while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
296 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 153 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
297 154
298 if (time_now_seconds() > next_timeout_check) { 155 if (time_now_seconds() > next_timeout_check) {
@@ -308,13 +165,7 @@ static void *server_mainloop(void *args) {
308 165
309 /* This work struct is about to go out of scope, so hand its tally over 166 /* This work struct is about to go out of scope, so hand its tally over
310 before anything can read it there */ 167 before anything can read it there */
311 stats_counters_retire(&ws); 168 workstruct_deinit(&ws);
312
313 free(ws.inbuf);
314 free(ws.outbuf);
315#ifdef _DEBUG_HTTPERROR
316 free(ws.debugbuf);
317#endif
318 return 0; 169 return 0;
319} 170}
320 171
@@ -323,7 +174,7 @@ static void *server_mainloop(void *args) {
323 member asked for it before binding, the first one included, so whether 174 member asked for it before binding, the first one included, so whether
324 this entry will have workers has to be known here. */ 175 this entry will have workers has to be known here. */
325static socket_sharing listen_sharing_for(const ot_listen *entry) { 176static socket_sharing listen_sharing_for(const ot_listen *entry) {
326 if (entry->proto == FLAG_UDP && entry->workers > 1 && socket_can_balance()) 177 if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance())
327 return SOCKET_BALANCED; 178 return SOCKET_BALANCED;
328 return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; 179 return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE;
329} 180}
@@ -379,10 +230,41 @@ static void listen_bind_all(void) {
379 listen_bind_one(entries + i); 230 listen_bind_one(entries + i);
380} 231}
381 232
382/* The socket the event loop was using becomes the first worker's. Where the 233/* The socket the event loop was using is passed to the first worker.
383 kernel spreads datagrams, every further worker binds one of its own, so 234 If start up tests show that the kernel can spread connections over
384 none of them share a receive queue. 235 a group bound to one address, every worker binds one of its own.
385 All others read from the first */ 236 If not, all workers call accept on the first. */
237static void listen_start_tcp_workers(void) {
238 size_t count, i;
239 ot_listen *entries = config_listen_entries(&count);
240
241 for (i = 0; i < count; ++i) {
242 ot_listen *entry = entries + i;
243 int balance = listen_sharing_for(entry) == SOCKET_BALANCED;
244 int worker;
245
246 if (entry->proto != FLAG_TCP || entry->workers <= 0)
247 continue;
248
249 event_del((int)entry->sock);
250 tcp_worker_install(entry->sock);
251
252 for (worker = 1; worker < entry->workers; ++worker) {
253 int64_t sock = entry->sock;
254
255 if (balance) {
256 sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED);
257 if (sock == -1) {
258 fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker,
259 strerror(errno));
260 sock = entry->sock;
261 }
262 }
263 tcp_worker_install(sock);
264 }
265 }
266}
267
386static void listen_start_udp_workers(void) { 268static void listen_start_udp_workers(void) {
387 size_t count, i; 269 size_t count, i;
388 ot_listen *entries = config_listen_entries(&count); 270 ot_listen *entries = config_listen_entries(&count);
@@ -543,9 +425,11 @@ int main(int argc, char **argv) {
543 425
544 install_signal_handlers(); 426 install_signal_handlers();
545 427
428 listen_start_tcp_workers();
546 listen_start_udp_workers(); 429 listen_start_udp_workers();
547 430
548 server_mainloop(0); 431 server_mainloop(0);
432 tcp_workers_deinit();
549 udp_workers_deinit(); 433 udp_workers_deinit();
550 434
551 /* No thread is serving any more, so the teardown can run where a debugger 435 /* No thread is serving any more, so the teardown can run where a debugger
diff --git a/opentracker.conf.sample b/opentracker.conf.sample
index 1d535e8..f075703 100644
--- a/opentracker.conf.sample
+++ b/opentracker.conf.sample
@@ -21,9 +21,9 @@
21# 21#
22# A listen statement takes options after the address: 22# A listen statement takes options after the address:
23# 23#
24# workers=<count> handle this udp socket in blocking reads in that 24# workers=<count> handle this socket in that many dedicated worker
25# many dedicated worker threads instead of in the 25# threads instead of in the event loop. Defaults to 1
26# event loop. Defaults to 1. Ignored for tcp. 26# for udp and 0 for tcp.
27# shared allow another process to bind the same address and 27# shared allow another process to bind the same address and
28# let the kernel share the traffic. Only useful if you 28# let the kernel share the traffic. Only useful if you
29# deliberately run a second tracker there. 29# deliberately run a second tracker there.
@@ -35,6 +35,7 @@
35# anywhere. 35# anywhere.
36# 36#
37# listen.udp.workers 4 37# listen.udp.workers 4
38# listen.tcp.workers 4
38# listen.shared 1 39# listen.shared 1
39# 40#
40# Raising the worker count pays off where the kernel spreads arriving 41# Raising the worker count pays off where the kernel spreads arriving
diff --git a/ot_fdstate.c b/ot_fdstate.c
index 7d742da..09daf50 100644
--- a/ot_fdstate.c
+++ b/ot_fdstate.c
@@ -91,7 +91,7 @@ uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
91 } 91 }
92 92
93 word = atomic_load_explicit(&slot->state, memory_order_relaxed); 93 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
94 atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed); 94 atomic_store_explicit(&slot->cookie, cookie, memory_order_release);
95 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed); 95 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
96 /* Publishes the two above to whoever claims the slot next */ 96 /* Publishes the two above to whoever claims the slot next */
97 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release); 97 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release);
@@ -183,14 +183,17 @@ int fdstate_next_expired(ot_time now, int *cursor) {
183void *fdstate_cookie(int fd) { 183void *fdstate_cookie(int fd) {
184 ot_fdslot *slot = fdstate_slot(fd); 184 ot_fdslot *slot = fdstate_slot(fd);
185 185
186 return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; 186 /* Pairs with the release in fdstate_set_cookie and fdstate_open */
187 return slot ? atomic_load_explicit(&slot->cookie, memory_order_acquire) : NULL;
187} 188}
188 189
189void fdstate_set_cookie(int fd, void *cookie) { 190void fdstate_set_cookie(int fd, void *cookie) {
190 ot_fdslot *slot = fdstate_slot(fd); 191 ot_fdslot *slot = fdstate_slot(fd);
191 192
193 /* Publishes what the cookie points at to whoever reads it next, which may
194 be another thread entirely */
192 if (slot) 195 if (slot)
193 atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed); 196 atomic_store_explicit(&slot->cookie, cookie, memory_order_release);
194} 197}
195 198
196void fdstate_set_expiry(int fd, ot_time expiry) { 199void fdstate_set_expiry(int fd, ot_time expiry) {
diff --git a/ot_fullscrape.c b/ot_fullscrape.c
index a86d6d7..ae29308 100644
--- a/ot_fullscrape.c
+++ b/ot_fullscrape.c
@@ -23,6 +23,7 @@
23#include "ot_fullscrape.h" 23#include "ot_fullscrape.h"
24#include "ot_iovec.h" 24#include "ot_iovec.h"
25#include "ot_mutex.h" 25#include "ot_mutex.h"
26#include "ot_workqueue.h"
26#include "ot_urlencoded.h" 27#include "ot_urlencoded.h"
27#include "trackerlogic.h" 28#include "trackerlogic.h"
28 29
@@ -54,7 +55,7 @@ static void *fullscrape_worker(void *args) {
54 55
55 while (g_opentracker_running) { 56 while (g_opentracker_running) {
56 ot_tasktype tasktype = TASK_FULLSCRAPE; 57 ot_tasktype tasktype = TASK_FULLSCRAPE;
57 ot_taskid taskid = mutex_workqueue_poptask(&tasktype); 58 ot_taskid taskid = workqueue_poptask(&tasktype);
58 if (!taskid) 59 if (!taskid)
59 break; 60 break;
60#ifdef WANT_COMPRESSION_ZSTD 61#ifdef WANT_COMPRESSION_ZSTD
@@ -68,7 +69,7 @@ static void *fullscrape_worker(void *args) {
68 else 69 else
69#endif 70#endif
70 fullscrape_make(taskid, tasktype); 71 fullscrape_make(taskid, tasktype);
71 mutex_workqueue_pushchunked(taskid, NULL); 72 workqueue_pushchunked(taskid, NULL);
72 } 73 }
73 return NULL; 74 return NULL;
74} 75}
@@ -79,12 +80,12 @@ void fullscrape_init(void) {
79} 80}
80 81
81void fullscrape_deinit(void) { 82void fullscrape_deinit(void) {
82 mutex_workqueue_wakeall(); 83 workqueue_wakeall();
83 pthread_join( thread_id, NULL ); 84 pthread_join( thread_id, NULL );
84} 85}
85 86
86void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { 87void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
87 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); 88 workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
88} 89}
89 90
90static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) { 91static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
@@ -170,7 +171,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
170 if (r > re) { 171 if (r > re) {
171 iovector.iov_len = r - (char *)iovector.iov_base; 172 iovector.iov_len = r - (char *)iovector.iov_base;
172 173
173 if (mutex_workqueue_pushchunked(taskid, &iovector)) { 174 if (workqueue_pushchunked(taskid, &iovector)) {
174 free(iovector.iov_base); 175 free(iovector.iov_base);
175 mutex_bucket_unlock(bucket, 0); 176 mutex_bucket_unlock(bucket, 0);
176 return; 177 return;
@@ -200,7 +201,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
200 201
201 /* Send rest of data */ 202 /* Send rest of data */
202 iovector.iov_len = r - (char *)iovector.iov_base; 203 iovector.iov_len = r - (char *)iovector.iov_base;
203 if (mutex_workqueue_pushchunked(taskid, &iovector)) 204 if (workqueue_pushchunked(taskid, &iovector))
204 free(iovector.iov_base); 205 free(iovector.iov_base);
205} 206}
206 207
@@ -251,7 +252,7 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
251 while (!strm.avail_out) { 252 while (!strm.avail_out) {
252 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; 253 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
253 254
254 if (mutex_workqueue_pushchunked(taskid, &iovector)) { 255 if (workqueue_pushchunked(taskid, &iovector)) {
255 free(iovector.iov_base); 256 free(iovector.iov_base);
256 mutex_bucket_unlock(bucket, 0); 257 mutex_bucket_unlock(bucket, 0);
257 return; 258 return;
@@ -291,7 +292,7 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
291 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n"); 292 fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n");
292 293
293 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; 294 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
294 if (mutex_workqueue_pushchunked(taskid, &iovector)) { 295 if (workqueue_pushchunked(taskid, &iovector)) {
295 free(iovector.iov_base); 296 free(iovector.iov_base);
296 deflateEnd(&strm); 297 deflateEnd(&strm);
297 return; 298 return;
@@ -314,7 +315,7 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
314 315
315 /* Only pass the new buffer if there actually was some data left in the buffer */ 316 /* Only pass the new buffer if there actually was some data left in the buffer */
316 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; 317 iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base;
317 if (!iovector.iov_len || mutex_workqueue_pushchunked(taskid, &iovector)) 318 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
318 free(iovector.iov_base); 319 free(iovector.iov_base);
319 } 320 }
320 321
@@ -380,7 +381,7 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
380 while (outbuf.pos + OT_SCRAPE_MAXENTRYLEN > outbuf.size) { 381 while (outbuf.pos + OT_SCRAPE_MAXENTRYLEN > outbuf.size) {
381 iovector.iov_len = outbuf.size; 382 iovector.iov_len = outbuf.size;
382 383
383 if (mutex_workqueue_pushchunked(taskid, &iovector)) { 384 if (workqueue_pushchunked(taskid, &iovector)) {
384 free(iovector.iov_base); 385 free(iovector.iov_base);
385 ZSTD_freeCCtx(zstream); 386 ZSTD_freeCCtx(zstream);
386 return mutex_bucket_unlock(bucket, 0); 387 return mutex_bucket_unlock(bucket, 0);
@@ -418,7 +419,7 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
418 more_bytes = ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end); 419 more_bytes = ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end);
419 420
420 iovector.iov_len = outbuf.pos; 421 iovector.iov_len = outbuf.pos;
421 if (mutex_workqueue_pushchunked(taskid, &iovector)) { 422 if (workqueue_pushchunked(taskid, &iovector)) {
422 free(iovector.iov_base); 423 free(iovector.iov_base);
423 ZSTD_freeCCtx(zstream); 424 ZSTD_freeCCtx(zstream);
424 return; 425 return;
@@ -443,7 +444,7 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
443 444
444 /* Only pass the new buffer if there actually was some data left in the buffer */ 445 /* Only pass the new buffer if there actually was some data left in the buffer */
445 iovector.iov_len = outbuf.pos; 446 iovector.iov_len = outbuf.pos;
446 if (!iovector.iov_len || mutex_workqueue_pushchunked(taskid, &iovector)) 447 if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector))
447 free(iovector.iov_base); 448 free(iovector.iov_base);
448 } 449 }
449 450
diff --git a/ot_fullscrape.h b/ot_fullscrape.h
index 0e7690c..b759a27 100644
--- a/ot_fullscrape.h
+++ b/ot_fullscrape.h
@@ -9,6 +9,7 @@
9#ifdef WANT_FULLSCRAPE 9#ifdef WANT_FULLSCRAPE
10 10
11#include "ot_mutex.h" 11#include "ot_mutex.h"
12#include "ot_workqueue.h"
12 13
13void fullscrape_init(void); 14void fullscrape_init(void);
14void fullscrape_deinit(void); 15void fullscrape_deinit(void);
diff --git a/ot_http.c b/ot_http.c
index bb6ff24..b0e1de3 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -22,6 +22,7 @@
22#include "ot_iovec.h" 22#include "ot_iovec.h"
23#include "ot_ip.h" 23#include "ot_ip.h"
24#include "ot_mutex.h" 24#include "ot_mutex.h"
25#include "ot_workqueue.h"
25#include "ot_scan.h" 26#include "ot_scan.h"
26#include "ot_event.h" 27#include "ot_event.h"
27#include "ot_fdstate.h" 28#include "ot_fdstate.h"
@@ -39,22 +40,30 @@ ssize_t g_stats_path_len;
39 40
40enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; 41enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 };
41 42
42int http_adopt(const int64_t sock, struct http_data *cookie) { 43struct http_data *http_adopt(const int64_t sock) {
43 struct http_data *heap = malloc(sizeof(*heap)); 44 struct http_data *cookie = fdstate_cookie(sock), *heap;
45 int interest;
44 46
45 if (!heap) 47 if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
46 return -1; 48 return cookie;
49
50 if (!(heap = malloc(sizeof(*heap))))
51 return NULL;
47 52
48 *heap = *cookie; 53 *heap = *cookie;
49 heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; 54 heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK;
55
56 /* Publishing the cookie is what lets another thread reach this connection,
57 so nothing may touch it afterwards, not even to read what it asked for */
58 interest = heap->interest;
50 fdstate_set_cookie((int)sock, heap); 59 fdstate_set_cookie((int)sock, heap);
51 60
52 if (event_add((int)sock, heap->interest, fdstate_generation((int)sock)) == -1) { 61 if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) {
53 fdstate_set_cookie((int)sock, cookie); 62 fdstate_set_cookie((int)sock, cookie);
54 free(heap); 63 free(heap);
55 return -1; 64 return NULL;
56 } 65 }
57 return 0; 66 return heap;
58} 67}
59 68
60/* Give up a connection: the one place that knows everything hanging off it. */ 69/* Give up a connection: the one place that knows everything hanging off it. */
@@ -69,7 +78,7 @@ void http_close(const int64_t sock) {
69 free(cookie->batch); 78 free(cookie->batch);
70 buf_reset(&cookie->request); 79 buf_reset(&cookie->request);
71 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) 80 if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))
72 mutex_workqueue_canceltask(sock); 81 workqueue_canceltask(sock);
73 if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) 82 if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK))
74 free(cookie); 83 free(cookie);
75 } 84 }
@@ -348,13 +357,17 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
348 } 357 }
349#endif 358#endif
350#endif 359#endif
351 /* Pass this task to the worker thread */
352 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; 360 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
353 361
354 /* Clients waiting for us should not easily timeout */ 362 /* Clients waiting for us should not easily timeout */
355 fdstate_set_expiry(sock, 0); 363 fdstate_set_expiry(sock, 0);
356 fullscrape_deliver(sock, format);
357 http_interest_set(sock, cookie, 0); 364 http_interest_set(sock, cookie, 0);
365
366 /* Everything this connection needs has been written, so it can be given
367 a home of its own and only then be made reachable by a worker thread */
368 if (!http_adopt(sock))
369 HTTPERROR_500;
370 fullscrape_deliver(sock, format);
358 return ws->reply_size = -2; 371 return ws->reply_size = -2;
359 } 372 }
360#endif 373#endif
@@ -366,6 +379,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
366 379
367 /* Complex stats also include expensive memory debugging tools */ 380 /* Complex stats also include expensive memory debugging tools */
368 fdstate_set_expiry(sock, 0); 381 fdstate_set_expiry(sock, 0);
382
383 if (!http_adopt(sock))
384 HTTPERROR_500;
369 stats_deliver(sock, mode); 385 stats_deliver(sock, mode);
370 return ws->reply_size = -2; 386 return ws->reply_size = -2;
371 } 387 }
@@ -448,8 +464,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; 464 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
449 /* Clients waiting for us should not easily timeout */ 465 /* Clients waiting for us should not easily timeout */
450 fdstate_set_expiry(sock, 0); 466 fdstate_set_expiry(sock, 0);
451 fullscrape_deliver(sock, format);
452 http_interest_set(sock, cookie, 0); 467 http_interest_set(sock, cookie, 0);
468
469 if (!http_adopt(sock))
470 HTTPERROR_500;
471 fullscrape_deliver(sock, format);
453 return ws->reply_size = -2; 472 return ws->reply_size = -2;
454} 473}
455#endif 474#endif
diff --git a/ot_http.h b/ot_http.h
index 82d645b..9cacd21 100644
--- a/ot_http.h
+++ b/ot_http.h
@@ -40,9 +40,10 @@ void http_interest_set(const int64_t s, struct http_data *cookie, int
40/* Close a connection and release everything that belongs to it */ 40/* Close a connection and release everything that belongs to it */
41void http_close(const int64_t s); 41void http_close(const int64_t s);
42 42
43/* Promote a stack cookie to a heap one for persistence and handing it over 43/* Promote a stack cookie to a heap one, for persistence and for handing it
44 to the event loop. */ 44 over to the event loop or to another thread. Does nothing to one that is
45int http_adopt(const int64_t s, struct http_data *cookie); 45 already on the heap. Returns the connection's state, or NULL. */
46struct http_data *http_adopt(const int64_t s);
46 47
47ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); 48ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws);
48ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); 49ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial);
diff --git a/ot_mutex.c b/ot_mutex.c
index 5392264..d856d5f 100644
--- a/ot_mutex.c
+++ b/ot_mutex.c
@@ -31,7 +31,6 @@ static pthread_mutex_t bucket_mutex[OT_BUCKET_COUNT];
31static _Atomic size_t g_torrent_count; 31static _Atomic size_t g_torrent_count;
32 32
33/* Self pipe from opentracker.c */ 33/* Self pipe from opentracker.c */
34extern int g_self_pipe[2];
35 34
36ot_vector *mutex_bucket_lock(int bucket) { 35ot_vector *mutex_bucket_lock(int bucket) {
37 pthread_mutex_lock(bucket_mutex + bucket); 36 pthread_mutex_lock(bucket_mutex + bucket);
@@ -60,237 +59,17 @@ void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcount) {
60 59
61size_t mutex_get_torrent_count(void) { return atomic_load_explicit(&g_torrent_count, memory_order_relaxed); } 60size_t mutex_get_torrent_count(void) { return atomic_load_explicit(&g_torrent_count, memory_order_relaxed); }
62 61
63/* TaskQueue Magic */
64
65struct ot_task {
66 ot_taskid taskid;
67 ot_tasktype tasktype;
68 int64_t sock;
69 size_t iovec_entries;
70 struct iovec *iovec;
71 struct ot_task *next;
72};
73
74static ot_taskid next_free_taskid = 1;
75static struct ot_task *tasklist;
76static pthread_mutex_t tasklist_mutex;
77static pthread_cond_t tasklist_being_filled;
78
79int mutex_workqueue_pushtask(int64_t sock, ot_tasktype tasktype) {
80 struct ot_task **tmptask, *task;
81
82 task = malloc(sizeof(struct ot_task));
83 if (!task)
84 return -1;
85
86 task->taskid = 0;
87 task->tasktype = tasktype;
88 task->sock = sock;
89 task->iovec_entries = 0;
90 task->iovec = NULL;
91 task->next = 0;
92
93 /* Want exclusive access to tasklist */
94 pthread_mutex_lock(&tasklist_mutex);
95
96 /* Skip to end of list */
97 tmptask = &tasklist;
98 while (*tmptask)
99 tmptask = &(*tmptask)->next;
100 *tmptask = task;
101
102 /* Inform waiting workers and release lock */
103 pthread_cond_broadcast(&tasklist_being_filled);
104 pthread_mutex_unlock(&tasklist_mutex);
105 return 0;
106}
107
108void mutex_workqueue_canceltask(int64_t sock) {
109 struct ot_task **task;
110
111 /* Want exclusive access to tasklist */
112 pthread_mutex_lock(&tasklist_mutex);
113
114 for (task = &tasklist; *task; task = &((*task)->next))
115 if ((*task)->sock == sock) {
116 struct iovec *iovec = (*task)->iovec;
117 struct ot_task *ptask = *task;
118 size_t i;
119
120 /* Free task's iovec, the buffers it points at and the vector itself */
121 for (i = 0; i < (*task)->iovec_entries; ++i)
122 free(iovec[i].iov_base);
123 free(iovec);
124
125 *task = (*task)->next;
126 free(ptask);
127 break;
128 }
129
130 /* Release lock */
131 pthread_mutex_unlock(&tasklist_mutex);
132}
133
134ot_taskid mutex_workqueue_poptask(ot_tasktype *tasktype) {
135 struct ot_task *task;
136 ot_taskid taskid = 0;
137
138 /* Want exclusive access to tasklist */
139 pthread_mutex_lock(&tasklist_mutex);
140
141 while (!taskid) {
142 /* Skip to the first unassigned task this worker wants to do */
143 for (task = tasklist; task; task = task->next)
144 if (!task->taskid && (TASK_CLASS_MASK & task->tasktype) == *tasktype) {
145 /* If we found an outstanding task, assign a taskid to it
146 and leave the loop */
147 task->taskid = taskid = ++next_free_taskid;
148 *tasktype = task->tasktype;
149 break;
150 }
151
152 /* Wait until the next task is being fed, unless the tracker is on its
153 way out, in which case the worker is meant to leave rather than wait */
154 if (!taskid) {
155 if (!g_opentracker_running)
156 break;
157 pthread_cond_wait(&tasklist_being_filled, &tasklist_mutex);
158 }
159 }
160
161 /* Release lock */
162 pthread_mutex_unlock(&tasklist_mutex);
163
164 return taskid;
165}
166
167void mutex_workqueue_pushsuccess(ot_taskid taskid) {
168 struct ot_task **task;
169
170 /* Want exclusive access to tasklist */
171 pthread_mutex_lock(&tasklist_mutex);
172
173 for (task = &tasklist; *task; task = &((*task)->next))
174 if ((*task)->taskid == taskid) {
175 struct ot_task *ptask = *task;
176 *task = (*task)->next;
177 free(ptask);
178 break;
179 }
180
181 /* Release lock */
182 pthread_mutex_unlock(&tasklist_mutex);
183}
184
185int mutex_workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovec) {
186 struct ot_task *task;
187 const char byte = 'o';
188
189 /* Want exclusive access to tasklist */
190 pthread_mutex_lock(&tasklist_mutex);
191
192 for (task = tasklist; task; task = task->next)
193 if (task->taskid == taskid) {
194 task->iovec_entries = iovec_entries;
195 task->iovec = iovec;
196 task->tasktype = TASK_DONE;
197 break;
198 }
199
200 /* Release lock */
201 pthread_mutex_unlock(&tasklist_mutex);
202
203 /* A full pipe has already done this nudge's job, so a failure is fine */
204 (void)!write(g_self_pipe[1], &byte, 1);
205
206 /* Indicate whether the worker has to throw away results */
207 return task ? 0 : -1;
208}
209
210int mutex_workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec) {
211 struct ot_task *task;
212 const char byte = 'o';
213
214 /* Want exclusive access to tasklist */
215 pthread_mutex_lock(&tasklist_mutex);
216
217 for (task = tasklist; task; task = task->next)
218 if (task->taskid == taskid) {
219 if (iovec) {
220 if (iovec_append(&task->iovec_entries, &task->iovec, iovec))
221 task->tasktype = TASK_DONE_PARTIAL;
222 else
223 task = NULL;
224 } else
225 task->tasktype = TASK_DONE;
226 break;
227 }
228
229 /* Release lock */
230 pthread_mutex_unlock(&tasklist_mutex);
231
232 /* A full pipe has already done this nudge's job, so a failure is fine */
233 (void)!write(g_self_pipe[1], &byte, 1);
234
235 /* Indicate whether the worker has to throw away results */
236 return task ? 0 : -1;
237}
238
239int64_t mutex_workqueue_popresult(size_t *iovec_entries, struct iovec **iovec, int *is_partial) {
240 struct ot_task **task;
241 int64_t sock = -1;
242
243 *is_partial = 0;
244
245 /* Want exclusive access to tasklist */
246 pthread_mutex_lock(&tasklist_mutex);
247
248 for (task = &tasklist; *task; task = &((*task)->next))
249 if (((*task)->tasktype & TASK_CLASS_MASK) == TASK_DONE) {
250 struct ot_task *ptask = *task;
251 *iovec_entries = ptask->iovec_entries;
252 *iovec = ptask->iovec;
253 sock = ptask->sock;
254
255 if ((*task)->tasktype == TASK_DONE) {
256 *task = ptask->next;
257 free(ptask);
258 } else {
259 ptask->iovec_entries = 0;
260 ptask->iovec = NULL;
261 *is_partial = 1;
262 /* Prevent task from showing up immediately again unless new data was added */
263 (*task)->tasktype = TASK_FULLSCRAPE;
264 }
265 break;
266 }
267
268 /* Release lock */
269 pthread_mutex_unlock(&tasklist_mutex);
270 return sock;
271}
272
273void mutex_init(void) { 62void mutex_init(void) {
274 size_t i; 63 size_t i;
275 pthread_mutex_init(&tasklist_mutex, NULL);
276 pthread_cond_init(&tasklist_being_filled, NULL);
277 for (i = 0; i < OT_BUCKET_COUNT; ++i) 64 for (i = 0; i < OT_BUCKET_COUNT; ++i)
278 pthread_mutex_init(bucket_mutex + i, NULL); 65 pthread_mutex_init(bucket_mutex + i, NULL);
279 memset(all_torrents, 0, sizeof(all_torrents)); 66 memset(all_torrents, 0, sizeof(all_torrents));
280} 67}
281 68
282/* Let every worker parked on the queue see that the tracker is stopping */ 69/* Let every worker parked on the queue see that the tracker is stopping */
283void mutex_workqueue_wakeall(void) {
284 pthread_mutex_lock(&tasklist_mutex);
285 pthread_cond_broadcast(&tasklist_being_filled);
286 pthread_mutex_unlock(&tasklist_mutex);
287}
288
289void mutex_deinit(void) { 70void mutex_deinit(void) {
290 size_t i; 71 size_t i;
291 for (i = 0; i < OT_BUCKET_COUNT; ++i) 72 for (i = 0; i < OT_BUCKET_COUNT; ++i)
292 pthread_mutex_destroy(bucket_mutex + i); 73 pthread_mutex_destroy(bucket_mutex + i);
293 pthread_mutex_destroy(&tasklist_mutex);
294 pthread_cond_destroy(&tasklist_being_filled);
295 memset(all_torrents, 0, sizeof(all_torrents)); 74 memset(all_torrents, 0, sizeof(all_torrents));
296} 75}
diff --git a/ot_mutex.h b/ot_mutex.h
index c048fdb..c1218d7 100644
--- a/ot_mutex.h
+++ b/ot_mutex.h
@@ -10,7 +10,6 @@
10#include "trackerlogic.h" 10#include "trackerlogic.h"
11 11
12void mutex_init(void); 12void mutex_init(void);
13void mutex_workqueue_wakeall(void);
14void mutex_deinit(void); 13void mutex_deinit(void);
15 14
16ot_vector *mutex_bucket_lock(int bucket); 15ot_vector *mutex_bucket_lock(int bucket);
@@ -21,60 +20,4 @@ void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcoun
21 20
22size_t mutex_get_torrent_count(void); 21size_t mutex_get_torrent_count(void);
23 22
24typedef enum {
25 TASK_STATS_CONNS = 0x0001,
26 TASK_STATS_TCP = 0x0002,
27 TASK_STATS_UDP = 0x0003,
28 TASK_STATS_SCRAPE = 0x0004,
29 TASK_STATS_FULLSCRAPE = 0x0005,
30 TASK_STATS_TPB = 0x0006,
31 TASK_STATS_HTTPERRORS = 0x0007,
32 TASK_STATS_VERSION = 0x0008,
33 TASK_STATS_RENEW = 0x000a,
34 TASK_STATS_SYNCS = 0x000b,
35 TASK_STATS_COMPLETED = 0x000c,
36 TASK_STATS_NUMWANTS = 0x000d,
37
38 TASK_STATS = 0x0100, /* Mask */
39 TASK_STATS_TORRENTS = 0x0101,
40 TASK_STATS_PEERS = 0x0102,
41 TASK_STATS_SLASH24S = 0x0103,
42 TASK_STATS_TOP10 = 0x0104,
43 TASK_STATS_TOP100 = 0x0105,
44 TASK_STATS_EVERYTHING = 0x0106,
45 TASK_STATS_FULLLOG = 0x0107,
46 TASK_STATS_WOODPECKERS = 0x0108,
47
48 TASK_FULLSCRAPE = 0x0200, /* Default mode */
49 TASK_FULLSCRAPE_TPB_BINARY = 0x0201,
50 TASK_FULLSCRAPE_TPB_ASCII = 0x0202,
51 TASK_FULLSCRAPE_TPB_ASCII_PLUS = 0x0203,
52 TASK_FULLSCRAPE_TPB_URLENCODED = 0x0204,
53 TASK_FULLSCRAPE_TRACKERSTATE = 0x0205,
54
55 TASK_DMEM = 0x0300,
56
57 TASK_DONE = 0x0f00,
58 TASK_DONE_PARTIAL = 0x0f01,
59
60 TASK_FLAG_GZIP = 0x1000,
61 TASK_FLAG_BZIP2 = 0x2000,
62 TASK_FLAG_ZSTD = 0x4000,
63 TASK_FLAG_CHUNKED = 0x8000,
64
65 TASK_TASK_MASK = 0x0fff,
66 TASK_CLASS_MASK = 0x0f00,
67 TASK_FLAGS_MASK = 0xf000
68} ot_tasktype;
69
70typedef unsigned long ot_taskid;
71
72int mutex_workqueue_pushtask(int64_t sock, ot_tasktype tasktype);
73void mutex_workqueue_canceltask(int64_t sock);
74void mutex_workqueue_pushsuccess(ot_taskid taskid);
75ot_taskid mutex_workqueue_poptask(ot_tasktype *tasktype);
76int mutex_workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovector);
77int mutex_workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec);
78int64_t mutex_workqueue_popresult(size_t *iovec_entries, struct iovec **iovector, int *is_partial);
79
80#endif 23#endif
diff --git a/ot_stats.c b/ot_stats.c
index 5f97a2f..d38355a 100644
--- a/ot_stats.c
+++ b/ot_stats.c
@@ -23,6 +23,7 @@
23#include "ot_iovec.h" 23#include "ot_iovec.h"
24#include "ot_ip.h" 24#include "ot_ip.h"
25#include "ot_mutex.h" 25#include "ot_mutex.h"
26#include "ot_workqueue.h"
26#include "ot_fmt.h" 27#include "ot_fmt.h"
27#include "ot_stats.h" 28#include "ot_stats.h"
28#include "trackerlogic.h" 29#include "trackerlogic.h"
@@ -803,17 +804,17 @@ static void *stats_worker(void *args) {
803 804
804 while (g_opentracker_running) { 805 while (g_opentracker_running) {
805 ot_tasktype tasktype = TASK_STATS; 806 ot_tasktype tasktype = TASK_STATS;
806 ot_taskid taskid = mutex_workqueue_poptask(&tasktype); 807 ot_taskid taskid = workqueue_poptask(&tasktype);
807 if (!taskid) 808 if (!taskid)
808 break; 809 break;
809 stats_make(&iovec_entries, &iovector, tasktype); 810 stats_make(&iovec_entries, &iovector, tasktype);
810 if (mutex_workqueue_pushresult(taskid, iovec_entries, iovector)) 811 if (workqueue_pushresult(taskid, iovec_entries, iovector))
811 iovec_free(&iovec_entries, &iovector); 812 iovec_free(&iovec_entries, &iovector);
812 } 813 }
813 return NULL; 814 return NULL;
814} 815}
815 816
816void stats_deliver(int64_t sock, int tasktype) { mutex_workqueue_pushtask(sock, tasktype); } 817void stats_deliver(int64_t sock, int tasktype) { workqueue_pushtask(sock, tasktype); }
817 818
818static pthread_t thread_id; 819static pthread_t thread_id;
819void stats_init(void) { 820void stats_init(void) {
@@ -822,6 +823,6 @@ void stats_init(void) {
822} 823}
823 824
824void stats_deinit(void) { 825void stats_deinit(void) {
825 mutex_workqueue_wakeall(); 826 workqueue_wakeall();
826 pthread_join(thread_id, NULL); 827 pthread_join(thread_id, NULL);
827} 828}
diff --git a/ot_sync.c b/ot_sync.c
index 3668aa1..c19cf36 100644
--- a/ot_sync.c
+++ b/ot_sync.c
@@ -14,6 +14,7 @@
14#include "ot_fmt.h" 14#include "ot_fmt.h"
15#include "ot_iovec.h" 15#include "ot_iovec.h"
16#include "ot_mutex.h" 16#include "ot_mutex.h"
17#include "ot_workqueue.h"
17#include "ot_scan.h" 18#include "ot_scan.h"
18#include "ot_stats.h" 19#include "ot_stats.h"
19#include "ot_sync.h" 20#include "ot_sync.h"
@@ -139,10 +140,10 @@ static void *sync_worker(void *args) {
139 140
140 while (1) { 141 while (1) {
141 ot_tasktype tasktype = TASK_SYNC_OUT; 142 ot_tasktype tasktype = TASK_SYNC_OUT;
142 ot_taskid taskid = mutex_workqueue_poptask(&tasktype); 143 ot_taskid taskid = workqueue_poptask(&tasktype);
143 sync_make(&iovec_entries, &iovector); 144 sync_make(&iovec_entries, &iovector);
144 stats_issue_event(ws, EVENT_SYNC_OUT, FLAG_TCP, iovec_length(&iovec_entries, &iovector)); 145 stats_issue_event(ws, EVENT_SYNC_OUT, FLAG_TCP, iovec_length(&iovec_entries, &iovector));
145 if (mutex_workqueue_pushresult(taskid, iovec_entries, iovector)) 146 if (workqueue_pushresult(taskid, iovec_entries, iovector))
146 iovec_free(&iovec_entries, &iovector); 147 iovec_free(&iovec_entries, &iovector);
147 } 148 }
148 return NULL; 149 return NULL;
@@ -158,7 +159,7 @@ void sync_deinit( ) {
158} 159}
159 160
160void sync_deliver( int64_t socket ) { 161void sync_deliver( int64_t socket ) {
161 mutex_workqueue_pushtask( socket, TASK_SYNC_OUT ); 162 workqueue_pushtask( socket, TASK_SYNC_OUT );
162} 163}
163 164
164#endif 165#endif
diff --git a/ot_tcp.c b/ot_tcp.c
new file mode 100644
index 0000000..47697ca
--- /dev/null
+++ b/ot_tcp.c
@@ -0,0 +1,238 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <pthread.h>
9#include <stdatomic.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_batch.h"
17#include "ot_buf.h"
18#include "ot_event.h"
19#include "ot_fdstate.h"
20#include "ot_http.h"
21#include "ot_mutex.h"
22#include "ot_socket.h"
23#include "ot_stats.h"
24#include "ot_tcp.h"
25#include "trackerlogic.h"
26
27static ssize_t header_complete(char *request, ssize_t byte_count) {
28 ssize_t i = 0, state = 0;
29
30 for (i = 1; i < byte_count; i += 2)
31 if (request[i] <= 13) {
32 i--;
33 for (state = 0; i < byte_count; ++i) {
34 char c = request[i];
35 if (c == '\r' || c == '\n')
36 state = (state >> 2) | ((c << 6) & 0xc0);
37 else
38 break;
39 if (state >= 0xa0 || state == 0x99)
40 return i + 1;
41 }
42 }
43 return 0;
44}
45
46void tcp_read(const int64_t sock, struct ot_workstruct *ws) {
47 struct http_data *cookie = fdstate_cookie(sock);
48 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
49
50 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
51 http_close(sock);
52 return;
53 }
54
55 if (byte_count == -1)
56 return;
57
58 ws->request = NULL;
59 ws->request_size = 0;
60 ws->header_size = 0;
61 ws->keep_alive = 0;
62
63 /* If we get the whole request in one packet, handle it without copying */
64 if (!cookie->request.data) {
65 if ((ws->header_size = header_complete(ws->inbuf, byte_count))) {
66 ws->request = ws->inbuf;
67 ws->request_size = byte_count;
68 http_handle_request(sock, ws);
69 } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count))
70 http_close(sock);
71 return;
72 }
73
74 if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) {
75 http_issue_error(sock, ws, CODE_HTTPERROR_500);
76 return;
77 }
78
79 while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) {
80 ws->request = cookie->request.data;
81 ws->request_size = (ssize_t)cookie->request.size;
82 http_handle_request(sock, ws);
83#ifdef WANT_KEEPALIVE
84 if (!ws->keep_alive)
85#endif
86 return;
87 }
88}
89
90void tcp_write(const int64_t sock) {
91 struct http_data *cookie = fdstate_cookie(sock);
92 int chunked = 0;
93 int keep_alive = 0;
94
95 if (cookie) {
96 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
97 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
98
99 if (cookie->batch) {
100 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
101 http_close(sock);
102 return;
103 }
104 if (batch_pending(cookie->batch))
105 return;
106 }
107 }
108
109 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
110 if (chunked)
111 http_interest_set(sock, cookie, 0);
112 else if (keep_alive) {
113 /* The reply is out and the client asked to keep the connection, so go
114 back to waiting for the next request */
115 http_interest_set(sock, cookie, EVENT_IN);
116 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT));
117 } else
118 http_close(sock);
119}
120
121/* The fast path, assuming that the complete request has usually arrived by the time a
122 connection is accepted, so it is answered here outside the event loop. The more load
123 a tracker has, the more likely it is that by the time it accepts, the roundtrip to
124 the peer has completed.
125 If the request could not be completely handled, the stack cookie is promoted to heap
126 and the connection handed over to the event loop to finish */
127void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) {
128 int64_t sock;
129 ot_ip6 ip;
130 uint16_t port;
131
132 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
133 struct http_data cookie;
134 uint32_t generation;
135
136 memset(&cookie, 0, sizeof(cookie));
137 memcpy(cookie.ip, ip, sizeof(ot_ip6));
138 cookie.interest = EVENT_IN;
139 cookie.flag = STRUCT_HTTP_FLAG_ONSTACK;
140
141 generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT));
142 if (!fdstate_claim((int)sock, generation)) {
143 fdstate_close(sock);
144 event_close((int)sock);
145 continue;
146 }
147
148 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
149
150 tcp_read(sock, ws);
151
152 /* Gone already if it was answered and closed inline */
153 if (fdstate_generation((int)sock) != generation)
154 continue;
155
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))
161 http_close(sock);
162 else
163 fdstate_release((int)sock);
164 }
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}
diff --git a/ot_tcp.h b/ot_tcp.h
new file mode 100644
index 0000000..96b6ec1
--- /dev/null
+++ b/ot_tcp.h
@@ -0,0 +1,28 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_TCP_H__
7#define OT_TCP_H__
8
9#include <stdint.h>
10
11#include "trackerlogic.h"
12
13/* Take whatever connections are waiting on a listening socket and answer
14 them, each on this thread's stack, handing a connection that cannot be
15 finished to the event loop */
16void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws);
17
18/* Handler connections in the event loop has become readable or writable */
19void tcp_read(const int64_t sock, struct ot_workstruct *ws);
20void tcp_write(const int64_t sock);
21
22/* One thread accepting on one socket. Called once per worker. */
23void tcp_worker_install(int64_t sock);
24
25/* Stop every worker installed above and wait for it to be gone */
26void tcp_workers_deinit(void);
27
28#endif
diff --git a/ot_udp.c b/ot_udp.c
index 1ec5148..00ec566 100644
--- a/ot_udp.c
+++ b/ot_udp.c
@@ -220,26 +220,17 @@ int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) {
220static void *udp_worker(void *args) { 220static void *udp_worker(void *args) {
221 struct udp_worker *me = args; 221 struct udp_worker *me = args;
222 struct ot_workstruct ws; 222 struct ot_workstruct ws;
223 memset(&ws, 0, sizeof(ws));
224 223
225 stats_counters_register(&ws); 224 if (workstruct_init(&ws) == -1) {
226 ws.inbuf = malloc(G_INBUF_SIZE); 225 fprintf(stderr, "Warning: a udp worker could not get its buffers and will not run.\n");
227 ws.outbuf = malloc(G_OUTBUF_SIZE); 226 atomic_store(&me->left, 1);
228#ifdef _DEBUG_HTTPERROR 227 return NULL;
229 ws.debugbuf = malloc(G_DEBUGBUF_SIZE); 228 }
230#endif
231 229
232 while (g_opentracker_running) 230 while (g_opentracker_running)
233 handle_udp6(me->sock, &ws); 231 handle_udp6(me->sock, &ws);
234 232
235 stats_counters_retire(&ws); 233 workstruct_deinit(&ws);
236
237 free(ws.inbuf);
238 free(ws.outbuf);
239#ifdef _DEBUG_HTTPERROR
240 free(ws.debugbuf);
241#endif
242
243 atomic_store(&me->left, 1); 234 atomic_store(&me->left, 1);
244 return NULL; 235 return NULL;
245} 236}
diff --git a/ot_workqueue.c b/ot_workqueue.c
new file mode 100644
index 0000000..aac366c
--- /dev/null
+++ b/ot_workqueue.c
@@ -0,0 +1,243 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <pthread.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/uio.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_iovec.h"
15#include "ot_workqueue.h"
16#include "trackerlogic.h"
17
18/* Pushing a result wakes the thread that will send it */
19extern int g_self_pipe[2];
20
21struct ot_task {
22 ot_taskid taskid;
23 ot_tasktype tasktype;
24 int64_t sock;
25 size_t iovec_entries;
26 struct iovec *iovec;
27 struct ot_task *next;
28};
29
30static ot_taskid next_free_taskid = 1;
31static struct ot_task *tasklist;
32static pthread_mutex_t tasklist_mutex;
33static pthread_cond_t tasklist_being_filled;
34
35int workqueue_pushtask(int64_t sock, ot_tasktype tasktype) {
36 struct ot_task **tmptask, *task;
37
38 task = malloc(sizeof(struct ot_task));
39 if (!task)
40 return -1;
41
42 task->taskid = 0;
43 task->tasktype = tasktype;
44 task->sock = sock;
45 task->iovec_entries = 0;
46 task->iovec = NULL;
47 task->next = 0;
48
49 /* Want exclusive access to tasklist */
50 pthread_mutex_lock(&tasklist_mutex);
51
52 /* Skip to end of list */
53 tmptask = &tasklist;
54 while (*tmptask)
55 tmptask = &(*tmptask)->next;
56 *tmptask = task;
57
58 /* Inform waiting workers and release lock */
59 pthread_cond_broadcast(&tasklist_being_filled);
60 pthread_mutex_unlock(&tasklist_mutex);
61 return 0;
62}
63
64void workqueue_canceltask(int64_t sock) {
65 struct ot_task **task;
66
67 /* Want exclusive access to tasklist */
68 pthread_mutex_lock(&tasklist_mutex);
69
70 for (task = &tasklist; *task; task = &((*task)->next))
71 if ((*task)->sock == sock) {
72 struct iovec *iovec = (*task)->iovec;
73 struct ot_task *ptask = *task;
74 size_t i;
75
76 /* Free task's iovec, the buffers it points at and the vector itself */
77 for (i = 0; i < (*task)->iovec_entries; ++i)
78 free(iovec[i].iov_base);
79 free(iovec);
80
81 *task = (*task)->next;
82 free(ptask);
83 break;
84 }
85
86 /* Release lock */
87 pthread_mutex_unlock(&tasklist_mutex);
88}
89
90ot_taskid workqueue_poptask(ot_tasktype *tasktype) {
91 struct ot_task *task;
92 ot_taskid taskid = 0;
93
94 /* Want exclusive access to tasklist */
95 pthread_mutex_lock(&tasklist_mutex);
96
97 while (!taskid) {
98 /* Skip to the first unassigned task this worker wants to do */
99 for (task = tasklist; task; task = task->next)
100 if (!task->taskid && (TASK_CLASS_MASK & task->tasktype) == *tasktype) {
101 /* If we found an outstanding task, assign a taskid to it
102 and leave the loop */
103 task->taskid = taskid = ++next_free_taskid;
104 *tasktype = task->tasktype;
105 break;
106 }
107
108 /* Wait until the next task is being fed, unless the tracker is on its
109 way out, in which case the worker is meant to leave rather than wait */
110 if (!taskid) {
111 if (!g_opentracker_running)
112 break;
113 pthread_cond_wait(&tasklist_being_filled, &tasklist_mutex);
114 }
115 }
116
117 /* Release lock */
118 pthread_mutex_unlock(&tasklist_mutex);
119
120 return taskid;
121}
122
123void workqueue_pushsuccess(ot_taskid taskid) {
124 struct ot_task **task;
125
126 /* Want exclusive access to tasklist */
127 pthread_mutex_lock(&tasklist_mutex);
128
129 for (task = &tasklist; *task; task = &((*task)->next))
130 if ((*task)->taskid == taskid) {
131 struct ot_task *ptask = *task;
132 *task = (*task)->next;
133 free(ptask);
134 break;
135 }
136
137 /* Release lock */
138 pthread_mutex_unlock(&tasklist_mutex);
139}
140
141int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovec) {
142 struct ot_task *task;
143 const char byte = 'o';
144
145 /* Want exclusive access to tasklist */
146 pthread_mutex_lock(&tasklist_mutex);
147
148 for (task = tasklist; task; task = task->next)
149 if (task->taskid == taskid) {
150 task->iovec_entries = iovec_entries;
151 task->iovec = iovec;
152 task->tasktype = TASK_DONE;
153 break;
154 }
155
156 /* Release lock */
157 pthread_mutex_unlock(&tasklist_mutex);
158
159 /* A full pipe has already done this nudge's job, so a failure is fine */
160 (void)!write(g_self_pipe[1], &byte, 1);
161
162 /* Indicate whether the worker has to throw away results */
163 return task ? 0 : -1;
164}
165
166int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec) {
167 struct ot_task *task;
168 const char byte = 'o';
169
170 /* Want exclusive access to tasklist */
171 pthread_mutex_lock(&tasklist_mutex);
172
173 for (task = tasklist; task; task = task->next)
174 if (task->taskid == taskid) {
175 if (iovec) {
176 if (iovec_append(&task->iovec_entries, &task->iovec, iovec))
177 task->tasktype = TASK_DONE_PARTIAL;
178 else
179 task = NULL;
180 } else
181 task->tasktype = TASK_DONE;
182 break;
183 }
184
185 /* Release lock */
186 pthread_mutex_unlock(&tasklist_mutex);
187
188 /* A full pipe has already done this nudge's job, so a failure is fine */
189 (void)!write(g_self_pipe[1], &byte, 1);
190
191 /* Indicate whether the worker has to throw away results */
192 return task ? 0 : -1;
193}
194
195int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovec, int *is_partial) {
196 struct ot_task **task;
197 int64_t sock = -1;
198
199 *is_partial = 0;
200
201 /* Want exclusive access to tasklist */
202 pthread_mutex_lock(&tasklist_mutex);
203
204 for (task = &tasklist; *task; task = &((*task)->next))
205 if (((*task)->tasktype & TASK_CLASS_MASK) == TASK_DONE) {
206 struct ot_task *ptask = *task;
207 *iovec_entries = ptask->iovec_entries;
208 *iovec = ptask->iovec;
209 sock = ptask->sock;
210
211 if ((*task)->tasktype == TASK_DONE) {
212 *task = ptask->next;
213 free(ptask);
214 } else {
215 ptask->iovec_entries = 0;
216 ptask->iovec = NULL;
217 *is_partial = 1;
218 /* Prevent task from showing up immediately again unless new data was added */
219 (*task)->tasktype = TASK_FULLSCRAPE;
220 }
221 break;
222 }
223
224 /* Release lock */
225 pthread_mutex_unlock(&tasklist_mutex);
226 return sock;
227}
228
229void workqueue_wakeall(void) {
230 pthread_mutex_lock(&tasklist_mutex);
231 pthread_cond_broadcast(&tasklist_being_filled);
232 pthread_mutex_unlock(&tasklist_mutex);
233}
234
235void workqueue_init(void) {
236 pthread_mutex_init(&tasklist_mutex, NULL);
237 pthread_cond_init(&tasklist_being_filled, NULL);
238}
239
240void workqueue_deinit(void) {
241 pthread_mutex_destroy(&tasklist_mutex);
242 pthread_cond_destroy(&tasklist_being_filled);
243}
diff --git a/ot_workqueue.h b/ot_workqueue.h
new file mode 100644
index 0000000..f99a7b5
--- /dev/null
+++ b/ot_workqueue.h
@@ -0,0 +1,78 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_WORKQUEUE_H__
7#define OT_WORKQUEUE_H__
8
9#include <stdint.h>
10#include <sys/uio.h>
11
12#include "trackerlogic.h"
13
14/* Work the tracker hands to a thread of its own: a full scrape or an
15 expensive stats page, which take too long to build while a client waits.
16 The task goes in here, the reply comes back out. */
17
18typedef enum {
19 TASK_STATS_CONNS = 0x0001,
20 TASK_STATS_TCP = 0x0002,
21 TASK_STATS_UDP = 0x0003,
22 TASK_STATS_SCRAPE = 0x0004,
23 TASK_STATS_FULLSCRAPE = 0x0005,
24 TASK_STATS_TPB = 0x0006,
25 TASK_STATS_HTTPERRORS = 0x0007,
26 TASK_STATS_VERSION = 0x0008,
27 TASK_STATS_RENEW = 0x000a,
28 TASK_STATS_SYNCS = 0x000b,
29 TASK_STATS_COMPLETED = 0x000c,
30 TASK_STATS_NUMWANTS = 0x000d,
31
32 TASK_STATS = 0x0100, /* Mask */
33 TASK_STATS_TORRENTS = 0x0101,
34 TASK_STATS_PEERS = 0x0102,
35 TASK_STATS_SLASH24S = 0x0103,
36 TASK_STATS_TOP10 = 0x0104,
37 TASK_STATS_TOP100 = 0x0105,
38 TASK_STATS_EVERYTHING = 0x0106,
39 TASK_STATS_FULLLOG = 0x0107,
40 TASK_STATS_WOODPECKERS = 0x0108,
41
42 TASK_FULLSCRAPE = 0x0200, /* Default mode */
43 TASK_FULLSCRAPE_TPB_BINARY = 0x0201,
44 TASK_FULLSCRAPE_TPB_ASCII = 0x0202,
45 TASK_FULLSCRAPE_TPB_ASCII_PLUS = 0x0203,
46 TASK_FULLSCRAPE_TPB_URLENCODED = 0x0204,
47 TASK_FULLSCRAPE_TRACKERSTATE = 0x0205,
48
49 TASK_DMEM = 0x0300,
50
51 TASK_DONE = 0x0f00,
52 TASK_DONE_PARTIAL = 0x0f01,
53
54 TASK_FLAG_GZIP = 0x1000,
55 TASK_FLAG_BZIP2 = 0x2000,
56 TASK_FLAG_ZSTD = 0x4000,
57 TASK_FLAG_CHUNKED = 0x8000,
58
59 TASK_TASK_MASK = 0x0fff,
60 TASK_CLASS_MASK = 0x0f00,
61 TASK_FLAGS_MASK = 0xf000
62} ot_tasktype;
63
64typedef unsigned long ot_taskid;
65
66int workqueue_pushtask(int64_t sock, ot_tasktype tasktype);
67void workqueue_canceltask(int64_t sock);
68void workqueue_pushsuccess(ot_taskid taskid);
69ot_taskid workqueue_poptask(ot_tasktype *tasktype);
70int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovector);
71int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec);
72int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovector, int *is_partial);
73
74void workqueue_init(void);
75void workqueue_deinit(void);
76void workqueue_wakeall(void);
77
78#endif
diff --git a/tests/integration/test_tcpworkers.py b/tests/integration/test_tcpworkers.py
new file mode 100644
index 0000000..e43612a
--- /dev/null
+++ b/tests/integration/test_tcpworkers.py
@@ -0,0 +1,67 @@
1"""The tracker with its tcp side handed to worker threads.
2
3With workers configured, a connection is accepted and answered on a worker's
4own stack. Anything it cannot finish there is copied to the heap and handed
5to the event loop, which is the one place where a worker and the main thread
6touch the same structures, so these drive all three outcomes: answered
7inline, held open, and parked waiting for another thread entirely.
8"""
9
10import threading
11import unittest
12import urllib.parse
13
14from harness import TrackerTestCase, bdecode
15
16WORKERS = 4
17
18
19class TcpWorkers(TrackerTestCase):
20 config = "listen.tcp.workers %d\n" % WORKERS
21
22 def announce_path(self, name, port=6881):
23 return "/announce?info_hash=%s&peer_id=%s&port=%d&left=0" % (
24 urllib.parse.quote_from_bytes(self.hash_for(name), safe=""),
25 "x" * 20,
26 port,
27 )
28
29 def test_many_at_once_are_all_answered(self):
30 """Every worker gets something to do, and the swarm adds up."""
31 info_hash = self.hash_for("workers-swarm")
32 errors = []
33
34 def announce(index):
35 try:
36 self.tracker.announce(info_hash, peer_id=b"-OT0001-%012d" % index, port=7000 + index, left=0)
37 except Exception as problem: # noqa: BLE001 - reported, not swallowed
38 errors.append("%d: %r" % (index, problem))
39
40 threads = [threading.Thread(target=announce, args=(i,)) for i in range(32)]
41 for thread in threads:
42 thread.start()
43 for thread in threads:
44 thread.join()
45
46 self.assertEqual(errors, [], "\n".join(errors))
47 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"complete"], 32)
48
49 def test_a_kept_connection_outlives_the_worker_that_took_it(self):
50 """Keep-alive is the plain case of a connection a worker hands on."""
51 connection = self.tracker.connect()
52 try:
53 for i in range(4):
54 connection.send(self.announce_path("workers-kept"))
55 status, _, body = connection.reply()
56 self.assertIn(b"200", status)
57 self.assertIn(b"complete", bdecode(body))
58 finally:
59 connection.close()
60
61 def test_a_full_scrape_asked_for_by_a_worker_still_arrives(self):
62 """The reply is built by another thread, so the worker parks the
63 connection waiting for nothing at all before handing it over."""
64 for i in range(20):
65 self.tracker.announce(bytes([i]) + b"\0" * 19, port=7100 + i, left=0)
66 body = self.tracker.get("/scrape")
67 self.assertEqual(len(bdecode(body)[b"files"]), 20)
diff --git a/tests/runner.c b/tests/runner.c
index 71a5e2a..38bfb86 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,7 +27,7 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
27 {"ot_buf", test_buf}, 27 {"ot_buf", test_buf},
28 {"ot_ip", test_ip}, 28 {"ot_ip", test_ip},
29 {"ot_socket", test_socket}, 29 {"ot_socket", test_socket},
30 {"ot_mutex", test_mutex}, 30 {"ot_workqueue", test_workqueue},
31 {"ot_batch", test_batch}, 31 {"ot_batch", test_batch},
32 {"ot_fdstate", test_fdstate}, 32 {"ot_fdstate", test_fdstate},
33 {"ot_event", test_event}, 33 {"ot_event", test_event},
diff --git a/tests/t_mutex.c b/tests/t_workqueue.c
index 3307d70..13e0b2e 100644
--- a/tests/t_mutex.c
+++ b/tests/t_workqueue.c
@@ -8,7 +8,7 @@
8#include <string.h> 8#include <string.h>
9 9
10/* Opentracker */ 10/* Opentracker */
11#include "ot_mutex.h" 11#include "ot_workqueue.h"
12#include "test.h" 12#include "test.h"
13 13
14/* A worker asks for a class of task and blocks until one turns up, so a task 14/* A worker asks for a class of task and blocks until one turns up, so a task
@@ -20,25 +20,25 @@ static void case_task_goes_to_its_own_class(void) {
20 ot_taskid taskid; 20 ot_taskid taskid;
21 21
22 test_case("a worker is handed the task of the class it asked for"); 22 test_case("a worker is handed the task of the class it asked for");
23 CHECK_INT(mutex_workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0); 23 CHECK_INT(workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0);
24 asked = TASK_FULLSCRAPE; 24 asked = TASK_FULLSCRAPE;
25 taskid = mutex_workqueue_poptask(&asked); 25 taskid = workqueue_poptask(&asked);
26 CHECK(taskid != 0); 26 CHECK(taskid != 0);
27 27
28 test_case("and the full task type, not just its class"); 28 test_case("and the full task type, not just its class");
29 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII); 29 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII);
30 mutex_workqueue_canceltask(41); 30 workqueue_canceltask(41);
31} 31}
32 32
33static void case_flags_survive_the_queue(void) { 33static void case_flags_survive_the_queue(void) {
34 ot_tasktype asked; 34 ot_tasktype asked;
35 35
36 test_case("flags outside the class mask are carried through"); 36 test_case("flags outside the class mask are carried through");
37 CHECK_INT(mutex_workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0); 37 CHECK_INT(workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0);
38 asked = TASK_FULLSCRAPE; 38 asked = TASK_FULLSCRAPE;
39 CHECK(mutex_workqueue_poptask(&asked) != 0); 39 CHECK(workqueue_poptask(&asked) != 0);
40 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP); 40 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP);
41 mutex_workqueue_canceltask(42); 41 workqueue_canceltask(42);
42} 42}
43 43
44static void case_classes_do_not_cross(void) { 44static void case_classes_do_not_cross(void) {
@@ -46,22 +46,22 @@ static void case_classes_do_not_cross(void) {
46 ot_taskid taskid; 46 ot_taskid taskid;
47 47
48 test_case("a stats worker passes over a fullscrape task"); 48 test_case("a stats worker passes over a fullscrape task");
49 CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0); 49 CHECK_INT(workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0);
50 CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0); 50 CHECK_INT(workqueue_pushtask(44, TASK_STATS_PEERS), 0);
51 51
52 asked = TASK_STATS; 52 asked = TASK_STATS;
53 taskid = mutex_workqueue_poptask(&asked); 53 taskid = workqueue_poptask(&asked);
54 CHECK(taskid != 0); 54 CHECK(taskid != 0);
55 CHECK_INT(asked, TASK_STATS_PEERS); 55 CHECK_INT(asked, TASK_STATS_PEERS);
56 56
57 test_case("and leaves it for the one that wants it"); 57 test_case("and leaves it for the one that wants it");
58 asked = TASK_FULLSCRAPE; 58 asked = TASK_FULLSCRAPE;
59 taskid = mutex_workqueue_poptask(&asked); 59 taskid = workqueue_poptask(&asked);
60 CHECK(taskid != 0); 60 CHECK(taskid != 0);
61 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY); 61 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY);
62 62
63 mutex_workqueue_canceltask(43); 63 workqueue_canceltask(43);
64 mutex_workqueue_canceltask(44); 64 workqueue_canceltask(44);
65} 65}
66 66
67static void case_every_task_gets_its_own_id(void) { 67static void case_every_task_gets_its_own_id(void) {
@@ -69,36 +69,36 @@ static void case_every_task_gets_its_own_id(void) {
69 ot_taskid first, second; 69 ot_taskid first, second;
70 70
71 test_case("two queued tasks are handed out under different ids"); 71 test_case("two queued tasks are handed out under different ids");
72 CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0); 72 CHECK_INT(workqueue_pushtask(45, TASK_FULLSCRAPE), 0);
73 CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0); 73 CHECK_INT(workqueue_pushtask(46, TASK_FULLSCRAPE), 0);
74 74
75 asked = TASK_FULLSCRAPE; 75 asked = TASK_FULLSCRAPE;
76 first = mutex_workqueue_poptask(&asked); 76 first = workqueue_poptask(&asked);
77 asked = TASK_FULLSCRAPE; 77 asked = TASK_FULLSCRAPE;
78 second = mutex_workqueue_poptask(&asked); 78 second = workqueue_poptask(&asked);
79 CHECK(first != 0); 79 CHECK(first != 0);
80 CHECK(second != 0); 80 CHECK(second != 0);
81 CHECK(first != second); 81 CHECK(first != second);
82 82
83 mutex_workqueue_canceltask(45); 83 workqueue_canceltask(45);
84 mutex_workqueue_canceltask(46); 84 workqueue_canceltask(46);
85} 85}
86 86
87static void case_cancelled_task_is_not_handed_out(void) { 87static void case_cancelled_task_is_not_handed_out(void) {
88 ot_tasktype asked; 88 ot_tasktype asked;
89 89
90 test_case("a cancelled task is gone before any worker sees it"); 90 test_case("a cancelled task is gone before any worker sees it");
91 CHECK_INT(mutex_workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0); 91 CHECK_INT(workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0);
92 CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0); 92 CHECK_INT(workqueue_pushtask(48, TASK_FULLSCRAPE), 0);
93 mutex_workqueue_canceltask(47); 93 workqueue_canceltask(47);
94 94
95 asked = TASK_FULLSCRAPE; 95 asked = TASK_FULLSCRAPE;
96 CHECK(mutex_workqueue_poptask(&asked) != 0); 96 CHECK(workqueue_poptask(&asked) != 0);
97 CHECK_INT(asked, TASK_FULLSCRAPE); 97 CHECK_INT(asked, TASK_FULLSCRAPE);
98 mutex_workqueue_canceltask(48); 98 workqueue_canceltask(48);
99} 99}
100 100
101void test_mutex(void) { 101void test_workqueue(void) {
102 case_task_goes_to_its_own_class(); 102 case_task_goes_to_its_own_class();
103 case_flags_survive_the_queue(); 103 case_flags_survive_the_queue();
104 case_classes_do_not_cross(); 104 case_classes_do_not_cross();
diff --git a/tests/test.h b/tests/test.h
index 0eddc82..d58742c 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,7 +86,7 @@ void test_scan(void);
86void test_buf(void); 86void test_buf(void);
87void test_ip(void); 87void test_ip(void);
88void test_socket(void); 88void test_socket(void);
89void test_mutex(void); 89void test_workqueue(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void); 92void test_event(void);
diff --git a/trackerlogic.c b/trackerlogic.c
index 477c4a6..b0b7f4e 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -20,6 +20,7 @@
20#include "ot_ip.h" 20#include "ot_ip.h"
21#include "ot_livesync.h" 21#include "ot_livesync.h"
22#include "ot_mutex.h" 22#include "ot_mutex.h"
23#include "ot_workqueue.h"
23#include "ot_random.h" 24#include "ot_random.h"
24#include "ot_stats.h" 25#include "ot_stats.h"
25#include "ot_udp.h" 26#include "ot_udp.h"
@@ -560,6 +561,7 @@ void trackerlogic_init(void) {
560 561
561 /* Initialise background worker threads */ 562 /* Initialise background worker threads */
562 mutex_init(); 563 mutex_init();
564 workqueue_init();
563 clean_init(); 565 clean_init();
564 fullscrape_init(); 566 fullscrape_init();
565 accesslist_init(); 567 accesslist_init();
@@ -567,6 +569,35 @@ void trackerlogic_init(void) {
567 stats_init(); 569 stats_init();
568} 570}
569 571
572int workstruct_init(struct ot_workstruct *ws) {
573 memset(ws, 0, sizeof(*ws));
574 ws->inbuf = malloc(G_INBUF_SIZE);
575 ws->outbuf = malloc(G_OUTBUF_SIZE);
576#ifdef _DEBUG_HTTPERROR
577 ws->debugbuf = malloc(G_DEBUGBUF_SIZE);
578#endif
579 if (!ws->inbuf || !ws->outbuf)
580 return -1;
581
582 /* Picking which peers to hand out starts here, so two workers seeded alike
583 would hand out alike */
584 random_bytes(ws->rand48_state, sizeof(ws->rand48_state));
585
586 stats_counters_register(ws);
587 return 0;
588}
589
590void workstruct_deinit(struct ot_workstruct *ws) {
591 /* The tally goes to the global before this struct goes out of scope */
592 stats_counters_retire(ws);
593
594 free(ws->inbuf);
595 free(ws->outbuf);
596#ifdef _DEBUG_HTTPERROR
597 free(ws->debugbuf);
598#endif
599}
600
570void trackerlogic_deinit(void) { 601void trackerlogic_deinit(void) {
571 int delta_torrentcount = 0; 602 int delta_torrentcount = 0;
572 size_t bucket, j; 603 size_t bucket, j;
@@ -594,5 +625,6 @@ void trackerlogic_deinit(void) {
594 } 625 }
595 626
596 /* Release mutexes */ 627 /* Release mutexes */
628 workqueue_deinit();
597 mutex_deinit(); 629 mutex_deinit();
598} 630}
diff --git a/trackerlogic.h b/trackerlogic.h
index d269267..53edabd 100644
--- a/trackerlogic.h
+++ b/trackerlogic.h
@@ -236,6 +236,12 @@ struct ot_workstruct {
236 int keep_alive; 236 int keep_alive;
237}; 237};
238 238
239/* Everything a thread that serves requests needs for itself. Set up and torn
240 down in one place so that a new kind of worker cannot miss a piece.
241 Returns -1 if the buffers could not be had. */
242int workstruct_init(struct ot_workstruct *ws);
243void workstruct_deinit(struct ot_workstruct *ws);
244
239/* 245/*
240 Exported functions 246 Exported functions
241*/ 247*/