From 493bdf03c43b006bb9c4441d28c50ba2ae1146d6 Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Fri, 21 Nov 2014 15:24:38 +0200 Subject: [PATCH 1/6] applying patch from pika 0.9.14b1, poll errors --- pika/adapters/blocking_connection.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pika/adapters/blocking_connection.py b/pika/adapters/blocking_connection.py index 3d8bae4c7..0041582f2 100644 --- a/pika/adapters/blocking_connection.py +++ b/pika/adapters/blocking_connection.py @@ -59,7 +59,17 @@ def ready(self): """ if self.poller: - events = self.poller.poll(self.poll_timeout) + try: + events = self.poller.poll(self.poll_timeout) + except RuntimeError as exception: + LOGGER.debug('poll RuntimeException, reinitializing') + self.poller = select.poll() + self.poll_events = select.POLLIN | select.POLLPRI + self.poller.register(self.fd, self.poll_events) + + # Re-poll again. + events = self.poller.poll(self.poll_timeout) + return True if events else False else: ready, unused_wri, unused_err = select.select([self.fd], [], [], From 576c7a741276f1c570f725a9b76b017bcc2625c7 Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Mon, 8 Dec 2014 16:59:11 +0200 Subject: [PATCH 2/6] patch from oficial repository https://github.com/pika/pika/issues/352 --- pika/adapters/base_connection.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pika/adapters/base_connection.py b/pika/adapters/base_connection.py index 57996ef9b..1de7f4097 100644 --- a/pika/adapters/base_connection.py +++ b/pika/adapters/base_connection.py @@ -101,13 +101,15 @@ def _adapter_connect(self): """ # Get the addresses for the socket, supporting IPv4 & IPv6 - sock_addrs = socket.getaddrinfo(self.params.host, self.params.port, - 0, 0, socket.getprotobyname("tcp")) - - # Iterate through each addr tuple trying to connect - for sock_addr in sock_addrs: + try: + addresses = socket.getaddrinfo(self.params.host, self.params.port) + except socket.error as error: + LOGGER.critical('Could not get addresses to use: %s (%s)', + error, self.params.host) + return False - # If the socket is created and connected, continue on + # If the socket is created and connected, continue on + for sock_addr in addresses: if self._create_and_connect_to_socket(sock_addr): return True From 91413209446106656aa2b5607434fdac5da37db2 Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Mon, 8 Dec 2014 17:22:25 +0200 Subject: [PATCH 3/6] import all changes on blocking_connection from master repository --- pika/adapters/blocking_connection.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pika/adapters/blocking_connection.py b/pika/adapters/blocking_connection.py index 0041582f2..c89c1d78d 100644 --- a/pika/adapters/blocking_connection.py +++ b/pika/adapters/blocking_connection.py @@ -59,17 +59,7 @@ def ready(self): """ if self.poller: - try: - events = self.poller.poll(self.poll_timeout) - except RuntimeError as exception: - LOGGER.debug('poll RuntimeException, reinitializing') - self.poller = select.poll() - self.poll_events = select.POLLIN | select.POLLPRI - self.poller.register(self.fd, self.poll_events) - - # Re-poll again. - events = self.poller.poll(self.poll_timeout) - + events = self.poller.poll(self.poll_timeout) return True if events else False else: ready, unused_wri, unused_err = select.select([self.fd], [], [], @@ -233,7 +223,7 @@ def process_data_events(self): def process_timeouts(self): """Process the self._timeouts event stack""" - for timeout_id in list(self._timeouts.keys()): + for timeout_id in self._timeouts.keys(): if self._deadline_passed(timeout_id): self._call_timeout_method(self._timeouts.pop(timeout_id)) @@ -319,13 +309,13 @@ def _deadline_passed(self, timeout_id): :rtype: bool """ - if timeout_id not in list(self._timeouts.keys()): + if timeout_id not in self._timeouts.keys(): return False return self._timeouts[timeout_id]['deadline'] <= time.time() def _handle_disconnect(self): """Called internally when the socket is disconnected already""" - self.disconnect() + self._adapter_disconnect() self._on_connection_closed(None, True) def _handle_read(self): @@ -517,7 +507,7 @@ def basic_publish(self, exchange, routing_key, body, if mandatory: self._response = None - if isinstance(body, str): + if isinstance(body, unicode): body = body.encode('utf-8') if self._confirmation: @@ -934,7 +924,7 @@ def stop_consuming(self, consumer_tag=None): if consumer_tag: self.basic_cancel(consumer_tag) else: - for consumer_tag in list(self._consumers.keys()): + for consumer_tag in self._consumers.keys(): self.basic_cancel(consumer_tag) self.wait = True @@ -1164,4 +1154,4 @@ def _wait_on_response(self, method_frame): :param pika.frame.Method method_frame: The frame to check """ - return method_frame.NAME not in self.NO_RESPONSE_FRAMES + return method_frame.NAME not in self.NO_RESPONSE_FRAMES \ No newline at end of file From 6661fca9d7c1c27d66c06eaf439f3f06e69cc271 Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Mon, 8 Dec 2014 17:26:34 +0200 Subject: [PATCH 4/6] re apply patches --- pika/adapters/blocking_connection.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pika/adapters/blocking_connection.py b/pika/adapters/blocking_connection.py index c89c1d78d..8db89957b 100644 --- a/pika/adapters/blocking_connection.py +++ b/pika/adapters/blocking_connection.py @@ -59,7 +59,16 @@ def ready(self): """ if self.poller: - events = self.poller.poll(self.poll_timeout) + try: + events = self.poller.poll(self.poll_timeout) + except RuntimeError as exception: + LOGGER.debug('poll RuntimeException, reinitializing') + self.poller = select.poll() + self.poll_events = select.POLLIN | select.POLLPRI + self.poller.register(self.fd, self.poll_events) + + # Re-poll again. + events = self.poller.poll(self.poll_timeout) return True if events else False else: ready, unused_wri, unused_err = select.select([self.fd], [], [], @@ -507,7 +516,7 @@ def basic_publish(self, exchange, routing_key, body, if mandatory: self._response = None - if isinstance(body, unicode): + if isinstance(body, str): body = body.encode('utf-8') if self._confirmation: From 95ea064128f4aae6bbed8a3f74d06938c40ac2ef Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Mon, 8 Dec 2014 17:37:41 +0200 Subject: [PATCH 5/6] additional fixes --- pika/adapters/blocking_connection.py | 77 +++++++++++++++++++++------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/pika/adapters/blocking_connection.py b/pika/adapters/blocking_connection.py index 8db89957b..6c9087353 100644 --- a/pika/adapters/blocking_connection.py +++ b/pika/adapters/blocking_connection.py @@ -10,12 +10,16 @@ classes. """ +import os import logging import select import socket import time import warnings +import errno +from functools import wraps +from pika import frame from pika import callback from pika import channel from pika import exceptions @@ -23,9 +27,26 @@ from pika import utils from pika.adapters import base_connection +if os.name == 'java': + from select import cpython_compatible_select as select_function +else: + from select import select as select_function + LOGGER = logging.getLogger(__name__) +def retry_on_eintr(f): + @wraps(f) + def inner(*args, **kwargs): + while True: + try: + return f(*args, **kwargs) + except select.error as e: + if e[0] != errno.EINTR: + raise + return inner + + class ReadPoller(object): """A poller that will check to see if data is ready on the socket using very short timeouts to avoid having to read on the socket, speeding up the @@ -34,6 +55,7 @@ class ReadPoller(object): """ POLL_TIMEOUT = 10 + @retry_on_eintr def __init__(self, fd, poll_timeout=POLL_TIMEOUT): """Create a new instance of the ReadPoller which wraps poll and select to determine if the socket has data to read on it. @@ -44,7 +66,7 @@ def __init__(self, fd, poll_timeout=POLL_TIMEOUT): """ self.fd = fd self.poll_timeout = poll_timeout - if hasattr(select, 'poll'): + if hasattr(select, 'poll') and os.name != 'java': self.poller = select.poll() self.poll_events = select.POLLIN | select.POLLPRI self.poller.register(self.fd, self.poll_events) @@ -52,6 +74,7 @@ def __init__(self, fd, poll_timeout=POLL_TIMEOUT): self.poller = None self.poll_timeout = float(poll_timeout) / 1000 + @retry_on_eintr def ready(self): """Check to see if the socket has data to read. @@ -198,7 +221,8 @@ def close(self, reply_code=200, reply_text='Normal shutdown'): self._close_channels(reply_code, reply_text) while self._has_open_channels: self.process_data_events() - self._send_connection_close(reply_code, reply_text) + if self.socket: + self._send_connection_close(reply_code, reply_text) while self.is_closing: self.process_data_events() if self.heartbeat: @@ -212,8 +236,9 @@ def connect(self): """ self._set_connection_state(self.CONNECTION_INIT) - if not self._adapter_connect(): - raise exceptions.AMQPConnectionError('Could not connect') + error = self._adapter_connect() + if error: + raise exceptions.AMQPConnectionError(error) def process_data_events(self): """Will make sure that data events are processed. Your app can @@ -280,8 +305,9 @@ def _adapter_connect(self): """ # Remove the default behavior for connection errors self.callbacks.remove(0, self.ON_CONNECTION_ERROR) - if not super(BlockingConnection, self)._adapter_connect(): - raise exceptions.AMQPConnectionError(1) + error = super(BlockingConnection, self)._adapter_connect() + if error: + raise exceptions.AMQPConnectionError(error) self.socket.settimeout(self.SOCKET_CONNECT_TIMEOUT) self._frames_written_without_read = 0 self._socket_timeouts = 0 @@ -292,7 +318,6 @@ def _adapter_connect(self): self.process_data_events() self.socket.settimeout(self.params.socket_timeout) self._set_connection_state(self.CONNECTION_OPEN) - return True def _adapter_disconnect(self): """Called if the connection is being requested to disconnect.""" @@ -322,11 +347,6 @@ def _deadline_passed(self, timeout_id): return False return self._timeouts[timeout_id]['deadline'] <= time.time() - def _handle_disconnect(self): - """Called internally when the socket is disconnected already""" - self._adapter_disconnect() - self._on_connection_closed(None, True) - def _handle_read(self): """If the ReadPoller says there is data to read, try adn read it in the _handle_read of the parent class. Once read, reset the counter that @@ -350,6 +370,15 @@ def _handle_timeout(self): LOGGER.critical('Closing connection due to timeout') self._on_connection_closed(None, True) + def _check_state_on_disconnect(self): + """Checks closing corner cases to see why we were disconnected and if we should + raise exceptions for the anticipated exception types. + """ + super(BlockingConnection, self)._check_state_on_disconnect() + if self.is_open: + # already logged a warning in the base class, now fire an exception + raise exceptions.ConnectionClosed() + def _flush_outbound(self): """Flush the outbound socket buffer.""" if self.outbound_buffer: @@ -396,9 +425,10 @@ def _send_frame(self, frame_value): """ super(BlockingConnection, self)._send_frame(frame_value) self._frames_written_without_read += 1 - if self._frames_written_without_read == self.WRITE_TO_READ_RATIO: - self._frames_written_without_read = 0 - self.process_data_events() + if self._frames_written_without_read >= self.WRITE_TO_READ_RATIO: + if not isinstance(frame_value, frame.Method): + self._frames_written_without_read = 0 + self.process_data_events() class BlockingChannel(channel.Channel): @@ -441,6 +471,7 @@ def __init__(self, connection, channel_number): self._frames = dict() self._replies = list() self._wait = False + self._received_response = False self.open() def basic_cancel(self, consumer_tag='', nowait=False): @@ -492,7 +523,8 @@ def basic_get(self, queue=None, no_ack=False): def basic_publish(self, exchange, routing_key, body, properties=None, mandatory=False, immediate=False): """Publish to the channel with the given exchange, routing key and body. - For more information on basic_publish and what the parameters do, see: + Returns a boolean value indicating the success of the operation. For + more information on basic_publish and what the parameters do, see: http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.publish @@ -669,7 +701,7 @@ def close(self, reply_code=0, reply_text="Normal Shutdown"): self._set_state(self.CLOSED) self._cleanup() - def consume(self, queue): + def consume(self, queue, no_ack=False, exclusive=False): """Blocking consumption of a queue instead of via a callback. This method is a generator that returns messages a tuple of method, properties, and body. @@ -686,6 +718,10 @@ def consume(self, queue): :param queue: The queue name to consume :type queue: str or unicode + :param no_ack: Tell the broker to not expect a response + :type no_ack: bool + :param exclusive: Don't allow other consumers on the queue + :type exclusive: bool :rtype: tuple(spec.Basic.Deliver, spec.BasicProperties, str or unicode) """ @@ -693,7 +729,9 @@ def consume(self, queue): if not self._generator: LOGGER.debug('Issuing Basic.Consume') self._generator = self.basic_consume(self._generator_callback, - queue) + queue, + no_ack, + exclusive) while True: if self._generator_messages: yield self._generator_messages.pop(0) @@ -1108,7 +1146,6 @@ def _rpc(self, method_frame, callback=None, acceptable_replies=None, self._on_rpc_complete, arguments=arguments) replies.append(key) - self._received_response = False self._send_method(method_frame, content, self._wait_on_response(method_frame)) if force_data_events and self._force_data_events_override is not False: @@ -1126,6 +1163,7 @@ def _send_method(self, method_frame, content=None, wait=False): """ self.wait = wait + prev_received_response = self._received_response self._received_response = False self.connection.send_method(self.channel_number, method_frame, content) while wait and not self._received_response: @@ -1133,6 +1171,7 @@ def _send_method(self, method_frame, content=None, wait=False): self.connection.process_data_events() except exceptions.AMQPConnectionError: break + self._received_response = prev_received_response def _validate_acceptable_replies(self, acceptable_replies): """Validate the list of acceptable replies From 787aecabc83f0ee07ab9fc103015a435e72a5a7c Mon Sep 17 00:00:00 2001 From: Konstantin Gritsenko Date: Mon, 8 Dec 2014 18:38:40 +0200 Subject: [PATCH 6/6] stable --- pika/adapters/blocking_connection.py | 77 +++++++--------------------- 1 file changed, 19 insertions(+), 58 deletions(-) diff --git a/pika/adapters/blocking_connection.py b/pika/adapters/blocking_connection.py index 6c9087353..8db89957b 100644 --- a/pika/adapters/blocking_connection.py +++ b/pika/adapters/blocking_connection.py @@ -10,16 +10,12 @@ classes. """ -import os import logging import select import socket import time import warnings -import errno -from functools import wraps -from pika import frame from pika import callback from pika import channel from pika import exceptions @@ -27,26 +23,9 @@ from pika import utils from pika.adapters import base_connection -if os.name == 'java': - from select import cpython_compatible_select as select_function -else: - from select import select as select_function - LOGGER = logging.getLogger(__name__) -def retry_on_eintr(f): - @wraps(f) - def inner(*args, **kwargs): - while True: - try: - return f(*args, **kwargs) - except select.error as e: - if e[0] != errno.EINTR: - raise - return inner - - class ReadPoller(object): """A poller that will check to see if data is ready on the socket using very short timeouts to avoid having to read on the socket, speeding up the @@ -55,7 +34,6 @@ class ReadPoller(object): """ POLL_TIMEOUT = 10 - @retry_on_eintr def __init__(self, fd, poll_timeout=POLL_TIMEOUT): """Create a new instance of the ReadPoller which wraps poll and select to determine if the socket has data to read on it. @@ -66,7 +44,7 @@ def __init__(self, fd, poll_timeout=POLL_TIMEOUT): """ self.fd = fd self.poll_timeout = poll_timeout - if hasattr(select, 'poll') and os.name != 'java': + if hasattr(select, 'poll'): self.poller = select.poll() self.poll_events = select.POLLIN | select.POLLPRI self.poller.register(self.fd, self.poll_events) @@ -74,7 +52,6 @@ def __init__(self, fd, poll_timeout=POLL_TIMEOUT): self.poller = None self.poll_timeout = float(poll_timeout) / 1000 - @retry_on_eintr def ready(self): """Check to see if the socket has data to read. @@ -221,8 +198,7 @@ def close(self, reply_code=200, reply_text='Normal shutdown'): self._close_channels(reply_code, reply_text) while self._has_open_channels: self.process_data_events() - if self.socket: - self._send_connection_close(reply_code, reply_text) + self._send_connection_close(reply_code, reply_text) while self.is_closing: self.process_data_events() if self.heartbeat: @@ -236,9 +212,8 @@ def connect(self): """ self._set_connection_state(self.CONNECTION_INIT) - error = self._adapter_connect() - if error: - raise exceptions.AMQPConnectionError(error) + if not self._adapter_connect(): + raise exceptions.AMQPConnectionError('Could not connect') def process_data_events(self): """Will make sure that data events are processed. Your app can @@ -305,9 +280,8 @@ def _adapter_connect(self): """ # Remove the default behavior for connection errors self.callbacks.remove(0, self.ON_CONNECTION_ERROR) - error = super(BlockingConnection, self)._adapter_connect() - if error: - raise exceptions.AMQPConnectionError(error) + if not super(BlockingConnection, self)._adapter_connect(): + raise exceptions.AMQPConnectionError(1) self.socket.settimeout(self.SOCKET_CONNECT_TIMEOUT) self._frames_written_without_read = 0 self._socket_timeouts = 0 @@ -318,6 +292,7 @@ def _adapter_connect(self): self.process_data_events() self.socket.settimeout(self.params.socket_timeout) self._set_connection_state(self.CONNECTION_OPEN) + return True def _adapter_disconnect(self): """Called if the connection is being requested to disconnect.""" @@ -347,6 +322,11 @@ def _deadline_passed(self, timeout_id): return False return self._timeouts[timeout_id]['deadline'] <= time.time() + def _handle_disconnect(self): + """Called internally when the socket is disconnected already""" + self._adapter_disconnect() + self._on_connection_closed(None, True) + def _handle_read(self): """If the ReadPoller says there is data to read, try adn read it in the _handle_read of the parent class. Once read, reset the counter that @@ -370,15 +350,6 @@ def _handle_timeout(self): LOGGER.critical('Closing connection due to timeout') self._on_connection_closed(None, True) - def _check_state_on_disconnect(self): - """Checks closing corner cases to see why we were disconnected and if we should - raise exceptions for the anticipated exception types. - """ - super(BlockingConnection, self)._check_state_on_disconnect() - if self.is_open: - # already logged a warning in the base class, now fire an exception - raise exceptions.ConnectionClosed() - def _flush_outbound(self): """Flush the outbound socket buffer.""" if self.outbound_buffer: @@ -425,10 +396,9 @@ def _send_frame(self, frame_value): """ super(BlockingConnection, self)._send_frame(frame_value) self._frames_written_without_read += 1 - if self._frames_written_without_read >= self.WRITE_TO_READ_RATIO: - if not isinstance(frame_value, frame.Method): - self._frames_written_without_read = 0 - self.process_data_events() + if self._frames_written_without_read == self.WRITE_TO_READ_RATIO: + self._frames_written_without_read = 0 + self.process_data_events() class BlockingChannel(channel.Channel): @@ -471,7 +441,6 @@ def __init__(self, connection, channel_number): self._frames = dict() self._replies = list() self._wait = False - self._received_response = False self.open() def basic_cancel(self, consumer_tag='', nowait=False): @@ -523,8 +492,7 @@ def basic_get(self, queue=None, no_ack=False): def basic_publish(self, exchange, routing_key, body, properties=None, mandatory=False, immediate=False): """Publish to the channel with the given exchange, routing key and body. - Returns a boolean value indicating the success of the operation. For - more information on basic_publish and what the parameters do, see: + For more information on basic_publish and what the parameters do, see: http://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.publish @@ -701,7 +669,7 @@ def close(self, reply_code=0, reply_text="Normal Shutdown"): self._set_state(self.CLOSED) self._cleanup() - def consume(self, queue, no_ack=False, exclusive=False): + def consume(self, queue): """Blocking consumption of a queue instead of via a callback. This method is a generator that returns messages a tuple of method, properties, and body. @@ -718,10 +686,6 @@ def consume(self, queue, no_ack=False, exclusive=False): :param queue: The queue name to consume :type queue: str or unicode - :param no_ack: Tell the broker to not expect a response - :type no_ack: bool - :param exclusive: Don't allow other consumers on the queue - :type exclusive: bool :rtype: tuple(spec.Basic.Deliver, spec.BasicProperties, str or unicode) """ @@ -729,9 +693,7 @@ def consume(self, queue, no_ack=False, exclusive=False): if not self._generator: LOGGER.debug('Issuing Basic.Consume') self._generator = self.basic_consume(self._generator_callback, - queue, - no_ack, - exclusive) + queue) while True: if self._generator_messages: yield self._generator_messages.pop(0) @@ -1146,6 +1108,7 @@ def _rpc(self, method_frame, callback=None, acceptable_replies=None, self._on_rpc_complete, arguments=arguments) replies.append(key) + self._received_response = False self._send_method(method_frame, content, self._wait_on_response(method_frame)) if force_data_events and self._force_data_events_override is not False: @@ -1163,7 +1126,6 @@ def _send_method(self, method_frame, content=None, wait=False): """ self.wait = wait - prev_received_response = self._received_response self._received_response = False self.connection.send_method(self.channel_number, method_frame, content) while wait and not self._received_response: @@ -1171,7 +1133,6 @@ def _send_method(self, method_frame, content=None, wait=False): self.connection.process_data_events() except exceptions.AMQPConnectionError: break - self._received_response = prev_received_response def _validate_acceptable_replies(self, acceptable_replies): """Validate the list of acceptable replies