class BaseChannel(object): _close_callback = None # close callback to use when closing, not always set (used for pooling) _closed_error_callback = None # callback which triggers when the underlying transport closes with error _exchange = None # exchange (too AMQP specific) _close_event = None # used for giving notice a close was processed # exchange related settings @TODO: these should likely come from config instead _exchange_type = 'topic' _exchange_auto_delete = None _exchange_durable = None # States, Inputs for FSM S_INIT = 'INIT' S_ACTIVE = 'ACTIVE' S_CLOSED = 'CLOSED' I_ATTACH = 'ATTACH' I_CLOSE = 'CLOSE' def __init__(self, close_callback=None): """ Initializes a BaseChannel instance. @param transport Underlying transport used for broker communication. Can be None, if so, will use the AMQPTransport stateless singleton. @type transport BaseTransport @param close_callback The method to invoke when close() is called on this BaseChannel. May be left as None, in which case close_impl() will be called. This expects to be a callable taking one param, this channel instance. """ self.set_close_callback(close_callback) self._transport = None # setup FSM for BaseChannel / SendChannel tree self._fsm = FSM(self.S_INIT) self._fsm.add_transition(self.I_ATTACH, self.S_INIT, None, self.S_ACTIVE) self._fsm.add_transition(self.I_CLOSE, self.S_ACTIVE, self._on_close, self.S_CLOSED) self._fsm.add_transition( self.I_CLOSE, self.S_CLOSED, lambda *args: self.on_channel_close(None, 0, ""), self.S_CLOSED) # closed is a goal state, multiple closes are ok self._fsm.add_transition(self.I_CLOSE, self.S_INIT, None, self.S_CLOSED) # INIT to CLOSED is fine too @property def exchange_auto_delete(self): if self._exchange_auto_delete is not None: return self._exchange_auto_delete if hasattr(self, '_send_name') and hasattr(self._send_name, 'exchange_auto_delete'): return self._send_name.exchange_auto_delete if hasattr(self, '_recv_name') and hasattr(self._recv_name, 'exchange_auto_delete'): return self._recv_name.exchange_auto_delete return True @exchange_auto_delete.setter def exchange_auto_delete(self, value): self._exchange_auto_delete = value @property def exchange_durable(self): if self._exchange_durable is not None: return self._exchange_durable if hasattr(self, '_send_name') and hasattr(self._send_name, 'exchange_durable'): return self._send_name.exchange_durable if hasattr(self, '_recv_name') and hasattr(self._recv_name, 'exchange_durable'): return self._recv_name.exchange_durable return False @exchange_durable.setter def exchange_durable(self, value): self._exchange_durable = value def set_close_callback(self, close_callback): """ Sets a callback method to be called when this channel's close method is invoked. By default, if no callback is set on this channel, close_impl() is called instead. The close callback is chiefly used to pool channels in the Node. @param close_callback The callback method. Should be a callable taking one argument, this channel. """ self._close_callback = close_callback @contextmanager def _ensure_transport(self): """ Ensures this Channel has been activated with the Node. """ # log.debug("BaseChannel._ensure_transport (current: %s)", self._transport is not None) if not self._transport: raise ChannelError("No transport attached") if not self._lock: raise ChannelError("No lock available") # is lock already acquired? spit out a notice if self._lock._is_owned(): log.warn( "INTERLEAVE DETECTED:\n\nCURRENT STACK:\n%s\n\nSTACK THAT LOCKED: %s\n", "".join(traceback.format_stack()), "".join(self._lock_trace)) with self._lock: # we could wait and wait, and it gets closed, and unless we check again, we'd never know! if not self._transport: raise ChannelError("No transport attached") self._lock_trace = traceback.format_stack() try: yield finally: self._lock_trace = None def _declare_exchange(self, exchange): """ Performs an AMQP exchange declare. @param exchange The name of the exchange to use. @TODO: this really shouldn't exist, messaging layer should not make this declaration. it will be provided. perhaps push into an ion layer derivation to help bootstrapping / getting started fast. """ self._exchange = exchange assert self._exchange assert self._transport with self._ensure_transport(): # log.debug("Exchange declare: %s, TYPE %s, DUR %s AD %s", self._exchange, self._exchange_type, # self.exchange_durable, self.exchange_auto_delete) self._transport.declare_exchange_impl( self._exchange, exchange_type=self._exchange_type, durable=self.exchange_durable, auto_delete=self.exchange_auto_delete) def get_channel_id(self): """ Gets the underlying AMQP channel's channel identifier (number). @return Channel id, or None. """ if not self._transport: return None return self._transport.channel_number def reset(self): """ Provides a hook for resetting a node (used only by pooling in the node). At this base level, is a no-op. """ pass def close(self): """ Public close method. If a close callback was specified when creating this instance, it will call that, otherwise it calls close_impl. If created via a Node (99% likely), the Node will take care of calling close_impl for you at the proper time. @returns An Event you can wait on for completion of the close. Will be set automatically if the channel came from the Node's pool. """ ev = Event() if self._close_callback: self._close_callback(self) ev.set() else: self._close_event = ev self._fsm.process(self.I_CLOSE) return ev def _on_close(self, fsm): self.close_impl() def close_impl(self): """ Closes the AMQP connection. """ log.trace("BaseChannel.close_impl (%s)", self.get_channel_id()) if self._transport: # the close destroys self._transport, so keep a ref here transport = self._transport with self._ensure_transport(): transport.close() # set to None now so nothing else tries to use the channel during the callback self._transport = None def on_channel_open(self, transport): """ Node calls here to attach a bound transport. """ transport.add_on_close_callback(self.on_channel_close) self.attach_transport(transport) def attach_transport(self, transport): """ Attaches a bound transport and indicates this channel is now open. """ self._transport = transport self._lock = coros.RLock() self._fsm.process(self.I_ATTACH) def set_closed_error_callback(self, callback): """ Sets the closed error callback. This callback is called when the underlying transport closes early with an error. This is typically used for internal operations with the broker and will likely not be used by others. @param callback The callback to trigger. Should take three parameters, this channel, the error code, and the error text. @returns The former value of the closed error callback. """ oldval = self._closed_error_callback self._closed_error_callback = callback return oldval @contextmanager def push_closed_error_callback(self, callback): """ Context manager based approach to set_closed_error_callback. """ cur_cb = self.set_closed_error_callback(callback) try: yield callback finally: self.set_closed_error_callback(cur_cb) def on_channel_close(self, transport, code, text): """ Callback for when the Pika channel is closed. """ # make callback to user event if we've closed if self._close_event is not None: self._close_event.set() self._close_event = None # remove transport so we don't try to use it again # (all?) calls are protected via _ensure_transport, which raise a ChannelError if you try to do anything with it. self._transport = None # make callback if it exists! if not (code == 0 or code == 200) and self._closed_error_callback: # run in try block because this can shutter the entire connection try: self._closed_error_callback(self, code, text) except Exception, e: log.warn("Closed error callback caught an exception: %s", str(e)) # fixup channel state fsm, but only if we're not executing a transition right now if self._fsm.current_state != self.S_CLOSED and self._fsm.next_state is None: self._fsm.current_state = self.S_CLOSED
class BaseChannel(object): _amq_chan = None # underlying transport _close_callback = None # close callback to use when closing, not always set (used for pooling) _closed_error_callback = None # callback which triggers when the underlying transport closes with error _exchange = None # exchange (too AMQP specific) # exchange related settings @TODO: these should likely come from config instead _exchange_type = 'topic' _exchange_auto_delete = True _exchange_durable = False # States, Inputs for FSM S_INIT = 'INIT' S_ACTIVE = 'ACTIVE' S_CLOSED = 'CLOSED' I_ATTACH = 'ATTACH' I_CLOSE = 'CLOSE' def __init__(self, transport=None, close_callback=None): """ Initializes a BaseChannel instance. @param transport Underlying transport used for broker communication. Can be None, if so, will use the AMQPTransport stateless singleton. @type transport BaseTransport @param close_callback The method to invoke when close() is called on this BaseChannel. May be left as None, in which case close_impl() will be called. This expects to be a callable taking one param, this channel instance. """ self.set_close_callback(close_callback) self._transport = transport or AMQPTransport.get_instance() # setup FSM for BaseChannel / SendChannel tree self._fsm = FSM(self.S_INIT) self._fsm.add_transition(self.I_ATTACH, self.S_INIT, None, self.S_ACTIVE) self._fsm.add_transition(self.I_CLOSE, self.S_ACTIVE, self._on_close, self.S_CLOSED) self._fsm.add_transition(self.I_CLOSE, self.S_CLOSED, None, self.S_CLOSED) # closed is a goal state, multiple closes are ok and are no-ops self._fsm.add_transition(self.I_CLOSE, self.S_INIT, None, self.S_CLOSED) # INIT to CLOSED is fine too def set_close_callback(self, close_callback): """ Sets a callback method to be called when this channel's close method is invoked. By default, if no callback is set on this channel, close_impl() is called instead. The close callback is chiefly used to pool channels in the Node. @param close_callback The callback method. Should be a callable taking one argument, this channel. """ self._close_callback = close_callback def _ensure_amq_chan(self): """ Ensures this Channel has been activated with the Node. """ # log.debug("BaseChannel._ensure_amq_chan (current: %s)", self._amq_chan is not None) if not self._amq_chan: raise ChannelError("No amq_chan attached") def _declare_exchange(self, exchange): """ Performs an AMQP exchange declare. @param exchange The name of the exchange to use. @TODO: this really shouldn't exist, messaging layer should not make this declaration. it will be provided. perhaps push into an ion layer derivation to help bootstrapping / getting started fast. """ self._exchange = exchange assert self._exchange self._ensure_amq_chan() assert self._transport # log.debug("Exchange declare: %s, TYPE %s, DUR %s AD %s", self._exchange, self._exchange_type, # self._exchange_durable, self._exchange_auto_delete) self._transport.declare_exchange_impl(self._amq_chan, self._exchange, exchange_type=self._exchange_type, durable=self._exchange_durable, auto_delete=self._exchange_auto_delete) def attach_underlying_channel(self, amq_chan): """ Attaches an AMQP channel and indicates this channel is now open. """ self._amq_chan = amq_chan self._fsm.process(self.I_ATTACH) def get_channel_id(self): """ Gets the underlying AMQP channel's channel identifier (number). @return Channel id, or None. """ if not self._amq_chan: return None return self._amq_chan.channel_number def reset(self): """ Provides a hook for resetting a node (used only by pooling in the node). At this base level, is a no-op. """ pass def close(self): """ Public close method. If a close callback was specified when creating this instance, it will call that, otherwise it calls close_impl. If created via a Node (99% likely), the Node will take care of calling close_impl for you at the proper time. """ if self._close_callback: self._close_callback(self) else: self._fsm.process(self.I_CLOSE) def _on_close(self, fsm): self.close_impl() def close_impl(self): """ Closes the AMQP connection. """ log.debug("BaseChannel.close_impl (%s)", self.get_channel_id()) if self._amq_chan: # the close destroys self._amq_chan, so keep a ref here amq_chan = self._amq_chan amq_chan.close() # set to None now so nothing else tries to use the channel during the callback self._amq_chan = None # PIKA BUG: in v0.9.5, this amq_chan instance will be left around in the callbacks # manager, and trips a bug in the handler for on_basic_deliver. We attempt to clean # up for Pika here so we don't goof up when reusing a channel number. amq_chan.callbacks.remove(amq_chan.channel_number, 'Basic.GetEmpty') amq_chan.callbacks.remove(amq_chan.channel_number, 'Channel.Close') amq_chan.callbacks.remove(amq_chan.channel_number, '_on_basic_deliver') amq_chan.callbacks.remove(amq_chan.channel_number, '_on_basic_get') # uncomment these lines to see the full callback list that Pika maintains #stro = pprint.pformat(callbacks._callbacks) #log.error(str(stro)) def on_channel_open(self, amq_chan): """ The node calls here to attach an open Pika channel. We attach our on_channel_close handler and then call attach_underlying_channel. """ amq_chan.add_on_close_callback(self.on_channel_close) self.attach_underlying_channel(amq_chan) def set_closed_error_callback(self, callback): """ Sets the closed error callback. This callback is called when the underlying transport closes early with an error. This is typically used for internal operations with the broker and will likely not be used by others. @param callback The callback to trigger. Should take three parameters, this channel, the error code, and the error text. @returns The former value of the closed error callback. """ oldval = self._closed_error_callback self._closed_error_callback = callback return oldval @contextmanager def push_closed_error_callback(self, callback): """ Context manager based approach to set_closed_error_callback. """ cur_cb = self.set_closed_error_callback(callback) try: yield callback finally: self.set_closed_error_callback(cur_cb) def on_channel_close(self, code, text): """ Callback for when the Pika channel is closed. """ logmeth = log.debug if not (code == 0 or code == 200): logmeth = log.error logmeth("BaseChannel.on_channel_close\n\tchannel number: %s\n\tcode: %d\n\ttext: %s", self.get_channel_id(), code, text) # remove amq_chan so we don't try to use it again # (all?) calls are protected via _ensure_amq_chan, which raise a ChannelError if you try to do anything with it. self._amq_chan = None # make callback if it exists! if not (code == 0 or code == 200) and self._closed_error_callback: # run in try block because this can shutter the entire connection try: self._closed_error_callback(self, code, text) except Exception, e: log.warn("Closed error callback caught an exception: %s", str(e))
class BaseChannel(object): _amq_chan = None # underlying transport _close_callback = None # close callback to use when closing, not always set (used for pooling) _closed_error_callback = None # callback which triggers when the underlying transport closes with error _exchange = None # exchange (too AMQP specific) # exchange related settings @TODO: these should likely come from config instead _exchange_type = 'topic' _exchange_auto_delete = True _exchange_durable = False # States, Inputs for FSM S_INIT = 'INIT' S_ACTIVE = 'ACTIVE' S_CLOSED = 'CLOSED' I_ATTACH = 'ATTACH' I_CLOSE = 'CLOSE' def __init__(self, transport=None, close_callback=None): """ Initializes a BaseChannel instance. @param transport Underlying transport used for broker communication. Can be None, if so, will use the AMQPTransport stateless singleton. @type transport BaseTransport @param close_callback The method to invoke when close() is called on this BaseChannel. May be left as None, in which case close_impl() will be called. This expects to be a callable taking one param, this channel instance. """ self.set_close_callback(close_callback) self._transport = transport or AMQPTransport.get_instance() # setup FSM for BaseChannel / SendChannel tree self._fsm = FSM(self.S_INIT) self._fsm.add_transition(self.I_ATTACH, self.S_INIT, None, self.S_ACTIVE) self._fsm.add_transition(self.I_CLOSE, self.S_ACTIVE, self._on_close, self.S_CLOSED) self._fsm.add_transition( self.I_CLOSE, self.S_CLOSED, None, self.S_CLOSED ) # closed is a goal state, multiple closes are ok and are no-ops self._fsm.add_transition(self.I_CLOSE, self.S_INIT, None, self.S_CLOSED) # INIT to CLOSED is fine too def set_close_callback(self, close_callback): """ Sets a callback method to be called when this channel's close method is invoked. By default, if no callback is set on this channel, close_impl() is called instead. The close callback is chiefly used to pool channels in the Node. @param close_callback The callback method. Should be a callable taking one argument, this channel. """ self._close_callback = close_callback def _ensure_amq_chan(self): """ Ensures this Channel has been activated with the Node. """ # log.debug("BaseChannel._ensure_amq_chan (current: %s)", self._amq_chan is not None) if not self._amq_chan: raise ChannelError("No amq_chan attached") def _declare_exchange(self, exchange): """ Performs an AMQP exchange declare. @param exchange The name of the exchange to use. @TODO: this really shouldn't exist, messaging layer should not make this declaration. it will be provided. perhaps push into an ion layer derivation to help bootstrapping / getting started fast. """ self._exchange = exchange assert self._exchange self._ensure_amq_chan() assert self._transport # log.debug("Exchange declare: %s, TYPE %s, DUR %s AD %s", self._exchange, self._exchange_type, # self._exchange_durable, self._exchange_auto_delete) self._transport.declare_exchange_impl( self._amq_chan, self._exchange, exchange_type=self._exchange_type, durable=self._exchange_durable, auto_delete=self._exchange_auto_delete) def attach_underlying_channel(self, amq_chan): """ Attaches an AMQP channel and indicates this channel is now open. """ self._amq_chan = amq_chan self._fsm.process(self.I_ATTACH) def get_channel_id(self): """ Gets the underlying AMQP channel's channel identifier (number). @return Channel id, or None. """ if not self._amq_chan: return None return self._amq_chan.channel_number def reset(self): """ Provides a hook for resetting a node (used only by pooling in the node). At this base level, is a no-op. """ pass def close(self): """ Public close method. If a close callback was specified when creating this instance, it will call that, otherwise it calls close_impl. If created via a Node (99% likely), the Node will take care of calling close_impl for you at the proper time. """ if self._close_callback: self._close_callback(self) else: self._fsm.process(self.I_CLOSE) def _on_close(self, fsm): self.close_impl() def close_impl(self): """ Closes the AMQP connection. """ log.debug("BaseChannel.close_impl (%s)", self.get_channel_id()) if self._amq_chan: # the close destroys self._amq_chan, so keep a ref here amq_chan = self._amq_chan amq_chan.close() # set to None now so nothing else tries to use the channel during the callback self._amq_chan = None # PIKA BUG: in v0.9.5, this amq_chan instance will be left around in the callbacks # manager, and trips a bug in the handler for on_basic_deliver. We attempt to clean # up for Pika here so we don't goof up when reusing a channel number. amq_chan.callbacks.remove(amq_chan.channel_number, 'Basic.GetEmpty') amq_chan.callbacks.remove(amq_chan.channel_number, 'Channel.Close') amq_chan.callbacks.remove(amq_chan.channel_number, '_on_basic_deliver') amq_chan.callbacks.remove(amq_chan.channel_number, '_on_basic_get') # uncomment these lines to see the full callback list that Pika maintains #stro = pprint.pformat(callbacks._callbacks) #log.error(str(stro)) def on_channel_open(self, amq_chan): """ The node calls here to attach an open Pika channel. We attach our on_channel_close handler and then call attach_underlying_channel. """ amq_chan.add_on_close_callback(self.on_channel_close) self.attach_underlying_channel(amq_chan) def set_closed_error_callback(self, callback): """ Sets the closed error callback. This callback is called when the underlying transport closes early with an error. This is typically used for internal operations with the broker and will likely not be used by others. @param callback The callback to trigger. Should take three parameters, this channel, the error code, and the error text. @returns The former value of the closed error callback. """ oldval = self._closed_error_callback self._closed_error_callback = callback return oldval @contextmanager def push_closed_error_callback(self, callback): """ Context manager based approach to set_closed_error_callback. """ cur_cb = self.set_closed_error_callback(callback) try: yield callback finally: self.set_closed_error_callback(cur_cb) def on_channel_close(self, code, text): """ Callback for when the Pika channel is closed. """ logmeth = log.debug if not (code == 0 or code == 200): logmeth = log.error logmeth( "BaseChannel.on_channel_close\n\tchannel number: %s\n\tcode: %d\n\ttext: %s", self.get_channel_id(), code, text) # remove amq_chan so we don't try to use it again # (all?) calls are protected via _ensure_amq_chan, which raise a ChannelError if you try to do anything with it. self._amq_chan = None # make callback if it exists! if not (code == 0 or code == 200) and self._closed_error_callback: # run in try block because this can shutter the entire connection try: self._closed_error_callback(self, code, text) except Exception, e: log.warn("Closed error callback caught an exception: %s", str(e))
class BaseChannel(object): _close_callback = None # close callback to use when closing, not always set (used for pooling) _closed_error_callback = None # callback which triggers when the underlying transport closes with error _exchange = None # exchange (too AMQP specific) _close_event = None # used for giving notice a close was processed # exchange related settings @TODO: these should likely come from config instead _exchange_type = 'topic' _exchange_auto_delete = None _exchange_durable = None # States, Inputs for FSM S_INIT = 'INIT' S_ACTIVE = 'ACTIVE' S_CLOSED = 'CLOSED' I_ATTACH = 'ATTACH' I_CLOSE = 'CLOSE' def __init__(self, close_callback=None): """ Initializes a BaseChannel instance. @param transport Underlying transport used for broker communication. Can be None, if so, will use the AMQPTransport stateless singleton. @type transport BaseTransport @param close_callback The method to invoke when close() is called on this BaseChannel. May be left as None, in which case close_impl() will be called. This expects to be a callable taking one param, this channel instance. """ self.set_close_callback(close_callback) self._transport = None # setup FSM for BaseChannel / SendChannel tree self._fsm = FSM(self.S_INIT) self._fsm.add_transition(self.I_ATTACH, self.S_INIT, None, self.S_ACTIVE) self._fsm.add_transition(self.I_CLOSE, self.S_ACTIVE, self._on_close, self.S_CLOSED) self._fsm.add_transition(self.I_CLOSE, self.S_CLOSED, lambda *args: self.on_channel_close(None, 0, ""), self.S_CLOSED) # closed is a goal state, multiple closes are ok self._fsm.add_transition(self.I_CLOSE, self.S_INIT, None, self.S_CLOSED) # INIT to CLOSED is fine too @property def exchange_auto_delete(self): # Fix OOIION-1710: Added because exchanges get deleted on broker restart if CFG.get_safe('container.messaging.names.durable', False): self._exchange_auto_delete = False return False if self._exchange_auto_delete is not None: return self._exchange_auto_delete if hasattr(self, '_send_name') and hasattr(self._send_name, 'exchange_auto_delete'): return self._send_name.exchange_auto_delete if hasattr(self, '_recv_name') and hasattr(self._recv_name, 'exchange_auto_delete'): return self._recv_name.exchange_auto_delete return True @exchange_auto_delete.setter def exchange_auto_delete(self, value): self._exchange_auto_delete = value @property def exchange_durable(self): # Fix OOIION-1710: Added because exchanges get deleted on broker restart if CFG.get_safe('container.messaging.names.durable', False): self._exchange_durable = True return True if self._exchange_durable is not None: return self._exchange_durable if hasattr(self, '_send_name') and hasattr(self._send_name, 'exchange_durable'): return self._send_name.exchange_durable if hasattr(self, '_recv_name') and hasattr(self._recv_name, 'exchange_durable'): return self._recv_name.exchange_durable return False @exchange_durable.setter def exchange_durable(self, value): self._exchange_durable = value def set_close_callback(self, close_callback): """ Sets a callback method to be called when this channel's close method is invoked. By default, if no callback is set on this channel, close_impl() is called instead. The close callback is chiefly used to pool channels in the Node. @param close_callback The callback method. Should be a callable taking one argument, this channel. """ self._close_callback = close_callback @contextmanager def _ensure_transport(self): """ Ensures this Channel has been activated with the Node. """ # log.debug("BaseChannel._ensure_transport (current: %s)", self._transport is not None) if not self._transport: raise ChannelError("No transport attached") if not self._lock: raise ChannelError("No lock available") # is lock already acquired? spit out a notice if self._lock._is_owned(): log.warn("INTERLEAVE DETECTED:\n\nCURRENT STACK:\n%s\n\nSTACK THAT LOCKED: %s\n", "".join(traceback.format_stack()), "".join(self._lock_trace)) with self._lock: # we could wait and wait, and it gets closed, and unless we check again, we'd never know! if not self._transport: raise ChannelError("No transport attached") self._lock_trace = traceback.format_stack() try: yield finally: self._lock_trace = None def _declare_exchange(self, exchange): """ Performs an AMQP exchange declare. @param exchange The name of the exchange to use. @TODO: this really shouldn't exist, messaging layer should not make this declaration. it will be provided. perhaps push into an ion layer derivation to help bootstrapping / getting started fast. """ self._exchange = exchange assert self._exchange assert self._transport with self._ensure_transport(): # log.debug("Exchange declare: %s, TYPE %s, DUR %s AD %s", self._exchange, self._exchange_type, # self.exchange_durable, self.exchange_auto_delete) self._transport.declare_exchange_impl(self._exchange, exchange_type=self._exchange_type, durable=self.exchange_durable, auto_delete=self.exchange_auto_delete) def get_channel_id(self): """ Gets the underlying AMQP channel's channel identifier (number). @return Channel id, or None. """ if not self._transport: return None return self._transport.channel_number def reset(self): """ Provides a hook for resetting a node (used only by pooling in the node). At this base level, is a no-op. """ pass def close(self): """ Public close method. If a close callback was specified when creating this instance, it will call that, otherwise it calls close_impl. If created via a Node (99% likely), the Node will take care of calling close_impl for you at the proper time. @returns An Event you can wait on for completion of the close. Will be set automatically if the channel came from the Node's pool. """ ev = Event() if self._close_callback: self._close_callback(self) ev.set() else: self._close_event = ev self._fsm.process(self.I_CLOSE) return ev def _on_close(self, fsm): self.close_impl() def close_impl(self): """ Closes the AMQP connection. """ #log.trace("BaseChannel.close_impl (%s)", self.get_channel_id()) if self._transport: # the close destroys self._transport, so keep a ref here transport = self._transport with self._ensure_transport(): transport.close() # set to None now so nothing else tries to use the channel during the callback self._transport = None def on_channel_open(self, transport): """ Node calls here to attach a bound transport. """ transport.add_on_close_callback(self.on_channel_close) self.attach_transport(transport) def attach_transport(self, transport): """ Attaches a bound transport and indicates this channel is now open. """ self._transport = transport self._lock = RLock() self._fsm.process(self.I_ATTACH) def set_closed_error_callback(self, callback): """ Sets the closed error callback. This callback is called when the underlying transport closes early with an error. This is typically used for internal operations with the broker and will likely not be used by others. @param callback The callback to trigger. Should take three parameters, this channel, the error code, and the error text. @returns The former value of the closed error callback. """ oldval = self._closed_error_callback self._closed_error_callback = callback return oldval @contextmanager def push_closed_error_callback(self, callback): """ Context manager based approach to set_closed_error_callback. """ cur_cb = self.set_closed_error_callback(callback) try: yield callback finally: self.set_closed_error_callback(cur_cb) def on_channel_close(self, transport, code, text): """ Callback for when the Pika channel is closed. """ # make callback to user event if we've closed if self._close_event is not None: self._close_event.set() self._close_event = None # remove transport so we don't try to use it again # (all?) calls are protected via _ensure_transport, which raise a ChannelError if you try to do anything with it. self._transport = None # make callback if it exists! if not (code == 0 or code == 200) and self._closed_error_callback: # run in try block because this can shutter the entire connection try: self._closed_error_callback(self, code, text) except Exception as e: log.warn("Closed error callback caught an exception: %s", str(e)) # fixup channel state fsm, but only if we're not executing a transition right now if self._fsm.current_state != self.S_CLOSED and self._fsm.next_state is None: self._fsm.current_state = self.S_CLOSED
class TransformPoll(TransformStreamListener): ''' Transform Extension of Stream Listener which allows the transform to poll and buffer data from the queue, in lieu of consuming automatically. ''' MESSAGE_BUFFER = 1024 TIMEOUT = 2.0 S_INIT = 'INIT' S_READY = 'READY' S_FETCH = 'FETCHED' S_EXCEPTION = 'EXCEPTION' I_READY = 'RESET' I_FETCH = 'FETCH' I_EXCEPTION = 'EXCEPT' def on_start(self): self.queue_name = self.CFG.get_safe('process.queue_name', self.id) self.subscriber = StreamSubscriber(process=self, exchange_name=self.queue_name, callback=self.recv_packet) self._msg_buffer = [] self._fsm = FSM(self.S_INIT) self._fsm.add_transition(self.I_READY, self.S_INIT, None, self.S_READY) self._fsm.add_transition(self.I_FETCH, self.S_READY, None, self.S_FETCH) self._fsm.add_transition(self.I_READY, self.S_FETCH, self._reset, self.S_READY) self._fsm.add_transition(self.I_EXCEPTION, self.S_FETCH, None, self.S_EXCEPTION) self._fsm.add_transition(self.I_READY, self.S_EXCEPTION, self._reset, self.S_READY) self.subscriber.initialize() self.done = gevent.event.Event() self._fsm.process(self.I_READY) self.greenlet = gevent.spawn(self.activate) def on_quit(self): self._ack_all() self.subscriber.close() if self.subscriber._chan._amq_chan is not None: log.error('Channel is still attached, forcing closure.') self.subscriber._chan.close_impl() self.done.set() self.greenlet.join(5) self.greenlet = None def _reset(self, fsm): self._ack_all() def _ack_all(self): while self._msg_buffer: msg = self._msg_buffer.pop() try: msg.ack() except: log.critical('Failed to ack message') def ack_all(self): ''' Acknowledge all the messages in the current buffer. ''' self._fsm.process(self.I_READY) def reject_all(self): ''' Reject all the messages in the curernt buffer. ''' self._reject_all() self._fsm.process(self.I_READY) def _reject_all(self): while self._msg_buffer: msg = self._msg_buffer.pop() try: msg.reject() except: log.critical('Failed to reject message') def poll_trigger(self): ''' Conditional method for determining when to fetch, meant to be overridden. ''' n = self.poll() if n: return True else: return False def poll(self): ''' Returns the number of available messages ''' return self.subscriber.get_stats()[0] def fetch(self): ''' Method for fetching ''' n = self.poll() return self._fetch(n) def _fetch(self, n): ''' Fetches n messages from the queue, The messages must be acknowledged before another fetch can take place ''' self._fsm.process(self.I_FETCH) try: if len(self._msg_buffer) + n >= self.MESSAGE_BUFFER: raise BadRequest('Request exceeds maximum buffer space') try: self._msg_buffer.extend( self.subscriber.get_n_msgs(n, self.TIMEOUT)) except gevent.Timeout: raise Timeout except: self._fsm.process(self.I_EXCEPTION) return [] return [(msg.body, msg.headers) for msg in self._msg_buffer] def activate(self): pass