def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5): self.width = 80 self.height = 25 self.id = id self.manager = manager self.autoclose = autoclose self.autoclose_retain = autoclose_retain self.output = BroadcastQueue() env = {} env.update(os.environ) env['TERM'] = 'linux' env['COLUMNS'] = str(self.width) env['LINES'] = str(self.height) env['LC_ALL'] = 'en_US.UTF8' self.command = command if not self.command: shell = os.environ.get('SHELL', None) if not shell: for sh in ['zsh', 'bash', 'sh']: try: shell = subprocess.check_output(['which', sh]) break except: pass self.command = shell args = ['sh', '-c', self.command] exe = 'sh' logging.info('Activating new terminal: %s', self.command) self.pid, self.fd = pty.fork() if self.pid == 0: setproctitle.setproctitle('%s terminal session #%i' % (sys.argv[0], os.getpid())) os.execvpe(exe, args, env) logging.info('Subprocess PID %s', self.pid) self.dead = False fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) self.stream_in = os.fdopen(self.fd, 'rb', 0) self.stream_out = os.fdopen(self.fd, 'wb', 0) self.pyte_stream = pyte.Stream() self.screen = pyte.DiffScreen(self.width, self.height) self.pyte_stream.attach(self.screen) self.last_cursor_position = None self.reader = gevent.spawn(self.reader_fn)
def __init__(self, session, name=None, log_tag=None, restricted=False, initial_identity=None): self.session = session self.process = None self.stream = None self.stream_reader = None self.worker = None self.name = name self.log_tag = log_tag self.restricted = restricted self.initial_identity = initial_identity self.q_http_replies = BroadcastQueue() self.q_socket_messages = BroadcastQueue()
class Push(): """ A service providing push messages to the client. """ def __init__(self, context): self.q = BroadcastQueue() def register(self): return self.q.register() def push(self, plugin, msg): """ Sends a push message to the client. :param plugin: routing ID :param msg: message """ self.q.broadcast((plugin, msg))
class Push(object): """ A service providing push messages to the client. """ def __init__(self, context): self.q = BroadcastQueue() def register(self): return self.q.register() def push(self, plugin, msg): """ Sends a push message to the client. :param plugin: routing ID :param msg: message """ self.q.broadcast((plugin, msg))
class WorkerGate(object): def __init__(self, session, name=None, log_tag=None, restricted=False, initial_identity=None): self.session = session self.process = None self.stream = None self.stream_reader = None self.worker = None self.name = name self.log_tag = log_tag self.restricted = restricted self.initial_identity = initial_identity self.q_http_replies = BroadcastQueue() self.q_socket_messages = BroadcastQueue() def start(self): pipe_parent, pipe_child = gipc.pipe( duplex=True, encoder=lambda x: pickle.dumps(x, 2), ) self.stream = GateStreamServerEndpoint(pipe_parent) stream_child = GateStreamWorkerEndpoint(pipe_child) self.process = gipc.start_process(target=self._target, kwargs={ 'stream': stream_child, '_pipe': pipe_child, }) logging.debug('Started child process %s', self.process.pid) self.stream_reader = gevent.spawn(self._stream_reader) def stop(self): logging.debug('Stopping child process %s', self.process.pid) try: os.killpg(self.process.pid, signal.SIGTERM) except OSError as e: logging.debug('Child process %s is already dead: %s', self.process.pid, e) return self.process.terminate() self.process.join(0.125) try: os.kill(self.process.pid, 0) logging.debug('Child process %s did not stop, killing', self.process.pid) os.kill(self.process.pid, signal.SIGKILL) os.killpg(self.process.pid, signal.SIGKILL) os.kill(self.process.pid, 0) logging.error('Child process %s did not stop after SIGKILL!', self.process.pid) except OSError: pass self.stream_reader.kill(block=False) def _stream_reader(self): try: while True: resp = self.stream.buffer_single_response(None) if not resp: return self.stream.ack_response(resp.id) if resp.object['type'] == 'socket': self.q_socket_messages.broadcast(resp) if resp.object['type'] == 'http': self.q_http_replies.broadcast(resp) if resp.object['type'] == 'terminate': self.session.deactivate() if resp.object['type'] == 'restart-master': aj.restart() if resp.object['type'] == 'reload-config': aj.config.load() aj.config.ensure_structure() self.stream.send({ 'type': 'config-data', 'data': aj.config.data, }) if resp.object['type'] == 'log': method = { 'info': logging.info, 'warn': logging.warn, 'debug': logging.debug, 'error': logging.error, 'critical': logging.critical, }.get(resp.object['method'], None) if method: method(resp.object['message'], extra=resp.object['kwargs']) except greenlet.GreenletExit: pass def _target(self, stream=None, _pipe=None): self.worker = Worker(stream, self) self.worker.run()
def __init__(self, context): self.context = context self.tasks = {} self.notifications = BroadcastQueue()
def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5, redirect=None): """ Setup the environment for a new terminal. :param manager: TerminalManager object :type manager: TerminalManager :param id: Id fo the terminal :type id: hex :param command: Command to run :type command: string :param autoclose: Parameter to close the terminal after the command :type autoclose: bool :param autoclose_retain: Time to wait before closing in seconds :type autoclose_retain: integer :param redirect: Default redirect URL after closing :type redirect: string """ self.width = 80 self.height = 25 self.id = id self.manager = manager self.autoclose = autoclose self.autoclose_retain = autoclose_retain if redirect: self.redirect = redirect else: self.redirect = '/view/terminal' self.output = BroadcastQueue() env = {} env.update(os.environ) env['TERM'] = 'linux' env['COLUMNS'] = str(self.width) env['LINES'] = str(self.height) env['LC_ALL'] = 'en_US.UTF8' self.command = command if not self.command: shell = os.environ.get('SHELL', None) if not shell: for sh in ['zsh', 'bash', 'sh']: try: shell = subprocess.check_output(['which', sh]) break except Exception as e: pass self.command = shell args = ['sh', '-c', self.command] exe = 'sh' logging.info(f'Activating new terminal: {self.command}') self.pid, self.fd = pty.fork() if self.pid == 0: setproctitle.setproctitle( f'{sys.argv[0]} terminal session #{os.getpid():d}') os.execvpe(exe, args, env) logging.info(f'Subprocess PID {self.pid}') self.dead = False fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) self.stream_in = os.fdopen(self.fd, 'rb', 0) self.stream_out = os.fdopen(self.fd, 'wb', 0) self.pyte_stream = pyte.Stream() self.screen = pyte.DiffScreen(self.width, self.height) self.pyte_stream.attach(self.screen) self.last_cursor_position = None self.reader = gevent.spawn(self.reader_fn)
class Terminal(): """ Terminal emulator based on pyte. """ def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5, redirect=None): """ Setup the environment for a new terminal. :param manager: TerminalManager object :type manager: TerminalManager :param id: Id fo the terminal :type id: hex :param command: Command to run :type command: string :param autoclose: Parameter to close the terminal after the command :type autoclose: bool :param autoclose_retain: Time to wait before closing in seconds :type autoclose_retain: integer :param redirect: Default redirect URL after closing :type redirect: string """ self.width = 80 self.height = 25 self.id = id self.manager = manager self.autoclose = autoclose self.autoclose_retain = autoclose_retain if redirect: self.redirect = redirect else: self.redirect = '/view/terminal' self.output = BroadcastQueue() env = {} env.update(os.environ) env['TERM'] = 'linux' env['COLUMNS'] = str(self.width) env['LINES'] = str(self.height) env['LC_ALL'] = 'en_US.UTF8' self.command = command if not self.command: shell = os.environ.get('SHELL', None) if not shell: for sh in ['zsh', 'bash', 'sh']: try: shell = subprocess.check_output(['which', sh]) break except Exception as e: pass self.command = shell args = ['sh', '-c', self.command] exe = 'sh' logging.info(f'Activating new terminal: {self.command}') self.pid, self.fd = pty.fork() if self.pid == 0: setproctitle.setproctitle( f'{sys.argv[0]} terminal session #{os.getpid():d}') os.execvpe(exe, args, env) logging.info(f'Subprocess PID {self.pid}') self.dead = False fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) self.stream_in = os.fdopen(self.fd, 'rb', 0) self.stream_out = os.fdopen(self.fd, 'wb', 0) self.pyte_stream = pyte.Stream() self.screen = pyte.DiffScreen(self.width, self.height) self.pyte_stream.attach(self.screen) self.last_cursor_position = None self.reader = gevent.spawn(self.reader_fn) def reader_fn(self): """ Infinite reader of the file descriptor associated with the shell. """ while True: wait_read(self.fd) self.run_single_read() self._check() if self.dead: return def run_single_read(self): """ Propagate the new data read in the file descriptor. """ try: data = self.stream_in.read() except IOError: data = '' if data: try: self.pyte_stream.feed(data.decode('utf-8')) self.broadcast_update() except UnicodeDecodeError: pass def broadcast_update(self): """ Send the new formated data to the broadcast queue. """ self.output.broadcast(self.format()) def _check(self): """ Check if a child process of the shell pid is available. """ try: pid, status = os.waitpid(self.pid, os.WNOHANG) except OSError: self.on_died(code=0) return if pid: self.on_died(code=status) def on_died(self, code=0): """ Change terminal status to dead, broadcast new status and autoclose it. :param code: Status code of the pid :type code: integer """ if self.dead: return logging.info(f'Terminal {self.id} has died') self.dead = True if code: self.pyte_stream.feed('\n\n * ' + f'Process has exited with status {code}') else: self.pyte_stream.feed('\n\n * ' + 'Process has exited successfully') self.broadcast_update() if self.autoclose: gevent.spawn_later(self.autoclose_retain, self.manager.remove, self.id) """ TODO if self.callback: try: self.callback(exitcode=code) except TypeError: self.callback() """ def has_updates(self): """ Test if the user made some changes. :return: Bool :rtype: bool """ if self.last_cursor_position != (self.screen.cursor.x, self.screen.cursor.y): return True return len(self.screen.dirty) > 0 def format(self, full=False): """ Prepare the terminal content and attributes to send to the frontend. :param full: To send the full buffer :type full: bool :return: Attributes and content of the terminal :rtype: dict """ def compress(line): return [[tok or 0 for tok in ch] for _, ch in line.items()] l = {} self.screen.dirty.add(self.screen.cursor.y) if self.last_cursor_position: self.screen.dirty.add(self.last_cursor_position[1]) for k in self.screen.dirty: if k < len(self.screen.buffer): l[k] = compress(self.screen.buffer[k]) self.screen.dirty.clear() if full: l = [compress(x) for _, x in self.screen.buffer.items()] r = { 'lines': l, 'cx': self.screen.cursor.x, 'cy': self.screen.cursor.y, 'cursor': not self.screen.cursor.hidden, 'h': self.screen.lines, 'w': self.screen.columns, } self.last_cursor_position = (self.screen.cursor.x, self.screen.cursor.y) return r def feed(self, data): """ Write input data into the file descriptor. :param data: New data from the user :type data: string """ wait_write(self.fd) self.stream_out.write(data.encode('utf-8')) self.stream_out.flush() def resize(self, w, h): """ Resize terminal to new width and height. :param w: Width :type w: integer :param h: Height :type h: integer """ if self.dead: return if (h, w) == (self.screen.lines, self.screen.columns): return if w <= 0 or h <= 0: return self.width = w self.height = h logging.debug(f'Resizing terminal to {w}x{h}') self.screen.resize(h, w) winsize = struct.pack("HHHH", h, w, 0, 0) fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize) def kill(self): """ Kill this terminal through killing the process associated with the pid. """ self.reader.kill(block=False) logging.info(f'Killing subprocess PID {self.pid}') try: os.killpg(self.pid, signal.SIGTERM) except OSError: pass try: os.kill(self.pid, signal.SIGKILL) except OSError: pass
class Terminal(): def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5, redirect=None): self.width = 80 self.height = 25 self.id = id self.manager = manager self.autoclose = autoclose self.autoclose_retain = autoclose_retain if redirect: self.redirect = redirect else: self.redirect = '/view/terminal' self.output = BroadcastQueue() env = {} env.update(os.environ) env['TERM'] = 'linux' env['COLUMNS'] = str(self.width) env['LINES'] = str(self.height) env['LC_ALL'] = 'en_US.UTF8' self.command = command if not self.command: shell = os.environ.get('SHELL', None) if not shell: for sh in ['zsh', 'bash', 'sh']: try: shell = subprocess.check_output(['which', sh]) break except Exception as e: pass self.command = shell args = ['sh', '-c', self.command] exe = 'sh' logging.info('Activating new terminal: %s', self.command) self.pid, self.fd = pty.fork() if self.pid == 0: setproctitle.setproctitle('%s terminal session #%i' % (sys.argv[0], os.getpid())) os.execvpe(exe, args, env) logging.info('Subprocess PID %s', self.pid) self.dead = False fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) self.stream_in = os.fdopen(self.fd, 'rb', 0) self.stream_out = os.fdopen(self.fd, 'wb', 0) self.pyte_stream = pyte.Stream() self.screen = pyte.DiffScreen(self.width, self.height) self.pyte_stream.attach(self.screen) self.last_cursor_position = None self.reader = gevent.spawn(self.reader_fn) def reader_fn(self): while True: wait_read(self.fd) self.run_single_read() self._check() if self.dead: return def run_single_read(self): try: data = self.stream_in.read() except IOError: data = '' if data: try: self.pyte_stream.feed(data.decode('utf-8')) self.broadcast_update() except UnicodeDecodeError: pass def broadcast_update(self): self.output.broadcast(self.format()) def _check(self): try: pid, status = os.waitpid(self.pid, os.WNOHANG) except OSError: self.on_died(code=0) return if pid: self.on_died(code=status) def on_died(self, code=0): if self.dead: return logging.info('Terminal %s has died', self.id) self.dead = True if code: self.pyte_stream.feed(u'\n\n * ' + u'Process has exited with status %i' % code) else: self.pyte_stream.feed(u'\n\n * ' + u'Process has exited successfully') self.broadcast_update() if self.autoclose: gevent.spawn_later(self.autoclose_retain, self.manager.remove, self.id) """ TODO if self.callback: try: self.callback(exitcode=code) except TypeError: self.callback() """ def has_updates(self): if self.last_cursor_position != (self.screen.cursor.x, self.screen.cursor.y): return True return len(self.screen.dirty) > 0 def format(self, full=False): def compress(line): return [[tok or 0 for tok in ch] for _, ch in line.items()] l = {} self.screen.dirty.add(self.screen.cursor.y) if self.last_cursor_position: self.screen.dirty.add(self.last_cursor_position[1]) for k in self.screen.dirty: if k < len(self.screen.buffer): l[k] = compress(self.screen.buffer[k]) self.screen.dirty.clear() if full: l = [compress(x) for _, x in self.screen.buffer.items()] r = { 'lines': l, 'cx': self.screen.cursor.x, 'cy': self.screen.cursor.y, 'cursor': not self.screen.cursor.hidden, 'h': self.screen.lines, 'w': self.screen.columns, } self.last_cursor_position = (self.screen.cursor.x, self.screen.cursor.y) return r def feed(self, data): wait_write(self.fd) self.stream_out.write(data.encode('utf-8')) self.stream_out.flush() def resize(self, w, h): if self.dead: return if (h, w) == (self.screen.lines, self.screen.columns): return if w <= 0 or h <= 0: return self.width = w self.height = h logging.debug('Resizing terminal to %sx%s', w, h) self.screen.resize(h, w) winsize = struct.pack("HHHH", h, w, 0, 0) fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize) def kill(self): self.reader.kill(block=False) logging.info('Killing subprocess PID %s', self.pid) try: os.killpg(self.pid, signal.SIGTERM) except OSError: pass try: os.kill(self.pid, signal.SIGKILL) except OSError: pass
class WorkerGate(object): def __init__(self, session, name=None, log_tag=None, restricted=False, initial_identity=None): self.session = session self.process = None self.stream = None self.stream_reader = None self.worker = None self.name = name self.log_tag = log_tag self.restricted = restricted self.initial_identity = initial_identity self.q_http_replies = BroadcastQueue() self.q_socket_messages = BroadcastQueue() def start(self): pipe_parent, pipe_child = gipc.pipe(duplex=True) self.stream = GateStreamServerEndpoint(pipe_parent) stream_child = GateStreamWorkerEndpoint(pipe_child) self.process = gipc.start_process(target=self._target, kwargs={ 'stream': stream_child, '_pipe': pipe_child, }) logging.debug('Started child process %s', self.process.pid) self.stream_reader = gevent.spawn(self._stream_reader) def stop(self): logging.debug('Stopping child process %s', self.process.pid) try: os.killpg(self.process.pid, signal.SIGTERM) except OSError as e: logging.debug('Child process %s is already dead: %s', self.process.pid, e) return self.process.terminate() self.process.join(0.125) try: os.kill(self.process.pid, 0) logging.debug('Child process %s did not stop, killing', self.process.pid) os.kill(self.process.pid, signal.SIGKILL) os.killpg(self.process.pid, signal.SIGKILL) os.kill(self.process.pid, 0) logging.error('Child process %s did not stop after SIGKILL!', self.process.pid) except OSError: pass self.stream_reader.kill(block=False) def _stream_reader(self): try: while True: resp = self.stream.buffer_single_response(None) if not resp: return self.stream.ack_response(resp.id) if resp.object['type'] == 'socket': self.q_socket_messages.broadcast(resp) if resp.object['type'] == 'http': self.q_http_replies.broadcast(resp) if resp.object['type'] == 'terminate': self.session.deactivate() if resp.object['type'] == 'restart-master': aj.restart() except greenlet.GreenletExit: pass def _target(self, stream=None, _pipe=None): self.worker = Worker(stream, self) self.worker.run()
def __init__(self, context): self.q = BroadcastQueue()
class Terminal(object): def __init__(self, manager=None, id=None, command=None, autoclose=False, autoclose_retain=5): self.width = 80 self.height = 25 self.id = id self.manager = manager self.autoclose = autoclose self.autoclose_retain = autoclose_retain self.output = BroadcastQueue() env = {} env.update(os.environ) env['TERM'] = 'linux' env['COLUMNS'] = str(self.width) env['LINES'] = str(self.height) env['LC_ALL'] = 'en_US.UTF8' self.command = command if not self.command: shell = os.environ.get('SHELL', None) if not shell: for sh in ['zsh', 'bash', 'sh']: try: shell = subprocess.check_output(['which', sh]) break except: pass self.command = shell args = ['sh', '-c', self.command] exe = 'sh' logging.info('Activating new terminal: %s', self.command) self.pid, self.fd = pty.fork() if self.pid == 0: setproctitle.setproctitle('%s terminal session #%i' % (sys.argv[0], os.getpid())) os.execvpe(exe, args, env) logging.info('Subprocess PID %s', self.pid) self.dead = False fl = fcntl.fcntl(self.fd, fcntl.F_GETFL) fcntl.fcntl(self.fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) self.stream_in = os.fdopen(self.fd, 'rb', 0) self.stream_out = os.fdopen(self.fd, 'wb', 0) self.pyte_stream = pyte.Stream() self.screen = pyte.DiffScreen(self.width, self.height) self.pyte_stream.attach(self.screen) self.last_cursor_position = None self.reader = gevent.spawn(self.reader_fn) def reader_fn(self): while True: wait_read(self.fd) self.run_single_read() self._check() if self.dead: return def run_single_read(self): try: data = self.stream_in.read() except IOError: data = '' if data: try: self.pyte_stream.feed(data.decode('utf-8')) self.broadcast_update() except UnicodeDecodeError: pass def broadcast_update(self): self.output.broadcast(self.format()) def _check(self): try: pid, status = os.waitpid(self.pid, os.WNOHANG) except OSError: self.on_died(code=0) return if pid: self.on_died(code=status) def on_died(self, code=0): if self.dead: return logging.info('Terminal %s has died', self.id) self.dead = True if code: self.pyte_stream.feed(u'\n\n * ' + u'Process has exited with status %i' % code) else: self.pyte_stream.feed(u'\n\n * ' + u'Process has exited successfully') self.broadcast_update() if self.autoclose: gevent.spawn_later(self.autoclose_retain, self.manager.remove, self.id) """ TODO if self.callback: try: self.callback(exitcode=code) except TypeError: self.callback() """ def has_updates(self): if self.last_cursor_position != (self.screen.cursor.x, self.screen.cursor.y): return True return len(self.screen.dirty) > 0 def format(self, full=False): def compress(line): return [[tok or 0 for tok in ch] for ch in line] l = {} self.screen.dirty.add(self.screen.cursor.y) if self.last_cursor_position: self.screen.dirty.add(self.last_cursor_position[1]) for k in self.screen.dirty: if k < len(self.screen.buffer): l[k] = compress(self.screen.buffer[k]) self.screen.dirty.clear() if full: l = [compress(x) for x in self.screen.buffer] r = { 'lines': l, 'cx': self.screen.cursor.x, 'cy': self.screen.cursor.y, 'cursor': not self.screen.cursor.hidden, 'h': self.screen.lines, 'w': self.screen.columns, } self.last_cursor_position = (self.screen.cursor.x, self.screen.cursor.y) return r def feed(self, data): wait_write(self.fd) self.stream_out.write(data.encode('utf-8')) self.stream_out.flush() def resize(self, w, h): if self.dead: return if (h, w) == (self.screen.lines, self.screen.columns): return if w <= 0 or h <= 0: return self.width = w self.height = h logging.debug('Resizing terminal to %sx%s', w, h) self.screen.resize(h, w) winsize = struct.pack("HHHH", h, w, 0, 0) fcntl.ioctl(self.fd, termios.TIOCSWINSZ, winsize) def kill(self): self.reader.kill(block=False) logging.info('Killing subprocess PID %s', self.pid) try: os.killpg(self.pid, signal.SIGTERM) except OSError: pass try: os.kill(self.pid, signal.SIGKILL) except OSError: pass
class WorkerGate(object): def __init__(self, session, name=None, log_tag=None, restricted=False, initial_identity=None): self.session = session self.process = None self.stream = None self.stream_reader = None self.worker = None self.name = name self.log_tag = log_tag self.restricted = restricted self.initial_identity = initial_identity self.q_http_replies = BroadcastQueue() self.q_socket_messages = BroadcastQueue() def start(self): pipe_parent, pipe_child = gipc.pipe(duplex=True) self.stream = GateStreamServerEndpoint(pipe_parent) stream_child = GateStreamWorkerEndpoint(pipe_child) self.process = gipc.start_process( target=self._target, kwargs={ 'stream': stream_child, '_pipe': pipe_child, } ) logging.debug('Started child process %s', self.process.pid) self.stream_reader = gevent.spawn(self._stream_reader) def stop(self): logging.debug('Stopping child process %s', self.process.pid) try: os.killpg(self.process.pid, signal.SIGTERM) except OSError as e: logging.debug('Child process %s is already dead: %s', self.process.pid, e) return self.process.terminate() self.process.join(0.125) try: os.kill(self.process.pid, 0) logging.debug('Child process %s did not stop, killing', self.process.pid) os.kill(self.process.pid, signal.SIGKILL) os.killpg(self.process.pid, signal.SIGKILL) os.kill(self.process.pid, 0) logging.error('Child process %s did not stop after SIGKILL!', self.process.pid) except OSError: pass self.stream_reader.kill(block=False) def _stream_reader(self): try: while True: resp = self.stream.buffer_single_response(None) if not resp: return self.stream.ack_response(resp.id) if resp.object['type'] == 'socket': self.q_socket_messages.broadcast(resp) if resp.object['type'] == 'http': self.q_http_replies.broadcast(resp) if resp.object['type'] == 'terminate': self.session.deactivate() if resp.object['type'] == 'restart-master': aj.restart() if resp.object['type'] == 'reload-config': aj.config.load() aj.config.ensure_structure() self.stream.send({ 'type': 'config-data', 'data': aj.config.data, }) except greenlet.GreenletExit: pass def _target(self, stream=None, _pipe=None): self.worker = Worker(stream, self) self.worker.run()
class WorkerGate(): def __init__(self, session, gateway_middleware, name=None, log_tag=None, restricted=False, initial_identity=None): self.session = session self.process = None self.stream = None self.stream_reader = None self.worker = None self.name = name self.log_tag = log_tag self.restricted = restricted self.gateway_middleware = gateway_middleware self.initial_identity = initial_identity self.q_http_replies = BroadcastQueue() self.q_socket_messages = BroadcastQueue() def start(self): pipe_parent, pipe_child = gipc.pipe( duplex=True, encoder=lambda x: pickle.dumps(x, 2), ) self.stream = GateStreamServerEndpoint(pipe_parent) stream_child = GateStreamWorkerEndpoint(pipe_child) self.process = gipc.start_process(target=self._target, kwargs={ 'stream': stream_child, '_pipe': pipe_child, }) logging.debug(f'Started child process {self.process.pid}') self.stream_reader = gevent.spawn(self._stream_reader) def stop(self): logging.debug(f'Stopping child process {self.process.pid}') try: os.killpg(self.process.pid, signal.SIGTERM) except OSError as e: logging.debug( f'Child process {self.process.pid} is already dead: {e}') return self.process.terminate() self.process.join(0.125) try: os.kill(self.process.pid, 0) logging.debug( f'Child process {self.process.pid} did not stop, killing') os.kill(self.process.pid, signal.SIGKILL) os.killpg(self.process.pid, signal.SIGKILL) os.kill(self.process.pid, 0) logging.error( f'Child process {self.process.pid} did not stop after SIGKILL!' ) except OSError: pass self.stream.destroy() self.stream_reader.kill(block=False) def send_config_data(self): logging.debug(f'Sending a config update to {self.name}') self.stream.send({ 'type': 'config-data', 'data': { 'config': aj.config.data, 'users': aj.users.data } }) def send_sessionlist(self): logging.debug(f'Sending a session list update to {self.name}') self.stream.send({ 'type': 'session-list', 'data': { key: session.serialize() for key, session in self.gateway_middleware.sessions.items() }, }) def _stream_reader(self): try: while True: resp = self.stream.buffer_single_response(None) if not resp: return self.stream.ack_response(resp.id) if resp.object['type'] == 'socket': self.q_socket_messages.broadcast(resp) if resp.object['type'] == 'http': self.q_http_replies.broadcast(resp) if resp.object['type'] == 'terminate': if self.session != self.gateway_middleware: # Not the restricted session, we can disable it self.session.deactivate() if resp.object['type'] == 'restart-master': aj.restart() if resp.object['type'] == 'update-sessionlist': self.gateway_middleware.broadcast_sessionlist() if resp.object['type'] == 'reload-config': aj.config.load() aj.users.load() aj.config.ensure_structure() self.gateway_middleware.broadcast_config_data() if resp.object['type'] == 'log': method = { 'info': logging.info, 'warn': logging.warning, 'warning': logging.warning, 'debug': logging.debug, 'error': logging.error, 'critical': logging.critical, }.get(resp.object['method'], None) if method: method(f"{resp.object['message']}", extra=resp.object['kwargs']) except greenlet.GreenletExit: pass def _target(self, stream=None, _pipe=None): self.worker = Worker(stream, self) self.worker.run()