def connect_application_session(server_factory, MyApplicationSession, component_config=None): """ Connect an ApplicationSession class to the given server factory. """ application_session_factory = ApplicationSessionFactory(component_config) application_session_factory.session = MyApplicationSession client_factory = WampRawSocketClientFactory(application_session_factory) # client_factory = WampWebSocketClientFactory(application_session_factory) server_protocol = server_factory.buildProtocol(None) client_protocol = client_factory.buildProtocol(None) server_transport = FakeTransport(server_protocol, True) client_transport = FakeTransport(client_protocol, False) pump = connect(server_protocol, server_transport, client_protocol, client_transport, debug=False) return client_protocol._session, pump
def getMapUpdaterComponentService(self, url, realm, mapUpdater): def create(config): try: session = MapUpdaterComponent(mapUpdater, config) except Exception as e: # the app component could not be created .. fatal print(e) else: session.debug_app = True return session sessionFactory = ApplicationSessionFactory( ComponentConfig(realm, None)) sessionFactory.session = create transportFactory = WampWebSocketClientFactory( sessionFactory, url=url) transportFactory.noisy = False transportFactory.autoPingInterval = 30 transportFactory.autoPingTimeout = 30 isSecure, host, port, resource, path, params = parseWsUrl(url) endpoint = HostnameEndpoint(reactor, host.encode('utf8'), port) if isSecure: contextFactory = optionsForClientTLS(hostname=host) endpoint = wrapClientTLS(contextFactory, endpoint) return ClientService(endpoint, transportFactory)
def connect(self, ip, port, realm): self._realm = realm self._url = 'ws://' + ip + ':' + port + '/ws' self._reactor_thread = None self._session_factoryWriter = None self._session_factoryReader = None cfgReader = ComponentConfig(self._realm, self._extra) cfgWriter = ComponentConfig(self._realm, self._extra) self._session_factoryReader = ApplicationSessionFactory(cfgReader) self._session_factoryReader.session = ClientReader self._session_factoryWriter = ApplicationSessionFactory(cfgWriter) self._session_factoryWriter.session = ClientWriter self._factoryReader = WampWebSocketClientFactory( self._session_factoryReader, url=self._url, # debug = self._debug, debug_wamp = self._debug_wamp ) self._factoryWriter = WampWebSocketClientFactory( self._session_factoryWriter, url=self._url, # debug = self._debug, debug_wamp = self._debug_wamp ) self._reactor_thread = threading.Thread(target=reactor.run, args=(False, )) self._reactor_thread.daemon = True endpoint_descriptor = 'tcp:' + ip + ':' + port self._clientReader = clientFromString(reactor, endpoint_descriptor) self._clientReader.connect(self._factoryReader) self._clientWriter = clientFromString(reactor, endpoint_descriptor) self._clientWriter.connect(self._factoryWriter) self._reactor_thread.start() return self
def wampClient(wampAddress, wampClientEndpoint, topic, key): """ Sets up an Autobahn|python WAMPv2 client. Code modified from WAMP documentation. """ component_config = types.ComponentConfig(realm = "realm1", extra = {'key': unicode(key), 'topic': unicode(topic)}) session_factory = ApplicationSessionFactory(config = component_config) session_factory._myConnection = [] session_factory.session = SubpubTileset ## create a WAMP-over-WebSocket transport client factory #transport_factory = WampWebSocketClientFactory(session_factory, wampAddress, debug = False) transport_factory = MyClientFactory(session_factory, wampAddress, debug = False, debug_wamp = False) transport_factory.setProtocolOptions(failByDrop = False) ## start a WebSocket client from an endpoint client = clientFromString(reactor, wampClientEndpoint) client.connect(transport_factory) return session_factory._myConnection
def test_minimal(self): embedded_components, client_components = [], [ Case2_Backend, Case2_Frontend ] ## create a WAMP router factory ## router_factory = RouterFactory() ## create a WAMP router session factory ## session_factory = RouterSessionFactory(router_factory) ## .. and create and add an WAMP application session to ## run next to the router ## config = types.ComponentConfig(realm=self.realm, extra={'caselog': 'case1.log'}) try: log = io.open('caselog.log', 'w') except Exception as e: print(e) return # log = io.open(config.extra['caselog'], 'w') config.log = log config.dlog = [] config.components = [] config.all_done = [] for C in embedded_components: one_done = Deferred() config.all_done.append(one_done) c = C(config, one_done) config.components.append(c) session_factory.add(c) if self.transport == "websocket": ## create a WAMP-over-WebSocket transport server factory ## transport_factory = WampWebSocketServerFactory( session_factory, debug_wamp=self.debug) transport_factory.setProtocolOptions(failByDrop=False, openHandshakeTimeout=0, closeHandshakeTimeout=0) elif self.transport in ['rawsocket-json', 'rawsocket-msgpack']: ## create a WAMP-over-RawSocket transport server factory ## if self.transport == 'rawsocket-msgpack': serializer = MsgPackSerializer() elif self.transport == 'rawsocket-json': serializer = JsonSerializer() else: raise Exception("should not arrive here") transport_factory = WampRawSocketServerFactory( session_factory, serializer, debug=self.debug) else: raise Exception("should not arrive here") ## start the server from an endpoint ## from twisted.internet import reactor server = serverFromString(reactor, self.server) d = server.listen(transport_factory) def onlisten(port): config.port = port d.addCallback(onlisten) clients = [] clients_d = [] for C in client_components: ## create a WAMP application session factory ## session_factory = ApplicationSessionFactory(config) one_done = Deferred() config.all_done.append(one_done) def make_make(Klass, done): def make(config): c = Klass(config, done) config.components.append(c) return c return make ## .. and set the session class on the factory ## session_factory.session = make_make(C, one_done) if self.transport == "websocket": serializers = [JsonSerializer()] ## create a WAMP-over-WebSocket transport client factory ## transport_factory = WampWebSocketClientFactory( session_factory, serializers=serializers, url=self.url, debug_wamp=self.debug) if True: def maker(Klass): class TestClientProtocol( WampWebSocketClientProtocol): def onOpen(self): self.txcnt = 0 self.rxcnt = 0 WampWebSocketClientProtocol.onOpen(self) def sendMessage(self, bytes, isBinary): self.txcnt += 1 print("> : {:>3} : {:<20} : {}".format( self.txcnt, Klass.__name__, bytes)) WampWebSocketClientProtocol.sendMessage( self, bytes, isBinary) def onMessage(self, bytes, isBinary): self.rxcnt += 1 print("< : {:>3} : {:<20} : {}".format( self.rxcnt, Klass.__name__, bytes)) WampWebSocketClientProtocol.onMessage( self, bytes, isBinary) return TestClientProtocol transport_factory.protocol = maker(C) else: transport_factory.protocol = WampWebSocketClientProtocol transport_factory.setProtocolOptions( failByDrop=False, openHandshakeTimeout=0, closeHandshakeTimeout=0) elif self.transport in ['rawsocket-json', 'rawsocket-msgpack']: ## create a WAMP-over-RawSocket transport client factory ## if self.transport == 'rawsocket-msgpack': serializer = MsgPackSerializer() elif self.transport == 'rawsocket-json': serializer = JsonSerializer() else: raise Exception("should not arrive here") transport_factory = WampRawSocketClientFactory( session_factory, serializer, debug=self.debug) ## start the client from an endpoint ## cl = clientFromString(reactor, self.client) clients_d.append(cl.connect(transport_factory)) clients.append(cl) config.connected_clients = None def client_connected(res): config.connected_clients = [ proto for success, proto in res if success ] DeferredList(clients_d).addCallback(client_connected) d = DeferredList(config.all_done, consumeErrors=True) #d = config.components[1]._done def done(res): log.flush() log.close() if config.port: config.port.stopListening() if config.connected_clients: for proto in config.connected_clients: proto.transport.abortConnection() print("Log length: {}".format(len(config.dlog))) print(config.dlog) #from twisted.internet import reactor #reactor.callLater(1, reactor.stop) def error(err): print(err) d.addCallbacks(done, error) # d2 = Deferred() return d
def run(): """ Entry point into (native) worker processes. This wires up stuff such that a worker instance is talking WAMP-over-stdio to the node controller. """ import os import sys import platform import signal # Ignore SIGINT so we get consistent behavior on control-C versus # sending SIGINT to the controller process. When the controller is # shutting down, it sends TERM to all its children but ctrl-C # handling will send a SIGINT to all the processes in the group # (so then the controller sends a TERM but the child already or # will very shortly get a SIGINT as well). Twisted installs signal # handlers, but not for SIGINT if there's already a custom one # present. def ignore(sig, frame): log.debug("Ignoring SIGINT in worker.") signal.signal(signal.SIGINT, ignore) # create the top-level parser # import argparse parser = argparse.ArgumentParser() parser.add_argument('--reactor', default=None, choices=['select', 'poll', 'epoll', 'kqueue', 'iocp'], help='Explicit Twisted reactor selection (optional).') parser.add_argument('--loglevel', default="info", choices=['none', 'error', 'warn', 'info', 'debug', 'trace'], help='Initial log level.') parser.add_argument('-c', '--cbdir', type=str, help="Crossbar.io node directory (required).") parser.add_argument('-n', '--node', type=str, help='Crossbar.io node ID (required).') parser.add_argument('-w', '--worker', type=str, help='Crossbar.io worker ID (required).') parser.add_argument('-r', '--realm', type=str, help='Crossbar.io node (management) realm (required).') parser.add_argument('-t', '--type', choices=['router', 'container'], help='Worker type (required).') parser.add_argument('--title', type=str, default=None, help='Worker process title to set (optional).') options = parser.parse_args() # make sure logging to something else than stdio is setup _first_ # from crossbar._logging import make_JSON_observer, cb_logging_aware, _stderr from crossbar._logging import make_logger, log_publisher, start_logging from crossbar._logging import set_global_log_level # Set the global log level set_global_log_level(options.loglevel) log = make_logger() # Print a magic phrase that tells the capturing logger that it supports # Crossbar's rich logging print(cb_logging_aware, file=_stderr) _stderr.flush() flo = make_JSON_observer(_stderr) log_publisher.addObserver(flo) start_logging() try: import setproctitle except ImportError: log.debug("Could not set worker process title (setproctitle not installed)") else: # set process title if requested to # if options.title: setproctitle.setproctitle(options.title) else: WORKER_TYPE_TO_TITLE = { 'router': 'crossbar-worker [router]', 'container': 'crossbar-worker [container]' } setproctitle.setproctitle(WORKER_TYPE_TO_TITLE[options.type].strip()) # we use an Autobahn utility to import the "best" available Twisted reactor # from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor(options.reactor) from twisted.python.reflect import qual log.info("Worker running under {python}-{reactor}", python=platform.python_implementation(), reactor=qual(reactor.__class__).split('.')[-1]) options.cbdir = os.path.abspath(options.cbdir) os.chdir(options.cbdir) # log.msg("Starting from node directory {}".format(options.cbdir)) from crossbar.worker.router import RouterWorkerSession from crossbar.worker.container import ContainerWorkerSession WORKER_TYPE_TO_CLASS = { 'router': RouterWorkerSession, 'container': ContainerWorkerSession } from autobahn.twisted.websocket import WampWebSocketServerProtocol class WorkerServerProtocol(WampWebSocketServerProtocol): def connectionLost(self, reason): try: # this log message is unlikely to reach the controller (unless # only stdin/stdout pipes were lost, but not stderr) log.warn("Connection to node controller lost.") WampWebSocketServerProtocol.connectionLost(self, reason) except: pass finally: # losing the connection to the node controller is fatal: # stop the reactor and exit with error log.info("No more controller connection; shutting down.") reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) try: reactor.stop() except ReactorNotRunning: pass try: # create a WAMP application session factory # from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.wamp.types import ComponentConfig session_config = ComponentConfig(realm=options.realm, extra=options) session_factory = ApplicationSessionFactory(session_config) session_factory.session = WORKER_TYPE_TO_CLASS[options.type] # create a WAMP-over-WebSocket transport server factory # from autobahn.twisted.websocket import WampWebSocketServerFactory transport_factory = WampWebSocketServerFactory(session_factory, "ws://localhost", debug=False, debug_wamp=False) transport_factory.protocol = WorkerServerProtocol transport_factory.setProtocolOptions(failByDrop=False) # create a protocol instance and wire up to stdio # from twisted.python.runtime import platform as _platform from twisted.internet import stdio proto = transport_factory.buildProtocol(None) if _platform.isWindows(): stdio.StandardIO(proto) else: stdio.StandardIO(proto, stdout=3) # now start reactor loop # if False: log.info("vmprof enabled.") import os import vmprof PROFILE_FILE = 'vmprof_{}.dat'.format(os.getpid()) outfd = os.open(PROFILE_FILE, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(outfd, period=0.01) log.info("Entering event loop...") reactor.run() vmprof.disable() else: log.debug("Entering event loop...") reactor.run() except Exception as e: log.info("Unhandled exception: {}".format(e)) if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() else: sys.exit(1)
raise Exception("should not arrive here") from autobahn.twisted.websocket import WampWebSocketClientFactory, WampWebSocketClientProtocol ## start the server from an endpoint ## server = serverFromString(reactor, args.server) server.listen(transport_factory) clients = [] clients_d = [] for C in client_components: ## create a WAMP application session factory ## from autobahn.twisted.wamp import ApplicationSessionFactory session_factory = ApplicationSessionFactory(config) one_done = Deferred() config.all_done.append(one_done) def make_make(Klass, done): def make(config): c = Klass(config, done) config.components.append(c) return c return make ## .. and set the session class on the factory ## session_factory.session = make_make(C, one_done)
from autobahn.twisted.choosereactor import install_reactor from autobahn.twisted.websocket import WampWebSocketClientFactory from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.twisted.wamp import ApplicationSession from autobahn.wamp import types class MySession(ApplicationSession): def onJoin(self, details): print("Session attached to realm!") log.startLogging(sys.stdout) reactor = install_reactor() print("Running on reactor {}".format(reactor)) session_factory = ApplicationSessionFactory( types.ComponentConfig(realm=u"realm1")) session_factory.session = MySession transport_factory = WampWebSocketClientFactory(session_factory, url="ws://localhost", debug=True) client = clientFromString(reactor, "unix:/tmp/cbsocket") client.connect(transport_factory) reactor.run()
def run(): """ Entry point into (native) worker processes. This wires up stuff such that a worker instance is talking WAMP-over-stdio to the node controller. """ import os import sys import platform import signal # Ignore SIGINT so we get consistent behavior on control-C versus # sending SIGINT to the controller process. When the controller is # shutting down, it sends TERM to all its children but ctrl-C # handling will send a SIGINT to all the processes in the group # (so then the controller sends a TERM but the child already or # will very shortly get a SIGINT as well). Twisted installs signal # handlers, but not for SIGINT if there's already a custom one # present. def ignore(sig, frame): log.msg("Ignoring SIGINT in worker.") signal.signal(signal.SIGINT, ignore) # create the top-level parser # import argparse parser = argparse.ArgumentParser() parser.add_argument('-d', '--debug', action='store_true', help='Debug on (optional).') parser.add_argument('--reactor', default=None, choices=['select', 'poll', 'epoll', 'kqueue', 'iocp'], help='Explicit Twisted reactor selection (optional).') parser.add_argument('-c', '--cbdir', type=str, help="Crossbar.io node directory (required).") parser.add_argument('-n', '--node', type=str, help='Crossbar.io node ID (required).') parser.add_argument('-w', '--worker', type=str, help='Crossbar.io worker ID (required).') parser.add_argument('-r', '--realm', type=str, help='Crossbar.io node (management) realm (required).') parser.add_argument('-t', '--type', choices=['router', 'container'], help='Worker type (required).') parser.add_argument('--title', type=str, default=None, help='Worker process title to set (optional).') options = parser.parse_args() # make sure logging to something else than stdio is setup _first_ # from twisted.python import log from crossbar.twisted.processutil import BareFormatFileLogObserver flo = BareFormatFileLogObserver(sys.stderr) log.startLoggingWithObserver(flo.emit) try: import setproctitle except ImportError: log.msg( "Warning: could not set worker process title (setproctitle not installed)" ) else: # set process title if requested to # if options.title: setproctitle.setproctitle(options.title) else: WORKER_TYPE_TO_TITLE = { 'router': 'crossbar-worker [router]', 'container': 'crossbar-worker [container]' } setproctitle.setproctitle( WORKER_TYPE_TO_TITLE[options.type].strip()) # we use an Autobahn utility to import the "best" available Twisted reactor # from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor(options.reactor) from twisted.python.reflect import qual log.msg("Running under {} using {} reactor".format( platform.python_implementation(), qual(reactor.__class__).split('.')[-1])) options.cbdir = os.path.abspath(options.cbdir) os.chdir(options.cbdir) # log.msg("Starting from node directory {}".format(options.cbdir)) from crossbar.worker.router import RouterWorkerSession from crossbar.worker.container import ContainerWorkerSession WORKER_TYPE_TO_CLASS = { 'router': RouterWorkerSession, 'container': ContainerWorkerSession } from autobahn.twisted.websocket import WampWebSocketServerProtocol class WorkerServerProtocol(WampWebSocketServerProtocol): def connectionLost(self, reason): try: # this log message is unlikely to reach the controller (unless # only stdin/stdout pipes were lost, but not stderr) log.msg("Connection to node controller lost.") WampWebSocketServerProtocol.connectionLost(self, reason) except: pass finally: # loosing the connection to the node controller is fatal: # stop the reactor and exit with error if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() # if the reactor *isn't* running, we're already shutting down try: # create a WAMP application session factory # from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.wamp.types import ComponentConfig session_config = ComponentConfig(realm=options.realm, extra=options) session_factory = ApplicationSessionFactory(session_config) session_factory.session = WORKER_TYPE_TO_CLASS[options.type] # create a WAMP-over-WebSocket transport server factory # from autobahn.twisted.websocket import WampWebSocketServerFactory transport_factory = WampWebSocketServerFactory(session_factory, "ws://localhost", debug=False, debug_wamp=False) transport_factory.protocol = WorkerServerProtocol transport_factory.setProtocolOptions(failByDrop=False) # create a protocol instance and wire up to stdio # from twisted.internet import stdio proto = transport_factory.buildProtocol(None) stdio.StandardIO(proto) # now start reactor loop # log.msg("Entering event loop ..") reactor.run() except Exception as e: log.msg("Unhandled exception: {}".format(e)) if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() else: sys.exit(1)
## if args.debug: log.startLogging(sys.stdout) ## we use an Autobahn utility to import the "best" available Twisted reactor ## from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor() if args.debug: print("Running on reactor {}".format(reactor)) ## create a WAMP application session factory ## from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.wamp import types session_factory = ApplicationSessionFactory( types.ComponentConfig(realm=args.realm)) ## dynamically load the application component .. ## import importlib c = args.component.split('.') mod, klass = '.'.join(c[:-1]), c[-1] app = importlib.import_module(mod) ## .. and set the session class on the factory ## session_factory.session = getattr(app, klass) if args.transport == "websocket": ## create a WAMP-over-WebSocket transport client factory
from twisted.python import log from twisted.internet.endpoints import clientFromString log.startLogging(sys.stdout) ## we use an Autobahn utility to import the "best" available Twisted reactor ## from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor() print("Running on reactor {}".format(reactor)) ## create a WAMP application session factory ## from autobahn.twisted.wamp import ApplicationSessionFactory session_factory = ApplicationSessionFactory() ## .. and set the session class on the factory ## session_factory.session = MyAppComponent ## since we are running this component as a client, there ## will be only 1 app session instance anyway. We'll store a ## reference on the session factory, so we can access it ## from "outside" the session instance later (see below) ## session_factory._myAppSession = None
def run(options, reactor=None): """ Entry point into (native) worker processes. This wires up stuff such that a worker instance is talking WAMP-over-stdio to the node controller. """ import os import sys import platform import signal # make sure logging to something else than stdio is setup _first_ # from crossbar._logging import make_JSON_observer, cb_logging_aware from txaio import make_logger, start_logging from twisted.logger import globalLogPublisher from twisted.python.reflect import qual log = make_logger() # Print a magic phrase that tells the capturing logger that it supports # Crossbar's rich logging print(cb_logging_aware, file=sys.__stderr__) sys.__stderr__.flush() flo = make_JSON_observer(sys.__stderr__) globalLogPublisher.addObserver(flo) # Ignore SIGINT so we get consistent behavior on control-C versus # sending SIGINT to the controller process. When the controller is # shutting down, it sends TERM to all its children but ctrl-C # handling will send a SIGINT to all the processes in the group # (so then the controller sends a TERM but the child already or # will very shortly get a SIGINT as well). Twisted installs signal # handlers, but not for SIGINT if there's already a custom one # present. def ignore(sig, frame): log.debug("Ignoring SIGINT in worker.") signal.signal(signal.SIGINT, ignore) # actually begin logging start_logging(None, options.loglevel) # we use an Autobahn utility to import the "best" available Twisted reactor # from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor(options.reactor) # eg: crossbar.worker.container.ContainerWorkerSession l = options.klass.split('.') worker_module, worker_klass = '.'.join(l[:-1]), l[-1] # now load the worker module and class _mod = importlib.import_module(worker_module) klass = getattr(_mod, worker_klass) log.info( 'Started {worker_title} worker "{worker_id}" on node "{node_id}" [{klass} / {python}-{reactor}]', worker_title=klass.WORKER_TITLE, klass=options.klass, node_id=options.node, worker_id=options.worker, pid=os.getpid(), python=platform.python_implementation(), reactor=qual(reactor.__class__).split('.')[-1], ) # set process title if requested to # try: import setproctitle except ImportError: log.debug( "Could not set worker process title (setproctitle not installed)") else: if options.title: setproctitle.setproctitle(options.title) else: setproctitle.setproctitle('crossbar-worker [{}]'.format( options.klass)) # node directory # options.cbdir = os.path.abspath(options.cbdir) os.chdir(options.cbdir) # log.msg("Starting from node directory {}".format(options.cbdir)) # set process title if requested to # try: import setproctitle except ImportError: log.debug( "Could not set worker process title (setproctitle not installed)") else: if options.title: setproctitle.setproctitle(options.title) else: setproctitle.setproctitle('crossbar-worker [{}]'.format( options.klass)) from twisted.internet.error import ConnectionDone from autobahn.twisted.websocket import WampWebSocketServerProtocol class WorkerServerProtocol(WampWebSocketServerProtocol): def connectionLost(self, reason): # the behavior here differs slightly whether we're shutting down orderly # or shutting down because of "issues" if isinstance(reason.value, ConnectionDone): was_clean = True else: was_clean = False try: # this log message is unlikely to reach the controller (unless # only stdin/stdout pipes were lost, but not stderr) if was_clean: log.info("Connection to node controller closed cleanly") else: log.warn("Connection to node controller lost: {reason}", reason=reason) # give the WAMP transport a change to do it's thing WampWebSocketServerProtocol.connectionLost(self, reason) except: # we're in the process of shutting down .. so ignore .. pass finally: # after the connection to the node controller is gone, # the worker is "orphane", and should exit # determine process exit code if was_clean: exit_code = 0 else: exit_code = 1 # exit the whole worker process when the reactor has stopped reactor.addSystemEventTrigger('after', 'shutdown', os._exit, exit_code) # stop the reactor try: reactor.stop() except ReactorNotRunning: pass try: # create a WAMP application session factory # from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.wamp.types import ComponentConfig session_config = ComponentConfig(realm=options.realm, extra=options) session_factory = ApplicationSessionFactory(session_config) session_factory.session = klass # create a WAMP-over-WebSocket transport server factory # from autobahn.twisted.websocket import WampWebSocketServerFactory transport_factory = WampWebSocketServerFactory(session_factory, u'ws://localhost') transport_factory.protocol = WorkerServerProtocol transport_factory.setProtocolOptions(failByDrop=False) # create a protocol instance and wire up to stdio # from twisted.python.runtime import platform as _platform from twisted.internet import stdio proto = transport_factory.buildProtocol(None) if _platform.isWindows(): stdio.StandardIO(proto) else: stdio.StandardIO(proto, stdout=3) # now start reactor loop # if False: log.info("vmprof enabled.") import os import vmprof PROFILE_FILE = 'vmprof_{}.dat'.format(os.getpid()) outfd = os.open(PROFILE_FILE, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(outfd, period=0.01) log.info("Entering event loop...") reactor.run() vmprof.disable() else: log.debug("Entering event loop...") reactor.run() except Exception as e: log.info("Unhandled exception: {e}", e=e) if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() else: sys.exit(1)
def run(): """ Entry point into (native) worker processes. This wires up stuff such that a worker instance is talking WAMP-over-stdio to the node controller. """ import os import sys import platform import signal # Ignore SIGINT so we get consistent behavior on control-C versus # sending SIGINT to the controller process. When the controller is # shutting down, it sends TERM to all its children but ctrl-C # handling will send a SIGINT to all the processes in the group # (so then the controller sends a TERM but the child already or # will very shortly get a SIGINT as well). Twisted installs signal # handlers, but not for SIGINT if there's already a custom one # present. def ignore(sig, frame): log.debug("Ignoring SIGINT in worker.") signal.signal(signal.SIGINT, ignore) # create the top-level parser # import argparse parser = argparse.ArgumentParser() parser.add_argument('--reactor', default=None, choices=['select', 'poll', 'epoll', 'kqueue', 'iocp'], help='Explicit Twisted reactor selection (optional).') parser.add_argument( '--loglevel', default="info", choices=['none', 'error', 'warn', 'info', 'debug', 'trace'], help='Initial log level.') parser.add_argument('-c', '--cbdir', type=six.text_type, help="Crossbar.io node directory (required).") parser.add_argument('-r', '--realm', type=six.text_type, help='Crossbar.io node (management) realm (required).') parser.add_argument('-t', '--type', choices=['router', 'container', 'websocket-testee'], help='Worker type (required).') parser.add_argument('-w', '--worker', type=six.text_type, help='Crossbar.io worker ID (required).') parser.add_argument('--title', type=six.text_type, default=None, help='Worker process title to set (optional).') parser.add_argument( '--expose_controller', type=bool, default=False, help= 'Expose node controller session to all components (this feature requires Crossbar.io Fabric extension).' ) parser.add_argument( '--expose_shared', type=bool, default=False, help= 'Expose a shared object to all components (this feature requires Crossbar.io Fabric extension).' ) options = parser.parse_args() # make sure logging to something else than stdio is setup _first_ # from crossbar._logging import make_JSON_observer, cb_logging_aware from txaio import make_logger, start_logging from twisted.logger import globalLogPublisher log = make_logger() # Print a magic phrase that tells the capturing logger that it supports # Crossbar's rich logging print(cb_logging_aware, file=sys.__stderr__) sys.__stderr__.flush() flo = make_JSON_observer(sys.__stderr__) globalLogPublisher.addObserver(flo) start_logging(None, options.loglevel) # we use an Autobahn utility to import the "best" available Twisted reactor # from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor(options.reactor) WORKER_TYPE_TO_TITLE = { 'router': 'Router', 'container': 'Container', 'websocket-testee': 'WebSocket Testee' } from twisted.python.reflect import qual log.info( '{worker_title} worker "{worker_id}" process {pid} starting on {python}-{reactor} ..', worker_title=WORKER_TYPE_TO_TITLE[options.type], worker_id=options.worker, pid=os.getpid(), python=platform.python_implementation(), reactor=qual(reactor.__class__).split('.')[-1]) # set process title if requested to # try: import setproctitle except ImportError: log.debug( "Could not set worker process title (setproctitle not installed)") else: if options.title: setproctitle.setproctitle(options.title) else: WORKER_TYPE_TO_PROCESS_TITLE = { 'router': 'crossbar-worker [router]', 'container': 'crossbar-worker [container]', 'websocket-testee': 'crossbar-worker [websocket-testee]' } setproctitle.setproctitle( WORKER_TYPE_TO_PROCESS_TITLE[options.type].strip()) # node directory # options.cbdir = os.path.abspath(options.cbdir) os.chdir(options.cbdir) # log.msg("Starting from node directory {}".format(options.cbdir)) from crossbar.worker.router import RouterWorkerSession from crossbar.worker.container import ContainerWorkerSession from crossbar.worker.testee import WebSocketTesteeWorkerSession WORKER_TYPE_TO_CLASS = { 'router': RouterWorkerSession, 'container': ContainerWorkerSession, 'websocket-testee': WebSocketTesteeWorkerSession } from twisted.internet.error import ConnectionDone from autobahn.twisted.websocket import WampWebSocketServerProtocol class WorkerServerProtocol(WampWebSocketServerProtocol): def connectionLost(self, reason): # the behavior here differs slightly whether we're shutting down orderly # or shutting down because of "issues" if isinstance(reason.value, ConnectionDone): was_clean = True else: was_clean = False try: # this log message is unlikely to reach the controller (unless # only stdin/stdout pipes were lost, but not stderr) if was_clean: log.info("Connection to node controller closed cleanly") else: log.warn("Connection to node controller lost: {reason}", reason=reason) # give the WAMP transport a change to do it's thing WampWebSocketServerProtocol.connectionLost(self, reason) except: # we're in the process of shutting down .. so ignore .. pass finally: # after the connection to the node controller is gone, # the worker is "orphane", and should exit # determine process exit code if was_clean: exit_code = 0 else: exit_code = 1 # exit the whole worker process when the reactor has stopped reactor.addSystemEventTrigger('after', 'shutdown', os._exit, exit_code) # stop the reactor try: reactor.stop() except ReactorNotRunning: pass try: # create a WAMP application session factory # from autobahn.twisted.wamp import ApplicationSessionFactory from autobahn.wamp.types import ComponentConfig session_config = ComponentConfig(realm=options.realm, extra=options) session_factory = ApplicationSessionFactory(session_config) session_factory.session = WORKER_TYPE_TO_CLASS[options.type] # create a WAMP-over-WebSocket transport server factory # from autobahn.twisted.websocket import WampWebSocketServerFactory transport_factory = WampWebSocketServerFactory(session_factory, u'ws://localhost') transport_factory.protocol = WorkerServerProtocol transport_factory.setProtocolOptions(failByDrop=False) # create a protocol instance and wire up to stdio # from twisted.python.runtime import platform as _platform from twisted.internet import stdio proto = transport_factory.buildProtocol(None) if _platform.isWindows(): stdio.StandardIO(proto) else: stdio.StandardIO(proto, stdout=3) # now start reactor loop # if False: log.info("vmprof enabled.") import os import vmprof PROFILE_FILE = 'vmprof_{}.dat'.format(os.getpid()) outfd = os.open(PROFILE_FILE, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(outfd, period=0.01) log.info("Entering event loop...") reactor.run() vmprof.disable() else: log.debug("Entering event loop...") reactor.run() except Exception as e: log.info("Unhandled exception: {e}", e=e) if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() else: sys.exit(1)