def test_independent(self): """Test that waiters are independent.""" result = [] def bad(res): result.append(res) raise Exception() def fired(res): result.append(res) e = TwistedEvent() d = e.wait().addCallback(bad) e.wait().addCallback(fired) d.addErrback(lambda _ : None) e.fire() self.assertEqual(result, [True, True])
def test_fire(self): """Test event success.""" result = [] def fired(res): result.append(res) e = TwistedEvent() e.wait().addCallback(fired) self.assertEqual(result, []) e.fire() self.assertEqual(result, [True]) self.assertRaises(AlreadyFiredError, e.fire) self.assertRaises(AlreadyFiredError, e.fail, None) e.wait().addCallback(fired) self.assertEqual(result, [True, True]) e.fail_if_not_fired(None)
class AMQClient(FrameReceiver): channelClass = AMQChannel # Max unreceived heartbeat frames. The AMQP standard says it's 3. MAX_UNSEEN_HEARTBEAT = 3 def __init__(self, delegate, vhost, spec, heartbeat=0, clock=None, insist=False): FrameReceiver.__init__(self, spec) self.delegate = delegate # XXX Cyclic dependency self.delegate.client = self self.vhost = vhost self.channelFactory = type("Channel%s" % self.spec.klass.__name__, (self.channelClass, self.spec.klass), {}) self.channels = {} self.channelLock = defer.DeferredLock() self.outgoing = defer.DeferredQueue() self.work = defer.DeferredQueue() self.started = TwistedEvent() self.disconnected = TwistedEvent() # Fired upon connection shutdown self.closed = False self.queueLock = defer.DeferredLock() self.basic_return_queue = TimeoutDeferredQueue() self.queues = {} self.outgoing.get().addCallback(self.writer) self.work.get().addCallback(self.worker) self.heartbeatInterval = heartbeat self.insist = insist if clock is None: from twisted.internet import reactor clock = reactor self.clock = clock if self.heartbeatInterval > 0: self.checkHB = self.clock.callLater( self.heartbeatInterval * self.MAX_UNSEEN_HEARTBEAT, self.check_heartbeat) self.sendHB = LoopingCall(self.send_heartbeat) self.sendHB.clock = self.clock d = self.started.wait() d.addCallback(lambda _: self.reschedule_send_heartbeat()) d.addCallback(lambda _: self.reschedule_check_heartbeat()) # If self.started fails, don't start the heartbeat. d.addErrback(lambda _: None) def reschedule_send_heartbeat(self): if self.heartbeatInterval > 0: if self.sendHB.running: self.sendHB.stop() self.sendHB.start(self.heartbeatInterval, now=False) def reschedule_check_heartbeat(self): if self.checkHB.active(): self.checkHB.cancel() self.checkHB = self.clock.callLater( self.heartbeatInterval * self.MAX_UNSEEN_HEARTBEAT, self.check_heartbeat) def check_0_8(self): return (self.spec.minor, self.spec.major) == (0, 8) @defer.inlineCallbacks def channel(self, id): yield self.channelLock.acquire() try: try: ch = self.channels[id] except KeyError: ch = self.channelFactory(id, self.outgoing, self) self.channels[id] = ch finally: self.channelLock.release() defer.returnValue(ch) @defer.inlineCallbacks def queue(self, key): yield self.queueLock.acquire() try: try: q = self.queues[key] except KeyError: q = TimeoutDeferredQueue(clock=self.clock) self.queues[key] = q finally: self.queueLock.release() defer.returnValue(q) @defer.inlineCallbacks def close(self, reason=None, within=0): """Explicitely close the connection. @param reason: Optional closing reason. If not given, ConnectionDone will be used. @param within: Shutdown the client within this amount of seconds. If zero (the default), all channels and queues will be closed immediately. If greater than 0, try to close the AMQP connection cleanly, by sending a "close" method and waiting for "close-ok". If no reply is received within the given amount of seconds, the transport will be forcely shutdown. """ if self.closed: return if reason is None: reason = ConnectionDone() if within > 0: channel0 = yield self.channel(0) deferred = channel0.connection_close() call = self.clock.callLater(within, deferred.cancel) try: yield deferred except defer.CancelledError: pass else: call.cancel() self.do_close(reason) def do_close(self, reason): """Called when connection_close() is received""" # Let's close all channels and queues, since we don't want to write # any more data and no further read will happen. for ch in self.channels.values(): ch.close(reason) for q in self.queues.values(): q.close() self.delegate.close(reason) def writer(self, frame): self.send_frame(frame) self.outgoing.get().addCallback(self.writer) def worker(self, queue): d = self.dispatch(queue) def cb(ign): self.work.get().addCallback(self.worker) d.addCallback(cb) d.addErrback(self.close) @defer.inlineCallbacks def dispatch(self, queue): frame = yield queue.get() channel = yield self.channel(frame.channel) payload = frame.payload if payload.method.content: content = yield read_content(queue) else: content = None # Let the caller deal with exceptions thrown here. message = Message(payload.method, payload.args, content) self.delegate.dispatch(channel, message) # As soon as we connect to the target AMQP broker, send the init string def connectionMade(self): self.send_init_string() self.set_frame_mode() def frame_received(self, frame): self.process_frame(frame) def send_frame(self, frame): if frame.payload.type != Frame.HEARTBEAT: self.reschedule_send_heartbeat() FrameReceiver.send_frame(self, frame) @defer.inlineCallbacks def process_frame(self, frame): ch = yield self.channel(frame.channel) if frame.payload.type == Frame.HEARTBEAT: self.last_heartbeat_received = time() else: ch.dispatch(frame, self.work) if self.heartbeatInterval > 0 and not self.closed: self.reschedule_check_heartbeat() @defer.inlineCallbacks def authenticate(self, username, password, mechanism='AMQPLAIN', locale='en_US'): if mechanism == 'AMQPLAIN': response = {"LOGIN": username, "PASSWORD": password} elif mechanism == 'PLAIN': response = "\0" + username + "\0" + password else: raise ValueError('Unknown mechanism:' + mechanism) yield self.start(response, mechanism, locale) @defer.inlineCallbacks def start(self, response, mechanism='AMQPLAIN', locale='en_US'): self.response = response self.mechanism = mechanism self.locale = locale yield self.started.wait() channel0 = yield self.channel(0) if self.check_0_8(): result = yield channel0.connection_open(self.vhost, insist=self.insist) else: result = yield channel0.connection_open(self.vhost) defer.returnValue(result) def send_heartbeat(self): self.send_frame(Frame(0, Heartbeat())) self.last_heartbeat_sent = time() def check_heartbeat(self): if self.checkHB.active(): self.checkHB.cancel() # Abort the connection, since the other pear is unresponsive and # there's no point in shutting down cleanly and trying to flush # pending data. self.transport.abortConnection() def connectionLost(self, reason): if self.heartbeatInterval > 0: if self.sendHB.running: self.sendHB.stop() if self.checkHB.active(): self.checkHB.cancel() self.close(reason) self.disconnected.fire() def channel_failed(self, channel, reason): """Unexpected channel close""" pass