Question: As part of learning python and asyncio I have a simple TCP client/server architecture using asyncio (I have reasons why I need to use that) where I want the server to completely exit when it receives the string 'quit' from the client. The server, stored in asyncio_server.py, looks like this: import socket import asyncio class Server: def __init__(self, host, port): self.host = host self.port = port async def handle_client(self, reader, writer): # Callback from asyncio.start_server() when # a client tries to establish a connection addr = writer.get_extra_info('peername') print(f'Accepted connection from {addr!r}') request = None try: while request != 'quit': request = (await reader.read(255)).decode('utf8') print(f"Received {request!r} from {addr!r}") response = 'ok' writer.write(response.encode('utf8')) await writer.drain() print(f'Sent {response!r} to {addr!r}') print('----------') except Exception as e: print(f"Error handling client {addr!r}: {e}") finally: print(f"Connection closed by {addr!r}") writer.close() await writer.wait_closed() print(f'Closed the connection with {addr!r}') asyncio.get_event_loop().stop() # <<< WHAT SHOULD THIS BE? async def start(self): server = await asyncio.start_server(self.handle_client, self.host, self.port) async with server: print(f"Serving on {self.host}:{self.port}") await server.serve_forever() async def main(): server = Server(socket.gethostname(), 5000) await server.start() if __name__ == '__main__': asyncio.run(main())  and when the client sends quit the connection is closed and the server exits but always with the error message: Traceback (most recent call last): File "asyncio_server.py", line 54, in <module> asyncio.run(main()) File "C:\Python38-32\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Python38-32\lib\asyncio\base_events.py", line 614, in run_until_complete raise RuntimeError('Event loop stopped before Future completed.') RuntimeError: Event loop stopped before Future completed.  What do I need to do instead of or in addition to calling asyncio.get_event_loop().stop() and/or server.serve_forever() to have the server exit gracefully with no error messages? I've tried every alternative I can find with google, including calling cancel() on the server, using separate loop construct in main(), trying alternatives to stop(), alternatives to run_forever(), etc., etc. and cannot figure out what I'm supposed to do to gracefully stop the server and exit the program without error messages when it receives a quit message from the client. I'm using python 3.8.10 and cannot upgrade to a newer version due to managed environment constraints. I'm also calling python from git bash in case that matters.  Additional Information: The client code, stored in asyncio_client.py`, is below in case that's useful. import socket import asyncio class Client: def __init__(self, host, port): self.host = host self.port = port async def handle_server(self, reader, writer): addr = writer.get_extra_info('peername') print(f'Connected to from {addr!r}') request = input('Enter Request: ') while request.lower().strip() != 'quit': writer.write(request.encode()) await writer.drain() print(f'Sent {request!r} to {addr!r}') response = await reader.read(1024) print(f'Received {response.decode()!r} from {addr!r}') print('----------') request = input('Enter Request: ') writer.write(request.encode()) await writer.drain() print(f'Sent {request!r} to {addr!r}') writer.close() await writer.wait_closed() print(f'Closed the connection with {addr!r}') async def start(self): reader, writer = await asyncio.open_connection(host=self.host, port=self.port) await self.handle_server(reader, writer) async def main(): client = Client(socket.gethostname(), 5000) await client.start() if __name__ == '__main__': asyncio.run(main()) 

Answer: You can use an asyncio.Event to set when the QUIT message arrives. Then have asyncio wait for the either server_forever() or the Event to complete first. Once the Event is set, call the .close() method and stop the server. In the code below I assigned the server to an attribute. import socket import asyncio class Server: def __init__(self, host, port): self.host = host self.port = port self._server = None self._shutdown_event = asyncio.Event() async def handle_client(self, reader, writer): addr = writer.get_extra_info('peername') print(f'Accepted connection from {addr!r}') request = None try: while request != 'quit': request = (await reader.read(255)).decode('utf8') print(f"Received {request!r} from {addr!r}") writer.write(b'ok') await writer.drain() print(f'Sent "ok" to {addr!r}') print('----------') if request == 'quit': self._shutdown_event.set() # Signal shutdown await writer.drain() # Try to drain before closing break # exit the loop except Exception as e: print(f"Error handling client {addr!r}: {e}") finally: print(f"Connection closed by {addr!r}") writer.close() await writer.wait_closed() print(f'Closed the connection with {addr!r}') async def start(self): self._server = await asyncio.start_server(self.handle_client, self.host, self.port) async with self._server: print(f"Serving on {self.host}:{self.port}") await asyncio.wait( [ self._server.serve_forever(), self._shutdown_event.wait() ], return_when=asyncio.FIRST_COMPLETED ) await self.stop() async def stop(self): if self._server: self._server.close() await self._server.wait_closed() print("Server stopped.") async def main(): server = Server(socket.gethostname(), 5000) try: await server.start() except asyncio.CancelledError: print("Server task was cancelled.") except Exception as e: print(f"An error occurred: {e}") if __name__ == '__main__': asyncio.run(main())  Also, your client code is not reading the final message sent by the server after a quit is received. It is a small addition: import socket import asyncio class Client: def __init__(self, host, port): self.host = host self.port = port async def handle_server(self, reader, writer): addr = writer.get_extra_info('peername') print(f'Connected to from {addr!r}') request = input('Enter Request: ') while request.lower().strip() != 'quit': writer.write(request.encode()) await writer.drain() print(f'Sent {request!r} to {addr!r}') response = await reader.read(1024) print(f'Received {response.decode()!r} from {addr!r}') print('----------') request = input('Enter Request: ') writer.write(request.encode()) await writer.drain() print(f'Sent {request!r} to {addr!r}') response = await reader.read(1024) # read the final response print(f'Received {response.decode()!r} from {addr!r}') writer.close() await writer.wait_closed() print(f'Closed the connection with {addr!r}') async def start(self): reader, writer = await asyncio.open_connection(host=self.host, port=self.port) await self.handle_server(reader, writer) async def main(): client = Client(socket.gethostname(), 5000) await client.start() if __name__ == '__main__': asyncio.run(main())