Beispiel #1
0
    def receive_message(self, queue_name, receive_timeout_in_seconds=None):
        """
        Receive a message from the specified queue in Redis.

        :param queue_name: The name of the queue to which to send the message
        :type queue_name: union(str, unicode)
        :param receive_timeout_in_seconds: The optional timeout, which defaults to the setting with the same name
        :type receive_timeout_in_seconds: int

        :return: A tuple of request ID, message meta-information dict, and message body dict
        :rtype: tuple(int, dict, dict)

        :raise: MessageReceiveError, MessageReceiveTimeout, InvalidMessageError
        """
        queue_key = self.QUEUE_NAME_PREFIX + queue_name

        try:
            with self._get_timer('receive.get_redis_connection'):
                connection = self.backend_layer.get_connection(queue_key)
            # returns message or None if no new messages within timeout
            with self._get_timer('receive.pop_from_redis_queue'):
                result = connection.blpop(
                    [queue_key],
                    timeout=receive_timeout_in_seconds
                    or self.receive_timeout_in_seconds,
                )
            serialized_message = None
            if result:
                serialized_message = result[1]
        except CannotGetConnectionError as e:
            self._get_counter('receive.error.connection').increment()
            raise MessageReceiveError('Cannot get connection: {}'.format(
                e.args[0]))
        except Exception as e:
            self._get_counter('receive.error.unknown').increment()
            raise MessageReceiveError(
                'Unknown error receiving message for service {}'.format(
                    self.service_name), six.text_type(type(e).__name__),
                *e.args)

        if serialized_message is None:
            raise MessageReceiveTimeout(
                'No message received for service {}'.format(self.service_name))

        with self._get_timer('receive.deserialize'):
            message = self.serializer.blob_to_dict(serialized_message)

        if self._is_message_expired(message):
            self._get_counter('receive.error.message_expired').increment()
            raise MessageReceiveTimeout(
                'Message expired for service {}'.format(self.service_name))

        request_id = message.get('request_id')
        if request_id is None:
            self._get_counter('receive.error.no_request_id').increment()
            raise InvalidMessageError('No request ID for service {}'.format(
                self.service_name))

        return request_id, message.get('meta', {}), message.get('body')
Beispiel #2
0
    def send_response_message(self, request_id, meta, body):
        try:
            queue_name = meta['reply_to']
        except KeyError:
            self.metrics.counter('server.transport.redis_gateway.send.error.missing_reply_queue')
            raise InvalidMessageError('Missing reply queue name')

        with self.metrics.timer('server.transport.redis_gateway.send', resolution=TimerResolution.MICROSECONDS):
            self.core.send_message(queue_name, request_id, meta, body)
Beispiel #3
0
    def receive_message(self, receive_timeout_in_seconds=None):
        try:
            with self._get_timer('receive.get_from_requests_queue'):
                stream_id, protocol_key, serialized_message = self.requests_queue.get(
                    timeout=receive_timeout_in_seconds or self.receive_timeout_in_seconds,
                )
        except six.moves.queue.Empty:
            raise MessageReceiveTimeout('No message received for service {}'.format(self.service_name))
        except Exception as e:
            self._get_counter('receive.error.unknown').increment()
            raise MessageReceiveError(
                'Unknown error receiving message for service {}'.format(self.service_name),
                six.text_type(type(e).__name__),
                *e.args
            )

        with self._get_timer('receive.deserialize'):
            serializer = self.default_serializer
            if serialized_message.startswith(b'content-type'):
                # TODO: Breaking change: Assume all messages start with a content type. This should not be done until
                # TODO all servers and clients have Step 2 code. This will be a Step 3 breaking change.
                header, serialized_message = serialized_message.split(b';', 1)
                mime_type = header.split(b':', 1)[1].decode('utf-8').strip()
                if mime_type in Serializer.all_supported_mime_types:
                    serializer = Serializer.resolve_serializer(mime_type)

            message = serializer.blob_to_dict(serialized_message)
            message.setdefault('meta', {})['serializer'] = serializer

            meta = message.get('meta')
            meta['stream_id'] = stream_id
            meta['protocol_key'] = protocol_key

        if self._is_message_expired(message):
            self._get_counter('receive.error.message_expired').increment()
            raise MessageReceiveTimeout('Message expired for service {}'.format(self.service_name))

        request_id = message.get('request_id')
        if request_id is None:
            self._get_counter('receive.error.no_request_id').increment()
            raise InvalidMessageError('No request ID for service {}'.format(self.service_name))

        return request_id, message.get('meta', {}), message.get('body')
