def close_connection(self, connection: Connection, half_close: bool = False) -> None: if half_close: if not connection.state & ConnectionState.CAN_WRITE: return self.log(f"half-closing {connection}", "debug") try: writer = self.transports[connection].writer assert writer writer.write_eof() except OSError: # if we can't write to the socket anymore we presume it completely dead. connection.state = ConnectionState.CLOSED else: connection.state &= ~ConnectionState.CAN_WRITE else: connection.state = ConnectionState.CLOSED if connection.state is ConnectionState.CLOSED: handler = self.transports[connection].handler assert handler asyncio_utils.cancel_task(handler, "closed by command")
async def handle_connection(self, connection: Connection) -> None: """ Handle a connection for its entire lifetime. This means we read until EOF, but then possibly also keep on waiting for our side of the connection to be closed. """ cancelled = None reader = self.transports[connection].reader assert reader while True: try: data = await reader.read(65535) if not data: raise OSError("Connection closed by peer.") except OSError: break except asyncio.CancelledError as e: cancelled = e break self.server_event(events.DataReceived(connection, data)) try: await self.drain_writers() except asyncio.CancelledError as e: cancelled = e break if cancelled is None: connection.state &= ~ConnectionState.CAN_READ else: connection.state = ConnectionState.CLOSED self.server_event(events.ConnectionClosed(connection)) if cancelled is None and connection.state is ConnectionState.CAN_WRITE: # we may still use this connection to *send* stuff, # even though the remote has closed their side of the connection. # to make this work we keep this task running and wait for cancellation. await asyncio.Event().wait() try: writer = self.transports[connection].writer assert writer writer.close() except OSError: pass self.transports.pop(connection) if cancelled: raise cancelled