@@ -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