Beispiel #4
0
    def receive_message(self, queue_name):
        queue_key = self.QUEUE_NAME_PREFIX + queue_name

        try:
            with self._get_timer('receive.get_redis_connection'):
                connection = self.backend_layer.get_connection(queue_key)
            # returns message or None if no new messages within timeout
            with self._get_timer('receive.pop_from_redis_queue'):
                result = connection.blpop([queue_key], timeout=self.receive_timeout_in_seconds)
            serialized_message = None
            if result:
                serialized_message = result[1]
        except CannotGetConnectionError as e:
            self._get_counter('receive.error.connection').increment()
            raise MessageReceiveError('Cannot get connection: {}'.format(e.args[0]))
        except Exception as e:
            self._get_counter('receive.error.unknown').increment()
            raise MessageReceiveError(
                'Unknown error receiving message for service {}'.format(self.service_name),
                six.text_type(type(e).__name__),
                *e.args
            )

        if serialized_message is None:
            raise MessageReceiveTimeout('No message received for service {}'.format(self.service_name))

        with self._get_timer('receive.deserialize'):
            message = self.serializer.blob_to_dict(serialized_message)

        if self._is_message_expired(message):
            self._get_counter('receive.error.message_expired').increment()
            raise MessageReceiveTimeout('Message expired for service {}'.format(self.service_name))

        request_id = message.get('request_id')
        if request_id is None:
            self._get_counter('receive.error.no_request_id').increment()
            raise InvalidMessageError('No request ID for service {}'.format(self.service_name))

        return request_id, message.get('meta', {}), message.get('body')
Beispiel #5
0
    def send_message(self, request_id, meta, body, message_expiry_in_seconds=None):
        protocol_key = meta.get('protocol_key')
        stream_id = meta.get('stream_id')

        if request_id is None:
            raise InvalidMessageError('No request ID')

        if message_expiry_in_seconds:
            message_expiry = time.time() + message_expiry_in_seconds
        else:
            message_expiry = time.time() + self.message_expiry_in_seconds

        meta['__expiry__'] = message_expiry

        message = {'request_id': request_id, 'meta': meta, 'body': body}

        with self._get_timer('send.serialize'):
            serializer = self.default_serializer
            if 'serializer' in meta:
                # TODO: Breaking change: Assume a MIME type is always specified. This should not be done until all
                # TODO servers and clients have Step 2 code. This will be a Step 3 breaking change.
                serializer = meta.pop('serializer')
            serialized_message = (
                'content-type:{};'.format(serializer.mime_type).encode('utf-8') + serializer.dict_to_blob(message)
            )

        message_size_in_bytes = len(serialized_message)

        response_headers = [
            (':status', '200'),
            ('content-type', 'application/json'),
            ('content-length', str(message_size_in_bytes)),
            ('server', 'pysoa-h2'),
        ]

        if self.log_messages_larger_than_bytes and message_size_in_bytes > self.log_messages_larger_than_bytes:
            _oversized_message_logger.warning(
                'Oversized message sent for PySOA service {}'.format(self.service_name),
                extra={'data': {
                    'message': RecursivelyCensoredDictWrapper(message),
                    'serialized_length_in_bytes': message_size_in_bytes,
                    'threshold': self.log_messages_larger_than_bytes,
                }},
            )
        for i in range(-1, self.queue_full_retries):
            if i >= 0:
                time.sleep((2 ** i + random.random()) / self.EXPONENTIAL_BACK_OFF_FACTOR)
                self._get_counter('send.responses_queue_full_retry').increment()
                self._get_counter('send.responses_queue_full_retry.retry_{}'.format(i + 1)).increment()
            try:
                with self._get_timer('send.send_message_response_http2_queue'):
                    self.responses_queue.put((
                        protocol_key,
                        stream_id,
                        request_id,
                        serialized_message,
                        response_headers,
                    ), timeout=0)
                return
            except six.moves.queue.Full:
                continue
            except Exception as e:
                self._get_counter('send.error.unknown').increment()
                raise MessageSendError(
                    'Unknown error sending message for service {}'.format(self.service_name),
                    six.text_type(type(e).__name__),
                    *e.args
                )

        self._get_counter('send.error.responses_queue_full').increment()
        raise MessageSendError(
            'Http2 responses queue was full after {retries} retries'.format(
                retries=self.queue_full_retries,
            )
        )
