Ejemplo n.º 1
0
            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)
Ejemplo n.º 2
0
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.'))
Ejemplo n.º 3
0
    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()
Ejemplo n.º 4
0
            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
Ejemplo n.º 5
0
    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)
                ]
Ejemplo n.º 6
0
    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()