async def handle_write(): '''Wait for an asynchronous caput to finish''' try: write_status = await db_entry.auth_write( self.client_hostname, self.client_username, command.data, command.data_type, command.metadata) except Exception as ex: cid = self.circuit.channels_sid[command.sid].cid response_command = ca.ErrorResponse( command, cid, status_code=ca.ECA_INTERNAL.code_with_severity, error_message=('Python exception: {} {}' ''.format(type(ex).__name__, ex)) ) else: if write_status is None: # errors can be passed back by exceptions, and # returning none for write_status can just be # considered laziness write_status = True response_command = chan.write(ioid=command.ioid, status=write_status) if client_waiting: await self.send(response_command)
def test_error_response(circuit_pair): cli_circuit, srv_circuit = circuit_pair cli_channel, srv_channel = make_channels(*circuit_pair, 5, 1, name='a') req = cli_channel.read() buffers_to_send = cli_circuit.send(req) (req_received,), _ = srv_circuit.recv(*buffers_to_send) srv_circuit.process_command(req_received) srv_circuit.send(ca.ErrorResponse(original_request=req_received, cid=srv_channel.cid, status=42, error_message='Tom missed the train.'))
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. """ while True: try: command = await self.command_queue.get() self.circuit.process_command(command) except curio.TaskCancelled: break except Exception as ex: logger.error('Circuit command queue evaluation failed', exc_info=ex) 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): logger.error( 'Curio server error after client ' 'disconnection: %s', command) break logger.error('Curio server failed to process command: %s', command, exc_info=ex) if hasattr(command, 'sid'): cid = self.circuit.channels_sid[command.sid].cid response_command = ca.ErrorResponse( command, cid, status_code=ca.ECA_INTERNAL.code_with_severity, error_message=('Python exception: {} {}' ''.format(type(ex).__name__, ex))) await self.send(response_command) async with self.new_command_condition: await self.new_command_condition.notify_all()
async def handle_write(): '''Wait for an asynchronous caput to finish''' try: write_status = await db_entry.auth_write( self.client_hostname, self.client_username, command.data, command.data_type, command.metadata, user_address=self.circuit.address) except Exception as ex: self.log.exception('Invalid write request by %s (%s): %r', self.client_username, self.client_hostname, command) cid = self.circuit.channels_sid[command.sid].cid response_command = ca.ErrorResponse( command, cid, status=ca.CAStatus.ECA_PUTFAIL, error_message=('Python exception: {} {}' ''.format(type(ex).__name__, ex))) await self.send(response_command) else: if client_waiting: if write_status is None: # errors can be passed back by exceptions, and # returning none for write_status can just be # considered laziness write_status = True response_command = chan.write( ioid=command.ioid, status=write_status, data_count=db_entry.length) await self.send(response_command) finally: maybe_awaitable = self.write_event.set() # The curio backend makes this an awaitable thing. if maybe_awaitable is not None: await maybe_awaitable
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()