def handle_connect_error(fail): unrecoverable_error = False # FIXME - make txaio friendly # Can connect_f ever be in a cancelled state? # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError): # unrecoverable_error = True self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): if fail.value.error in [ u'wamp.error.no_such_realm', u'wamp.error.no_auth_method' ]: unrecoverable_error = True self.log.error(u"Fatal error, not reconnecting") self.log.error(u"{msg}", msg=fail.value.error_message()) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) elif self._is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." self.log.error(u"TLS failure: {reason}", reason=fail.value.args[1]) self.log.error(u"Marking this transport as failed") transport_candidate[0].failed() else: # This is some unknown failure, e.g. could # be SyntaxError etc so we're aborting the # whole mission self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) unrecoverable_error = True if unrecoverable_error: txaio.reject(done_f, fail) return txaio.call_later(0, transport_check, None) return
def main_success(_): self.log.debug("main_success") def leave(): try: session.leave() except SessionNotReady: # someone may have already called # leave() pass txaio.call_later(0, leave)
def test_default_reactor(): """ run the code that defaults txaio.config.loop """ pytest.importorskip('twisted') assert txaio.config.loop is None txaio.call_later(1, lambda: None) from twisted.internet import reactor assert txaio.config.loop is reactor
def test_default_reactor(framework_tx): """ run the code that defaults txaio.config.loop """ pytest.importorskip('twisted') assert txaio.config.loop is None try: txaio.call_later(1, lambda: None) from twisted.internet import reactor assert txaio.config.loop is reactor finally: txaio.config.loop = None
def handle_connect_error(fail): # FIXME - make txaio friendly # Can connect_f ever be in a cancelled state? # if txaio.using_asyncio and isinstance(fail.value, asyncio.CancelledError): # unrecoverable_error = True self.log.debug(u'component failed: {error}', error=txaio.failure_message(fail)) self.log.debug(u'{tb}', tb=txaio.failure_format_traceback(fail)) # If this is a "fatal error" that will never work, # we bail out now if isinstance(fail.value, ApplicationError): self.log.error(u"{msg}", msg=fail.value.error_message()) elif isinstance(fail.value, OSError): # failed to connect entirely, like nobody # listening etc. self.log.info(u"Connection failed: {msg}", msg=txaio.failure_message(fail)) elif self._is_ssl_error(fail.value): # Quoting pyOpenSSL docs: "Whenever # [SSL.Error] is raised directly, it has a # list of error messages from the OpenSSL # error queue, where each item is a tuple # (lib, function, reason). Here lib, function # and reason are all strings, describing where # and what the problem is. See err(3) for more # information." # (and 'args' is a 1-tuple containing the above # 3-tuple...) ssl_lib, ssl_func, ssl_reason = fail.value.args[0][0] self.log.error(u"TLS failure: {reason}", reason=ssl_reason) else: self.log.error( u'Connection failed: {error}', error=txaio.failure_message(fail), ) if self._is_fatal is None: is_fatal = False else: is_fatal = self._is_fatal(fail.value) if is_fatal: self.log.info("Error was fatal; failing transport") transport_candidate[0].failed() txaio.call_later(0, transport_check, None) return
def test_call_later_aio(framework_aio): ''' Wait for two Futures. ''' # Trollius doesn't come with this, so won't work on py2 pytest.importorskip('asyncio.test_utils') def time_gen(): when = yield assert when == 1 # even though we only do one call, I guess TestLoop needs # a "trailing" yield? "or something" when = yield 0 print("Hmmm", when) from asyncio.test_utils import TestLoop new_loop = TestLoop(time_gen) calls = [] with replace_loop(new_loop) as fake_loop: def foo(*args, **kw): calls.append((args, kw)) delay = txaio.call_later(1, foo, 5, 6, 7, foo="bar") assert len(calls) == 0 assert hasattr(delay, 'cancel') fake_loop.advance_time(2) fake_loop._run_once() assert len(calls) == 1 assert calls[0][0] == (5, 6, 7) assert calls[0][1] == dict(foo="bar")
def test_call_later_aio(framework_aio): ''' Wait for two Futures. ''' # Trollius doesn't come with this, so won't work on py2 pytest.importorskip('asyncio.test_utils') def time_gen(): when = yield assert when == 1 # even though we only do one call, I guess TestLoop needs # a "trailing" yield? "or something" when = yield 0 from asyncio.test_utils import TestLoop new_loop = TestLoop(time_gen) calls = [] with replace_loop(new_loop) as fake_loop: def foo(*args, **kw): calls.append((args, kw)) delay = txaio.call_later(1, foo, 5, 6, 7, foo="bar") assert len(calls) == 0 assert hasattr(delay, 'cancel') fake_loop.advance_time(2) fake_loop._run_once() assert len(calls) == 1 assert calls[0][0] == (5, 6, 7) assert calls[0][1] == dict(foo="bar")
def _notify_some(receivers): # we do a first pass over the proposed chunk of receivers # because not all of them will have a transport, and if this # will be the last chunk of receivers we need to figure out # which event is last... receivers_this_chunk = [] for receiver in receivers[:chunk_size]: if receiver._session_id and receiver._transport: receivers_this_chunk.append(receiver) else: vanished_receivers.append(receiver) receivers = receivers[chunk_size:] # XXX note there's still going to be some edge-cases here .. if # we are NOT the last chunk, but all the next chunk's receivers # (could be only 1 in that chunk!) vanish before we run our next # batch, then a "last" event will never go out ... # we now actually do the deliveries, but now we know which # receiver is the last one if receivers or not self._router.is_traced: # NOT the last chunk (or we're not traced so don't care) for receiver in receivers_this_chunk: # send out WAMP msg to peer self._router.send(receiver, msg) if self._event_store or storing_event: self._event_store.store_event_history(publication, subscription.id, receiver) else: # last chunk, so last receiver gets the different message for receiver in receivers_this_chunk[:-1]: self._router.send(receiver, msg) if self._event_store or storing_event: self._event_store.store_event_history(publication, subscription.id, receiver) # FIXME: I don't get the following comment and code path. when, how? and what to # do about event store? => storing_event # # we might have zero valid receivers if receivers_this_chunk: self._router.send(receivers_this_chunk[-1], last_msg) # FIXME: => storing_event if receivers: # still more to do .. return txaio.call_later(0, _notify_some, receivers) else: # all done! resolve all_d, which represents all receivers # to a single subscription matching the event txaio.resolve(all_d, None)
def _notify_some(receivers): for receiver in receivers[:chunk_size]: if (me_also or receiver != session) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and receiver._transport: self._router.send(receiver, msg) receivers = receivers[chunk_size:] if len(receivers) > 0: return txaio.call_later(0, _notify_some, receivers) else: txaio.resolve(all_d, None)
def test_asyncio_component(event_loop): orig_loop = txaio.config.loop txaio.config.loop = event_loop comp = Component( transports=[ { "url": "ws://localhost:12/bogus", "max_retries": 1, "max_retry_delay": 0.1, } ] ) # if having trouble, try starting some logging (and use # "py.test -s" to get real-time output) # txaio.start_logging(level="debug") f = comp.start(loop=event_loop) txaio.config.loop = event_loop finished = txaio.create_future() def fail(): finished.set_exception(AssertionError("timed out")) txaio.config.loop = orig_loop txaio.call_later(4.0, fail) def done(f): try: f.result() finished.set_exception(AssertionError("should get an error")) except RuntimeError as e: if 'Exhausted all transport connect attempts' not in str(e): finished.set_exception(AssertionError("wrong exception caught")) finished.set_result(None) txaio.config.loop = orig_loop assert comp._done_f is None f.add_done_callback(done) return finished
def _notify_some(receivers): # we do a first pass over the proposed chunk of receivers # because not all of them will have a transport, and if this # will be the last chunk of receivers we need to figure out # which event is last... receivers_this_chunk = [] for receiver in receivers[:chunk_size]: if (me_also or receiver != session ) and receiver != self._event_store: # the receiving subscriber session might have no transport, # or no longer be joined if receiver._session_id and receiver._transport: receivers_this_chunk.append( receiver) else: vanished_receivers.append(receiver) receivers = receivers[chunk_size:] # XXX note there's still going to be some edge-cases here .. if # we are NOT the last chunk, but all the next chunk's receivers # (could be only 1 in that chunk!) vanish before we run our next # batch, then a "last" event will never go out ... # we now actually do the deliveries, but now we know which # receiver is the last one if receivers or not self._router.is_traced: # NOT the last chunk (or we're not traced so don't care) for receiver in receivers_this_chunk: self._router.send(receiver, msg) else: # last chunk, so last receiver gets the different message for receiver in receivers_this_chunk[:-1]: self._router.send(receiver, msg) # we might have zero valid receivers if receivers_this_chunk: self._router.send( receivers_this_chunk[-1], last_msg) if receivers: # still more to do .. return txaio.call_later( 0, _notify_some, receivers) else: # all done! resolve all_d, which represents all receivers # to a single subscription matching the event txaio.resolve(all_d, None)
def test_call_later(): ''' Wait for two Futures. ''' # set up a test reactor or event-loop depending on asyncio or # Twisted twisted = False try: from twisted.internet.task import Clock new_loop = Clock() twisted = True except ImportError: # Trollius doesn't come with this, so won't work on py2 pytest.importorskip('asyncio.test_utils') def time_gen(): when = yield assert when == 1 # even though we only do one call, I guess TestLoop needs # a "trailing" yield? "or something" when = yield 0 print("Hmmm", when) from asyncio.test_utils import TestLoop new_loop = TestLoop(time_gen) calls = [] with replace_loop(new_loop) as fake_loop: def foo(*args, **kw): calls.append((args, kw)) delay = txaio.call_later(1, foo, 5, 6, 7, foo="bar") assert len(calls) == 0 assert hasattr(delay, 'cancel') if twisted: fake_loop.advance(2) else: # XXX maybe we monkey-patch a ".advance()" onto asyncio # loops that does both of these? fake_loop.advance_time(2) fake_loop._run_once() assert len(calls) == 1 assert calls[0][0] == (5, 6, 7) assert calls[0][1] == dict(foo="bar")
def _notify_some(receivers): for receiver in receivers[:chunk_size]: if ( me_also or receiver != session ) and receiver != self._event_store: # the receiving subscriber session # might have no transport, or no # longer be joined if receiver._session_id and receiver._transport: self._router.send( receiver, msg) receivers = receivers[chunk_size:] if len(receivers) > 0: # still more to do .. return txaio.call_later( 0, _notify_some, receivers) else: # all done! resolve all_d, which represents all receivers # to a single subscription matching the event txaio.resolve(all_d, None)
def test_call_later_tx(framework_tx): ''' Wait for two Futures. ''' from twisted.internet.task import Clock new_loop = Clock() calls = [] with replace_loop(new_loop) as fake_loop: def foo(*args, **kw): calls.append((args, kw)) delay = txaio.call_later(1, foo, 5, 6, 7, foo="bar") assert len(calls) == 0 assert hasattr(delay, 'cancel') fake_loop.advance(2) assert len(calls) == 1 assert calls[0][0] == (5, 6, 7) assert calls[0][1] == dict(foo="bar")
def callRemoteMessage(self, mcall, timeout = None): """ Uses the specified L{message.MethodCallMessage} to call a remote method. @rtype: L{twisted.internet.defer.Deferred} @returns: a Deferred to the result of the remote method call """ assert isinstance(mcall, message.MethodCallMessage) if mcall.expectReply: d = txaio.create_future() if timeout: timeout = txaio.call_later(timeout, self._onMethodTimeout, mcall.serial, d) self._pendingCalls[ mcall.serial ] = (d, timeout) self.sendMessage( mcall ) return d else: self.sendMessage( mcall ) return txaio.create_future_success(None)
def test_asyncio_component_404(event_loop): """ If something connects but then gets aborted, it should still try to re-connect (in real cases this could be e.g. wrong path, TLS failure, WebSocket handshake failure, etc) """ orig_loop = txaio.config.loop txaio.config.loop = event_loop class FakeTransport(object): def close(self): pass def write(self, data): pass fake_transport = FakeTransport() actual_protocol = [None] # set in a closure below def create_connection(protocol_factory=None, server_hostname=None, host=None, port=None, ssl=False): if actual_protocol[0] is None: protocol = protocol_factory() actual_protocol[0] = protocol protocol.connection_made(fake_transport) return txaio.create_future_success((fake_transport, protocol)) else: return txaio.create_future_error(RuntimeError("second connection fails completely")) with mock.patch.object(event_loop, 'create_connection', create_connection): event_loop.create_connection = create_connection comp = Component( transports=[ { "url": "ws://localhost:12/bogus", "max_retries": 1, "max_retry_delay": 0.1, } ] ) # if having trouble, try starting some logging (and use # "py.test -s" to get real-time output) # txaio.start_logging(level="debug") f = comp.start(loop=event_loop) txaio.config.loop = event_loop # now that we've started connecting, we *should* be able # to connetion_lost our transport .. but we do a # call-later to ensure we're after the setup stuff in the # event-loop (because asyncio doesn't synchronously # process already-completed Futures like Twisted does) def nuke_transport(): actual_protocol[0].connection_lost(None) # asyncio can call this with None txaio.call_later(0.1, nuke_transport) finished = txaio.create_future() def fail(): finished.set_exception(AssertionError("timed out")) txaio.config.loop = orig_loop txaio.call_later(1.0, fail) def done(f): try: f.result() finished.set_exception(AssertionError("should get an error")) except RuntimeError as e: if 'Exhausted all transport connect attempts' not in str(e): finished.set_exception(AssertionError("wrong exception caught")) finished.set_result(None) txaio.config.loop = orig_loop f.add_done_callback(done) return finished
async def generate_otp(self): key = str(random.randint(100000, 999999)) self._pending_otps.append(key) txaio.call_later(60, self._revoke_otp, key) return key