diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -174,6 +174,11 @@ Tell the :meth:`serve_forever` loop to stop and wait until it does. +.. attribute:: BaseServer.running + + True if the server is running. + + .. versionadded:: 3.3 .. attribute:: BaseServer.address_family diff --git a/Lib/socketserver.py b/Lib/socketserver.py --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -200,6 +200,22 @@ self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False + self.__running = False + + def __repr__(self): + info = [] + name = self.__class__.__module__ + "." + self.__class__.__name__ + info.append("status=%s" % ("running" if self.running else "stopped")) + if self.server_address: + info.append("address=%s" % repr(self.server_address)) + return '<%s %s at %#x>' % (name, ', '.join(info), id(self)) + + __str__ = __repr__ + + @property + def running(self): + """Return True if the server is running.""" + return self.__running def server_activate(self): """Called by constructor to activate the server. @@ -216,6 +232,9 @@ self.timeout. If you need to do periodic tasks, do them in another thread. """ + if self.running: + raise RuntimeError("the server is already running") + self.__running = True self.__is_shut_down.clear() try: while not self.__shutdown_request: @@ -230,6 +249,7 @@ self.service_actions() finally: self.__shutdown_request = False + self.__running = False self.__is_shut_down.set() def shutdown(self): @@ -239,6 +259,8 @@ serve_forever() is running in another thread, or it will deadlock. """ + if not self.running: + raise RuntimeError("the server is not running") self.__shutdown_request = True self.__is_shut_down.wait() diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -11,6 +11,7 @@ import tempfile import unittest import socketserver +import time import test.support from test.support import reap_children, reap_threads, verbose @@ -265,13 +266,41 @@ kwargs={'poll_interval':0.01}) t.daemon = True # In case this function raises. threads.append((t, s)) + for t, s in threads: t.start() + for t, s in threads: s.shutdown() for t, s in threads: t.join() s.server_close() + def test_running(self): + class MyServer(socketserver.TCPServer): + pass + + class MyHandler(socketserver.StreamRequestHandler): + pass + + s = MyServer((HOST, 0), MyHandler) + self.assertFalse(s.running) + with self.assertRaises(RuntimeError) as cm: + s.shutdown() + self.assertIn('not running', str(cm.exception)) + + t = threading.Thread(target=s.serve_forever, + kwargs={'poll_interval':0.01}) + t.start() + time.sleep(.1) + self.assertTrue(s.running) + try: + with self.assertRaises(RuntimeError) as cm: + s.serve_forever() + self.assertIn('already running', str(cm.exception)) + finally: + s.shutdown() + s.server_close() + def test_main(): if imp.lock_held():