Beispiel #6
0
    def send_message(self,
                     queue_name,
                     request_id,
                     meta,
                     body,
                     message_expiry_in_seconds=None):
        """
        Send a message to the specified queue in Redis.

        :param queue_name: The name of the queue to which to send the message
        :type queue_name: union(str, unicode)
        :param request_id: The message's request ID
        :type request_id: int
        :param meta: The message meta information, if any (should be an empty dict if no metadata)
        :type meta: dict
        :param body: The message body (should be a dict)
        :type body: dict
        :param message_expiry_in_seconds: The optional message expiry, which defaults to the setting with the same name
        :type message_expiry_in_seconds: int

        :raise: InvalidMessageError, MessageTooLarge, MessageSendError
        """
        if request_id is None:
            raise InvalidMessageError('No request ID')

        if message_expiry_in_seconds:
            message_expiry = time.time() + message_expiry_in_seconds
            redis_expiry = message_expiry_in_seconds + 10
        else:
            message_expiry = time.time() + self.message_expiry_in_seconds
            redis_expiry = self.message_expiry_in_seconds

        meta['__expiry__'] = message_expiry

        message = {'request_id': request_id, 'meta': meta, 'body': body}

        with self._get_timer('send.serialize'):
            serialized_message = self.serializer.dict_to_blob(message)

        message_size_in_bytes = len(serialized_message)
        if message_size_in_bytes > self.maximum_message_size_in_bytes:
            self._get_counter('send.error.message_too_large').increment()
            raise MessageTooLarge(message_size_in_bytes)
        elif self.log_messages_larger_than_bytes and message_size_in_bytes > self.log_messages_larger_than_bytes:
            _oversized_message_logger.warning(
                'Oversized message sent for PySOA service {}'.format(
                    self.service_name),
                extra={
                    'data': {
                        'message': RecursivelyCensoredDictWrapper(message),
                        'serialized_length_in_bytes': message_size_in_bytes,
                        'threshold': self.log_messages_larger_than_bytes,
                    }
                },
            )

        queue_key = self.QUEUE_NAME_PREFIX + queue_name

        # Try at least once, up to queue_full_retries times, then error
        for i in range(-1, self.queue_full_retries):
            if i >= 0:
                time.sleep((2**i + random.random()) /
                           self.EXPONENTIAL_BACK_OFF_FACTOR)
                self._get_counter('send.queue_full_retry').increment()
                self._get_counter(
                    'send.queue_full_retry.retry_{}'.format(i +
                                                            1)).increment()
            try:
                with self._get_timer('send.get_redis_connection'):
                    connection = self.backend_layer.get_connection(queue_key)

                with self._get_timer('send.send_message_to_redis_queue'):
                    self.backend_layer.send_message_to_queue(
                        queue_key=queue_key,
                        message=serialized_message,
                        expiry=redis_expiry,
                        capacity=self.queue_capacity,
                        connection=connection,
                    )
                return
            except redis.exceptions.ResponseError as e:
                # The Lua script handles capacity checking and sends the "full" error back
                if e.args[0] == 'queue full':
                    continue
                self._get_counter('send.error.response').increment()
                raise MessageSendError(
                    'Redis error sending message for service {}'.format(
                        self.service_name), *e.args)
            except CannotGetConnectionError as e:
                self._get_counter('send.error.connection').increment()
                raise MessageSendError('Cannot get connection: {}'.format(
                    e.args[0]))
            except Exception as e:
                self._get_counter('send.error.unknown').increment()
                raise MessageSendError(
                    'Unknown error sending message for service {}'.format(
                        self.service_name), six.text_type(type(e).__name__),
                    *e.args)

        self._get_counter('send.error.redis_queue_full').increment()
        raise MessageSendError(
            'Redis queue {queue_name} was full after {retries} retries'.format(
                queue_name=queue_name,
                retries=self.queue_full_retries,
            ))
