async def _command_queue_iteration(self, command): """ Coroutine which evaluates one item from the circuit command queue. 1. Dispatch and validate through caproto.VirtualCircuit.process_command - Upon server failure, respond to the client with caproto.ErrorResponse 2. Update Channel state if applicable. """ try: self.circuit.process_command(command) except ca.RemoteProtocolError: cid, sid = self._get_ids_from_command(command) if cid is not None: try: await self.send(ca.ServerDisconnResponse(cid=cid)) except Exception: self.log.exception( "Client broke the protocol in a recoverable way, but " "channel disconnection of cid=%d sid=%d failed.", cid, sid) raise LoopExit('Recoverable protocol error failure') else: self.log.exception( "Client broke the protocol in a recoverable way. " "Disconnected channel cid=%d sid=%d but keeping the " "circuit alive.", cid, sid) await self._wake_new_command() return else: self.log.exception( "Client broke the protocol in an unrecoverable way.") # TODO: Kill the circuit. raise LoopExit('Unrecoverable protocol error') except Exception: self.log.exception('Circuit command queue evaluation failed') # Internal error - ignore for now return if command is ca.DISCONNECTED: raise DisconnectedCircuit try: response = await self._process_command(command) return response except Exception as ex: if not self.connected: if not isinstance(command, ca.ClearChannelRequest): self.log.error( 'Server error after client ' 'disconnection: %s', command) raise LoopExit('Server error after client disconnection') cid, sid = self._get_ids_from_command(command) chan, _ = self._get_db_entry_from_command(command) self.log.exception('Server failed to process command (%r): %s', chan.name, command) if cid is not None: error_message = f'Python exception: {type(ex).__name__} {ex}' return [ ca.ErrorResponse(command, cid, status=ca.CAStatus.ECA_INTERNAL, error_message=error_message) ]
async def command_queue_loop(self): """ Coroutine which feeds from the circuit command queue. 1. Dispatch and validate through caproto.VirtualCircuit.process_command - Upon server failure, respond to the client with caproto.ErrorResponse 2. Update Channel state if applicable. """ # The write_event will be cleared when a write is scheduled and set # when one completes. maybe_awaitable = self.write_event.set() # The curio backend makes this an awaitable thing. if maybe_awaitable is not None: await maybe_awaitable while True: try: command = await self.command_queue.get() self.circuit.process_command(command) if command is ca.DISCONNECTED: break except self.TaskCancelled: break except ca.RemoteProtocolError as ex: if hasattr(command, 'sid'): sid = command.sid cid = self.circuit.channels_sid[sid].cid elif hasattr(command, 'cid'): cid = command.cid sid = self.circuit.channels[cid].sid else: cid, sid = None, None if cid is not None: try: await self.send(ca.ServerDisconnResponse(cid=cid)) except Exception: self.log.error( "Client broke the protocol in a recoverable " "way, but channel disconnection of cid=%d sid=%d " "failed.", cid, sid, exc_info=ex) break else: self.log.error( "Client broke the protocol in a recoverable " "way. Disconnected channel cid=%d sid=%d " "but keeping the circuit alive.", cid, sid, exc_info=ex) await self._wake_new_command() continue else: self.log.error("Client broke the protocol in an " "unrecoverable way.", exc_info=ex) # TODO: Kill the circuit. break except Exception: self.log.exception('Circuit command queue evaluation failed') continue try: response_command = await self._process_command(command) if response_command is not None: await self.send(*response_command) except DisconnectedCircuit: await self._on_disconnect() self.circuit.disconnect() await self.context.circuit_disconnected(self) break except Exception as ex: if not self.connected: if not isinstance(command, ca.ClearChannelRequest): self.log.error('Server error after client ' 'disconnection: %s', command) break self.log.exception('Server failed to process command: %s', command) if hasattr(command, 'sid'): cid = self.circuit.channels_sid[command.sid].cid response_command = ca.ErrorResponse( command, cid, status=ca.CAStatus.ECA_INTERNAL, error_message=('Python exception: {} {}' ''.format(type(ex).__name__, ex)) ) await self.send(response_command) await self._wake_new_command()