# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (c) 2009- Spyder Project Contributors # # Distributed under the terms of the MIT License # (see spyder/__init__.py for details) # ----------------------------------------------------------------------------- """ Spyder MS Language Server v3.0 transport proxy implementation. Main point-of-entry to start an LSP ZMQ/TCP transport proxy. """ # Standard library imports import argparse import logging import os import psutil import signal import threading from functools import partial # Local imports from spyder.plugins.completion.providers.languageserver.transport.tcp.producer import ( TCPLanguageServerClient) from spyder.plugins.completion.providers.languageserver.transport.stdio.producer import ( StdioLanguageServerClient) from spyder.py3compat import getcwd logger = logging.getLogger(__name__) PARENT_PROCESS_WATCH_INTERVAL = 3 # 3 s parser = argparse.ArgumentParser( description='ZMQ Python-based MS Language-Server v3.0 client for Spyder') parser.add_argument('--zmq-in-port', default=7000, help="ZMQ (in) port to be contacted") parser.add_argument('--zmq-out-port', default=7001, help="ZMQ (out) port to be contacted") parser.add_argument('--server-host', default='127.0.0.1', help='Host that serves the ls-server') parser.add_argument('--server-port', default=2087, help="Deployment port of the ls-server") parser.add_argument('--server-log-file', default=None, help="Log file to register ls-server activity") parser.add_argument('--folder', default=getcwd(), help="Initial current working directory used to " "initialize ls-server") parser.add_argument('--external-server', action="store_true", help="Do not start a local server") parser.add_argument('--stdio-server', action="store_true", help='Server communication should use stdio pipes') parser.add_argument('--transport-debug', default=0, type=int, help='Verbosity level for log messages') args, extra_args = parser.parse_known_args() def logger_init(level): """ Initialize the logger for this thread. Sets the log level to ERROR (0), WARNING (1), INFO (2), or DEBUG (3), depending on the argument `level`. """ levellist = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] handler = logging.StreamHandler() fmt = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) ' '-35s %(lineno) -5d: %(message)s') handler.setFormatter(logging.Formatter(fmt)) logger = logging.root logger.addHandler(handler) logger.setLevel(levellist[level]) class TerminateSignal(Exception): """Terminal exception descriptor.""" pass class SignalManager: """Manage and intercept SIGTERM and SIGKILL signals.""" def __init__(self): self.original_sigint = signal.getsignal(signal.SIGINT) self.original_sigterm = signal.getsignal(signal.SIGTERM) signal.signal(signal.SIGINT, self.exit_gracefully) signal.signal(signal.SIGTERM, self.exit_gracefully) if os.name == 'nt': self.original_sigbreak = signal.getsignal(signal.SIGBREAK) signal.signal(signal.SIGBREAK, self.exit_gracefully) def exit_gracefully(self, signum, frame): """Capture exit/kill signal and throw and exception.""" logger.info('Termination signal ({}) captured, ' 'initiating exit sequence'.format(signum)) raise TerminateSignal("Exit process!") def restore(self): """Restore signal handlers to their original settings.""" signal.signal(signal.SIGINT, self.original_sigint) signal.signal(signal.SIGTERM, self.original_sigterm) if os.name == 'nt': signal.signal(signal.SIGBREAK, self.original_sigbreak) if __name__ == '__main__': logger_init(args.transport_debug) extra_args = [x for x in extra_args if len(x) > 0] extra_args = ' '.join(extra_args) logger.debug(extra_args) process = psutil.Process() parent_pid = process.ppid() sig_manager = SignalManager() if args.stdio_server: LanguageServerClient = partial(StdioLanguageServerClient, server_args=extra_args, log_file=args.server_log_file) else: LanguageServerClient = partial(TCPLanguageServerClient, host=args.server_host, port=args.server_port) client = LanguageServerClient(zmq_in_port=args.zmq_in_port, zmq_out_port=args.zmq_out_port) client.start() is_alive = True def watch_parent_process(pid): """ Exit when the given pid is not alive. Code taken from the Python Language Server project. """ global is_alive if not psutil.pid_exists(pid): logger.info("parent process %s is not alive, exiting!", pid) is_alive = False if is_alive: threading.Timer(PARENT_PROCESS_WATCH_INTERVAL, watch_parent_process, args=[pid]).start() watching_thread = threading.Thread( target=watch_parent_process, args=(parent_pid,)) watching_thread.daemon = True watching_thread.start() try: while is_alive: client.listen() except TerminateSignal: pass client.stop() process.terminate() process.wait()