Beispiel #7
0
    def send_message(self, queue_name, request_id, meta, body):
        """
        Send a message to the specified queue in Redis.

        :param queue_name: The name of the queue to which to send the message
        :param request_id: The message's request ID
        :param meta: The meta information, if any (should be an empty dict if no metadata)
        :param body: The message body (should be a dict)
        :raise
        """
        if request_id is None:
            raise InvalidMessageError('No request ID')

        meta['__expiry__'] = self._get_message_expiry()

        message = {'request_id': request_id, 'meta': meta, 'body': body}

        with self._get_timer('send.serialize'):
            serialized_message = self.serializer.dict_to_blob(message)

        if len(serialized_message) > self.MAXIMUM_MESSAGE_BYTES:
            self._get_counter('send.error.message_too_large').increment()
            raise MessageTooLarge()

        queue_key = self.QUEUE_NAME_PREFIX + queue_name

        # Try at least once, up to queue_full_retries times, then error
        for i in range(-1, self.queue_full_retries):
            if i >= 0:
                time.sleep((2 ** i + random.random()) / self.EXPONENTIAL_BACK_OFF_FACTOR)
                self._get_counter('send.queue_full_retry').increment()
                self._get_counter('send.queue_full_retry.retry_{}'.format(i + 1)).increment()
            try:
                with self._get_timer('send.get_redis_connection'):
                    connection = self.backend_layer.get_connection(queue_key)

                with self._get_timer('send.send_message_to_redis_queue'):
                    self.backend_layer.send_message_to_queue(
                        queue_key=queue_key,
                        message=serialized_message,
                        expiry=self.message_expiry_in_seconds,
                        capacity=self.queue_capacity,
                        connection=connection,
                    )
                return
            except redis.exceptions.ResponseError as e:
                # The Lua script handles capacity checking and sends the "full" error back
                if e.args[0] == 'queue full':
                    continue
                self._get_counter('send.error.response').increment()
                raise MessageSendError('Redis error sending message for service {}'.format(self.service_name), *e.args)
            except CannotGetConnectionError as e:
                self._get_counter('send.error.connection').increment()
                raise MessageSendError('Cannot get connection: {}'.format(e.args[0]))
            except Exception as e:
                self._get_counter('send.error.unknown').increment()
                raise MessageSendError(
                    'Unknown error sending message for service {}'.format(self.service_name),
                    six.text_type(type(e).__name__),
                    *e.args
                )

        self._get_counter('send.error.redis_queue_full').increment()
        raise MessageSendError(
            'Redis queue {queue_name} was full after {retries} retries'.format(
                queue_name=queue_name,
                retries=self.queue_full_retries,
            )
        )
Beispiel #8
0
    def receive_message(self, queue_name, receive_timeout_in_seconds=None):
        """
        Receive a message from the specified queue in Redis.

        :param queue_name: The name of the queue to which to send the message
        :type queue_name: union(str, unicode)
        :param receive_timeout_in_seconds: The optional timeout, which defaults to the setting with the same name
        :type receive_timeout_in_seconds: int

        :return: A tuple of request ID, message meta-information dict, and message body dict
        :rtype: tuple(int, dict, dict)

        :raise: MessageReceiveError, MessageReceiveTimeout, InvalidMessageError
        """
        queue_key = self.QUEUE_NAME_PREFIX + queue_name

        try:
            with self._get_timer('receive.get_redis_connection'):
                connection = self.backend_layer.get_connection(queue_key)
            # returns message or None if no new messages within timeout
            with self._get_timer('receive.pop_from_redis_queue'):
                result = connection.blpop(
                    [queue_key],
                    timeout=receive_timeout_in_seconds
                    or self.receive_timeout_in_seconds,
                )
            serialized_message = None
            if result:
                serialized_message = result[1]
        except CannotGetConnectionError as e:
            self._get_counter('receive.error.connection').increment()
            raise MessageReceiveError('Cannot get connection: {}'.format(
                e.args[0]))
        except Exception as e:
            self._get_counter('receive.error.unknown').increment()
            raise MessageReceiveError(
                'Unknown error receiving message for service {}'.format(
                    self.service_name), six.text_type(type(e).__name__),
                *e.args)

        if serialized_message is None:
            raise MessageReceiveTimeout(
                'No message received for service {}'.format(self.service_name))

        with self._get_timer('receive.deserialize'):
            serializer = self.default_serializer
            if serialized_message.startswith(b'content-type'):
                # TODO: Breaking change: Assume all messages start with a content type. This should not be done until
                # TODO all servers and clients have Step 2 code. This will be a Step 3 breaking change.
                header, serialized_message = serialized_message.split(b';', 1)
                mime_type = header.split(b':', 1)[1].decode('utf-8').strip()
                if mime_type in Serializer.all_supported_mime_types:
                    serializer = Serializer.resolve_serializer(mime_type)

            message = serializer.blob_to_dict(serialized_message)
            message.setdefault('meta', {})['serializer'] = serializer

        if self._is_message_expired(message):
            self._get_counter('receive.error.message_expired').increment()
            raise MessageReceiveTimeout(
                'Message expired for service {}'.format(self.service_name))

        request_id = message.get('request_id')
        if request_id is None:
            self._get_counter('receive.error.no_request_id').increment()
            raise InvalidMessageError('No request ID for service {}'.format(
                self.service_name))

        return request_id, message.get('meta', {}), message.get('body')