summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-06 03:01:46 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-06 03:01:46 +0100
commit8bae93ce181086769175e5c3fb0f0ef66fb9ca89 (patch)
treeda86b766bd37f1a71d0847fd3ebbd93b1f95ec76
parent2cecc9da20afb55613589dafc854e9f4d1a74069 (diff)
Stop reading a connection a worker has taken over
-rw-r--r--src/ot_http.c4
-rw-r--r--src/ot_tcp.c5
-rw-r--r--src/trackerlogic.h3
-rw-r--r--tests/integration/test_keepalive.py19
4 files changed, 31 insertions, 0 deletions
diff --git a/src/ot_http.c b/src/ot_http.c
index e6a8db7..29f41a3 100644
--- a/src/ot_http.c
+++ b/src/ot_http.c
@@ -367,6 +367,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
367 a home of its own and only then be made reachable by a worker thread */ 367 a home of its own and only then be made reachable by a worker thread */
368 if (!http_adopt(sock)) 368 if (!http_adopt(sock))
369 HTTPERROR_500; 369 HTTPERROR_500;
370 ws->handed_off = 1;
370 fullscrape_deliver(sock, format); 371 fullscrape_deliver(sock, format);
371 return ws->reply_size = -2; 372 return ws->reply_size = -2;
372 } 373 }
@@ -383,6 +384,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
383 384
384 if (!http_adopt(sock)) 385 if (!http_adopt(sock))
385 HTTPERROR_500; 386 HTTPERROR_500;
387 ws->handed_off = 1;
386 stats_deliver(sock, mode); 388 stats_deliver(sock, mode);
387 return ws->reply_size = -2; 389 return ws->reply_size = -2;
388 } 390 }
@@ -477,6 +479,7 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
477 479
478 if (!http_adopt(sock)) 480 if (!http_adopt(sock))
479 HTTPERROR_500; 481 HTTPERROR_500;
482 ws->handed_off = 1;
480 fullscrape_deliver(sock, format); 483 fullscrape_deliver(sock, format);
481 return ws->reply_size = -2; 484 return ws->reply_size = -2;
482} 485}
@@ -742,6 +745,7 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
742 subroutine gets to answer: an error reply finishes with the connection 745 subroutine gets to answer: an error reply finishes with the connection
743 on its way out, and ws outlives the connection it was serving */ 746 on its way out, and ws outlives the connection it was serving */
744 ws->keep_alive = 0; 747 ws->keep_alive = 0;
748 ws->handed_off = 0;
745#ifdef WANT_KEEPALIVE 749#ifdef WANT_KEEPALIVE
746 if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k')) 750 if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k'))
747 ws->keep_alive = 1; 751 ws->keep_alive = 1;
diff --git a/src/ot_tcp.c b/src/ot_tcp.c
index 1351b92..f93795b 100644
--- a/src/ot_tcp.c
+++ b/src/ot_tcp.c
@@ -79,6 +79,11 @@ void tcp_read(const int64_t sock, struct ot_workstruct *ws) {
79 ws->request = cookie->request.data; 79 ws->request = cookie->request.data;
80 ws->request_size = (ssize_t)cookie->request.size; 80 ws->request_size = (ssize_t)cookie->request.size;
81 http_handle_request(sock, ws); 81 http_handle_request(sock, ws);
82
83 /* A worker owns the connection now, and the buffer this loop reads hangs
84 off it, so there is nothing here left to look at */
85 if (ws->handed_off)
86 return;
82#ifdef WANT_KEEPALIVE 87#ifdef WANT_KEEPALIVE
83 if (!ws->keep_alive) 88 if (!ws->keep_alive)
84#endif 89#endif
diff --git a/src/trackerlogic.h b/src/trackerlogic.h
index 4f970ba..d03d3b1 100644
--- a/src/trackerlogic.h
+++ b/src/trackerlogic.h
@@ -256,6 +256,9 @@ struct ot_workstruct {
256 uint16_t rand48_state[3]; 256 uint16_t rand48_state[3];
257 257
258 int keep_alive; 258 int keep_alive;
259
260 /* Set when a request path gave the connection to a worker thread. */
261 int handed_off;
259}; 262};
260 263
261/* Everything a thread that serves requests needs for itself. Set up and torn 264/* Everything a thread that serves requests needs for itself. Set up and torn
diff --git a/tests/integration/test_keepalive.py b/tests/integration/test_keepalive.py
index 01f02a5..2c724fc 100644
--- a/tests/integration/test_keepalive.py
+++ b/tests/integration/test_keepalive.py
@@ -7,6 +7,7 @@ the request has moved on, so whether to keep the connection has to be
7remembered on the connection itself. 7remembered on the connection itself.
8""" 8"""
9 9
10import time
10import unittest 11import unittest
11import urllib.parse 12import urllib.parse
12 13
@@ -106,6 +107,24 @@ class KeepAlive(TrackerTestCase):
106 finally: 107 finally:
107 c.close() 108 c.close()
108 109
110 def test_a_split_request_answered_by_a_worker_does_not_spin(self):
111 """A reply that a worker produces does not go through the send path,
112 so the request stays in the buffer the read loop is reading. The loop
113 used to find the same request again on every pass, queue a task for
114 it, and never hand the thread back to the event loop."""
115 for path in ("/stats", "/scrape"):
116 c = self.tracker.connect()
117 try:
118 c.sock.sendall(("GET %s HTTP/1.1\r\nConnection: keep-alive\r\n" % path).encode())
119 time.sleep(0.2)
120 c.sock.sendall(b"\r\n")
121 self.assertIn(b"200", c.reply()[0], "%s was not answered" % path)
122 finally:
123 c.close()
124
125 answer = self.tracker.announce(self.hash_for("after-" + path), port=6881, left=0)
126 self.assertNotIn(b"failure reason", answer, "the tracker stopped serving after %s" % path)
127
109 128
110if __name__ == "__main__": 129if __name__ == "__main__":
111 unittest.main() 130 unittest.main()