Skip to content

Commit 57f0822

Browse files
committed
bpo-35017: BaseServer should not accept any request after shutdown (GH-9952)
Prior to this revision, After the shutdown of a `BaseServer`, the server accepted a last single request if it was sent between the server socket polling and the polling timeout. This can be problematic for instance for a server restart for which you do not want to interrupt the service, by not closing the listening socket during the restart. One request failed because of this behavior. Note that only one request failed, following requests were not accepted, as expected.
1 parent e890421 commit 57f0822

File tree

3 files changed

+37
-0
lines changed

3 files changed

+37
-0
lines changed

Lib/socketserver.py

+4
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ def serve_forever(self, poll_interval=0.5):
230230

231231
while not self.__shutdown_request:
232232
ready = selector.select(poll_interval)
233+
# bpo-35017: request arriving after the server shutdown
234+
# and before the poll_interval timeout should not be accepted.
235+
if self.__shutdown_request:
236+
break
233237
if ready:
234238
self._handle_request_noblock()
235239

Lib/test/test_socketserver.py

+31
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,37 @@ def shutdown_request(self, request):
492492
self.assertEqual(server.shutdown_called, 1)
493493
server.server_close()
494494

495+
def test_no_accept_after_shutdown(self):
496+
rejected = False
497+
s = socketserver.TCPServer((HOST, 0), socketserver.StreamRequestHandler)
498+
499+
thread_serve = threading.Thread(
500+
name='MyServer serving',
501+
target=s.serve_forever)
502+
thread_serve.daemon = True # In case this function raises.
503+
thread_serve.start()
504+
505+
thread_shutdown = threading.Thread(
506+
name='MyServer shutdown',
507+
target=s.shutdown)
508+
thread_shutdown.daemon = True # In case this function raises.
509+
thread_shutdown.start()
510+
511+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
512+
sock.settimeout(0.1)
513+
sock.connect(s.server_address)
514+
sock.send(b"Request after shutdown\n")
515+
try:
516+
sock.recv(1024)
517+
except socket.timeout:
518+
rejected = True
519+
sock.close()
520+
521+
for t in [thread_serve, thread_shutdown]:
522+
t.join()
523+
s.server_close()
524+
self.assertEqual(rejected, True)
525+
495526

496527
if __name__ == "__main__":
497528
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
After shutdown, :class:`BaseServer` accepted a last request if it was sent
2+
between the socket polling and the polling timeout.

0 commit comments

Comments
 (0)