1616import collections
1717import collections .abc
1818import concurrent .futures
19+ import enum
1920import errno
2021import heapq
2122import itertools
@@ -272,6 +273,23 @@ async def restore(self):
272273 self ._proto .resume_writing ()
273274
274275
276+ class _ServerState (enum .Enum ):
277+ """This tracks the state of Server.
278+
279+ -[in]->INITIALIZED -[ss]-> SERVING -[cl]-> CLOSED -[wk]*-> SHUTDOWN
280+
281+ - in: Server.__init__()
282+ - ss: Server._start_serving()
283+ - cl: Server.close()
284+ - wk: Server._wakeup() *only called if number of clients == 0
285+ """
286+
287+ INITIALIZED = "initialized"
288+ SERVING = "serving"
289+ CLOSED = "closed"
290+ SHUTDOWN = "shutdown"
291+
292+
275293class Server (events .AbstractServer ):
276294
277295 def __init__ (self , loop , sockets , protocol_factory , ssl_context , backlog ,
@@ -287,32 +305,49 @@ def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog,
287305 self ._ssl_context = ssl_context
288306 self ._ssl_handshake_timeout = ssl_handshake_timeout
289307 self ._ssl_shutdown_timeout = ssl_shutdown_timeout
290- self ._serving = False
308+ self ._state = _ServerState . INITIALIZED
291309 self ._serving_forever_fut = None
292310
293311 def __repr__ (self ):
294312 return f'<{ self .__class__ .__name__ } sockets={ self .sockets !r} >'
295313
296314 def _attach (self , transport ):
297- assert self ._sockets is not None
315+ if self ._state != _ServerState .SERVING :
316+ raise RuntimeError ("server is not serving, cannot attach transport" )
298317 self ._clients .add (transport )
299318
300319 def _detach (self , transport ):
301320 self ._clients .discard (transport )
302- if len ( self ._clients ) == 0 and self ._sockets is None :
321+ if self ._state == _ServerState . CLOSED and len ( self ._clients ) == 0 :
303322 self ._wakeup ()
304323
305324 def _wakeup (self ):
325+ match self ._state :
326+ case _ServerState .SHUTDOWN :
327+ # gh109564: the wakeup method has two possible call-sites,
328+ # through an explicit call Server.close(), or indirectly through
329+ # Server._detach() by the last connected client.
330+ return
331+ case _ServerState .INITIALIZED | _ServerState .SERVING :
332+ raise RuntimeError ("cannot wakeup server before closing" )
333+ case _ServerState .CLOSED :
334+ self ._state = _ServerState .SHUTDOWN
335+
306336 waiters = self ._waiters
307337 self ._waiters = None
308338 for waiter in waiters :
309339 if not waiter .done ():
310340 waiter .set_result (None )
311341
312342 def _start_serving (self ):
313- if self ._serving :
314- return
315- self ._serving = True
343+ match self ._state :
344+ case _ServerState .SERVING :
345+ return
346+ case _ServerState .CLOSED | _ServerState .SHUTDOWN :
347+ raise RuntimeError (f'server { self !r} is closed' )
348+ case _ServerState .INITIALIZED :
349+ self ._state = _ServerState .SERVING
350+
316351 for sock in self ._sockets :
317352 sock .listen (self ._backlog )
318353 self ._loop ._start_serving (
@@ -324,7 +359,7 @@ def get_loop(self):
324359 return self ._loop
325360
326361 def is_serving (self ):
327- return self ._serving
362+ return self ._state == _ServerState . SERVING
328363
329364 @property
330365 def sockets (self ):
@@ -333,6 +368,13 @@ def sockets(self):
333368 return tuple (trsock .TransportSocket (s ) for s in self ._sockets )
334369
335370 def close (self ):
371+ match self ._state :
372+ case _ServerState .CLOSED | _ServerState .SHUTDOWN :
373+ # Shutdown state can only be reached after closing.
374+ return
375+ case _:
376+ self ._state = _ServerState .CLOSED
377+
336378 sockets = self ._sockets
337379 if sockets is None :
338380 return
@@ -341,8 +383,6 @@ def close(self):
341383 for sock in sockets :
342384 self ._loop ._stop_serving (sock )
343385
344- self ._serving = False
345-
346386 if (self ._serving_forever_fut is not None and
347387 not self ._serving_forever_fut .done ()):
348388 self ._serving_forever_fut .cancel ()
@@ -369,8 +409,6 @@ async def serve_forever(self):
369409 if self ._serving_forever_fut is not None :
370410 raise RuntimeError (
371411 f'server { self !r} is already being awaited on serve_forever()' )
372- if self ._sockets is None :
373- raise RuntimeError (f'server { self !r} is closed' )
374412
375413 self ._start_serving ()
376414 self ._serving_forever_fut = self ._loop .create_future ()
@@ -407,7 +445,7 @@ async def wait_closed(self):
407445 # from two places: self.close() and self._detach(), but only
408446 # when both conditions have become true. To signal that this
409447 # has happened, self._wakeup() sets self._waiters to None.
410- if self ._waiters is None :
448+ if self ._state == _ServerState . SHUTDOWN :
411449 return
412450 waiter = self ._loop .create_future ()
413451 self ._waiters .append (waiter )
0 commit comments