Skip to content

Commit f6a3c1b

Browse files
committed
Fix race condition by waiting for server ready state on startup
1 parent 5ed26be commit f6a3c1b

1 file changed

Lines changed: 19 additions & 0 deletions

File tree

pytest_httpserver/httpserver.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ def __init__(
650650
self.ssl_context = ssl_context
651651
self.threaded = threaded
652652
self.no_handler_status_code = 500
653+
self._server_ready_event: threading.Event = threading.Event()
653654

654655
def __repr__(self):
655656
return f"<{self.__class__.__name__} host={self.host} port={self.port}>"
@@ -730,8 +731,12 @@ def thread_target(self):
730731
This method serves as a thread target when the server is started.
731732
732733
This should not be called directly, but can be overridden to tailor it to your needs.
734+
735+
If overriding, you must call ``self._server_ready_event.set()`` before starting
736+
to serve requests, otherwise :py:meth:`start` will raise an error after timeout.
733737
"""
734738
assert self.server is not None
739+
self._server_ready_event.set()
735740
self.server.serve_forever()
736741

737742
def is_running(self) -> bool:
@@ -771,8 +776,22 @@ def start(self) -> None:
771776

772777
self.port = self.server.port # Update port (needed if `port` was set to 0)
773778
# Explicitly make the new thread daemonic to avoid shutdown issues
779+
self._server_ready_event.clear()
774780
self.server_thread = threading.Thread(target=self.thread_target, daemon=True)
775781
self.server_thread.start()
782+
if not self._server_ready_event.wait(timeout=10):
783+
# Clean up the server before raising.
784+
# Use server_close() instead of shutdown() to avoid deadlock
785+
# if serve_forever() was never called.
786+
self.server.server_close()
787+
self.server_thread.join(timeout=5)
788+
self.server = None
789+
self.server_thread = None
790+
raise HTTPServerError(
791+
"Server did not start within timeout. "
792+
"If you override thread_target(), ensure it calls "
793+
"self._server_ready_event.set() before serving."
794+
)
776795

777796
def stop(self):
778797
"""

0 commit comments

Comments
 (0)