def restart(self): """ (re)starts a Python IDE-server this method is complicated by SublimePythonIDE having two different debug modes, - one in which the server is started manually by the developer, in which case this developer has to set the DEBUG_PORT constant - and one case where the server is started automatically but in a verbose mode, in which it prints to its stderr, which is copied to ST3's console by an AsynchronousFileReader. For this the developer has to set SERVER_DEBUGGING to True """ try: if DEBUG_PORT is not None: # debug mode one self.port = DEBUG_PORT self.proc = DebugProcDummy() print("started server on user-defined FIXED port %i with %s" % (self.port, self.python)) elif SERVER_DEBUGGING: # debug mode two self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port), " --debug"] self.proc = subprocess.Popen( proc_args, cwd=os.path.dirname(self.python), stderr=subprocess.PIPE, creationflags=CREATION_FLAGS ) self.queue = Queue() self.stderr_reader = AsynchronousFileReader( "Server on port %i - STDERR" % self.port, self.proc.stderr, self.queue ) self.stderr_reader.start() sublime.set_timeout_async(self.debug_consume, 1000) print("started server on port %i with %s IN DEBUG MODE" % (self.port, self.python)) else: # standard run of the server in end-user mode self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port)] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), creationflags=CREATION_FLAGS) print("started server on port %i with %s" % (self.port, self.python)) # wait 100 ms to make sure python proc is still running for i in range(10): time.sleep(0.01) if self.proc.poll(): if SERVER_DEBUGGING: print(sys.exc_info()) raise OSError(None, "Python interpretor crashed (using path %s)" % self.python) # in any case, we also need a local client object self.proxy = xmlrpc.client.ServerProxy( "http://%s:%i" % (self.resolve_localhost(), self.port), allow_none=True ) self.set_heartbeat_timer() except OSError as e: print("error starting server:", e) print("-----------------------------------------------------------------------------------------------") print("Try to use an absolute path to your projects python interpreter. On Windows try to use forward") print("slashes as in C:/Python27/python.exe or properly escape with double-backslashes" "") print("-----------------------------------------------------------------------------------------------") raise e
class Proxy(object): '''Abstracts the external Python processes that do the actual work. SublimePython just calls local methods on Proxy objects. The Proxy objects start external Python processes, send them heartbeat messages, communicate with them and restart them if necessary.''' def __init__(self, python): self.python = python self.proc = None self.proxy = None self.port = None self.stderr_reader = None self.queue = None self.rpc_lock = threading.Lock() self.restart() def get_free_port(self): s = socket.socket() s.bind(('', 0)) port = s.getsockname()[1] s.close() return port def resolve_localhost(self): return socket.gethostbyname("localhost") def restart(self): ''' (re)starts a Python IDE-server this method is complicated by SublimePythonIDE having two different debug modes, - one in which the server is started manually by the developer, in which case this developer has to set the DEBUG_PORT constant - and one case where the server is started automatically but in a verbose mode, in which it prints to its stderr, which is copied to ST3's console by an AsynchronousFileReader. For this the developer has to set SERVER_DEBUGGING to True ''' try: if DEBUG_PORT is not None: # debug mode one self.port = DEBUG_PORT self.proc = DebugProcDummy() print("started server on user-defined FIXED port %i with %s" % (self.port, self.python)) elif SERVER_DEBUGGING: # debug mode two self.port = self.get_free_port() proc_args = [ self.python, SERVER_SCRIPT, str(self.port), " --debug" ] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), stderr=subprocess.PIPE, creationflags=CREATION_FLAGS) self.queue = Queue() self.stderr_reader = AsynchronousFileReader( "Server on port %i - STDERR" % self.port, self.proc.stderr, self.queue) self.stderr_reader.start() sublime.set_timeout_async(self.debug_consume, 1000) print("started server on port %i with %s IN DEBUG MODE" % (self.port, self.python)) else: # standard run of the server in end-user mode self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port)] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), creationflags=CREATION_FLAGS) print("started server on port %i with %s" % (self.port, self.python)) # wait 100 ms to make sure python proc is still running for i in range(10): time.sleep(0.01) if self.proc.poll(): if SERVER_DEBUGGING: print(sys.exc_info()) raise OSError( None, "Python interpretor crashed (using path %s)" % self.python) # in any case, we also need a local client object self.proxy = xmlrpc.client.ServerProxy( 'http://%s:%i' % (self.resolve_localhost(), self.port), allow_none=True) self.set_heartbeat_timer() except OSError as e: print("error starting server:", e) print( "-----------------------------------------------------------------------------------------------" ) print( "Try to use an absolute path to your projects python interpreter. On Windows try to use forward" ) print( "slashes as in C:/Python27/python.exe or properly escape with double-backslashes" "") print( "-----------------------------------------------------------------------------------------------" ) raise e def debug_consume(self): ''' If SERVER_DEBUGGING is enabled, is called by ST every 1000ms and prints output from server debugging readers. ''' # Check the queues if we received some output (until there is nothing more to get). while not self.queue.empty(): line = self.queue.get() print(str(line)) # Sleep a bit before asking the readers again. sublime.set_timeout_async(self.debug_consume, 1000) def set_heartbeat_timer(self): sublime.set_timeout_async(self.send_heartbeat, HEARTBEAT_INTERVALL * 1000) def stop(self): self.proxy = None self.queue = Queue() self.proc.terminate() def send_heartbeat(self): if self.proxy: self.heartbeat() # implemented in proxy through __getattr__ self.set_heartbeat_timer() def __getattr__(self, attr): '''deletegate all other calls to the xmlrpc client. wait if the server process is still runnning, but not responding if the server process has died, restart it''' def wrapper(*args, **kwargs): if not self.proxy: self.restart() time.sleep(0.2) method = getattr(self.proxy, attr) result = None tries = 0 # multiple ST3 threads may use the proxy (e.g. linting in parallel # to heartbeat etc.) XML-RPC client objects are single-threaded # only though, so we introduce a lock here with self.rpc_lock: while tries < RETRY_CONNECTION_LIMIT: try: result = method(*args, **kwargs) break except Exception: tries += 1 if self.proc.poll() is None: # just retry time.sleep(0.2) else: # died, restart and retry self.restart() time.sleep(0.2) return result return wrapper
def restart(self): ''' (re)starts a Python IDE-server this method is complicated by SublimePythonIDE having two different debug modes, - one in which the server is started manually by the developer, in which case this developer has to set the DEBUG_PORT constant - and one case where the server is started automatically but in a verbose mode, in which it prints to its stderr, which is copied to ST3's console by an AsynchronousFileReader. For this the developer has to set SERVER_DEBUGGING to True ''' try: if DEBUG_PORT is not None: # debug mode one self.port = DEBUG_PORT self.proc = DebugProcDummy() print("started server on user-defined FIXED port %i with %s" % (self.port, self.python)) elif SERVER_DEBUGGING: # debug mode two self.port = self.get_free_port() proc_args = [ self.python, SERVER_SCRIPT, str(self.port), " --debug" ] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), stderr=subprocess.PIPE, creationflags=CREATION_FLAGS) self.queue = Queue() self.stderr_reader = AsynchronousFileReader( "Server on port %i - STDERR" % self.port, self.proc.stderr, self.queue) self.stderr_reader.start() sublime.set_timeout_async(self.debug_consume, 1000) print("started server on port %i with %s IN DEBUG MODE" % (self.port, self.python)) else: # standard run of the server in end-user mode self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port)] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), creationflags=CREATION_FLAGS) print("started server on port %i with %s" % (self.port, self.python)) # wait 100 ms to make sure python proc is still running for i in range(10): time.sleep(0.01) if self.proc.poll(): if SERVER_DEBUGGING: print(sys.exc_info()) raise OSError( None, "Python interpretor crashed (using path %s)" % self.python) # in any case, we also need a local client object self.proxy = xmlrpc.client.ServerProxy( 'http://%s:%i' % (self.resolve_localhost(), self.port), allow_none=True) self.set_heartbeat_timer() except OSError as e: print("error starting server:", e) print( "-----------------------------------------------------------------------------------------------" ) print( "Try to use an absolute path to your projects python interpreter. On Windows try to use forward" ) print( "slashes as in C:/Python27/python.exe or properly escape with double-backslashes" "") print( "-----------------------------------------------------------------------------------------------" ) raise e
class Proxy(object): '''Abstracts the external Python processes that do the actual work. SublimePython just calls local methods on Proxy objects. The Proxy objects start external Python processes, send them heartbeat messages, communicate with them and restart them if necessary.''' def __init__(self, python): self.python = python self.proc = None self.proxy = None self.port = None self.stderr_reader = None self.queue = None self.restart() def get_free_port(self): s = socket.socket() s.bind(('', 0)) port = s.getsockname()[1] s.close() return port def restart(self): ''' (re)starts a Python IDE-server this method is complicated by SublimePythonIDE having two different debug modes, - one in which the server is started manually by the developer, in which case this developer has to set the DEBUG_PORT constant - and one case where the server is started automatically but in a verbose mode, in which it prints to its stderr, which is copied to ST3's console by an AsynchronousFileReader. For this the developer has to set SERVER_DEBUGGING to True ''' try: if DEBUG_PORT is not None: # debug mode one self.port = DEBUG_PORT self.proc = DebugProcDummy() print("started server on user-defined FIXED port %i with %s" % (self.port, self.python)) elif SERVER_DEBUGGING: # debug mode two self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port), " --debug"] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), stderr=subprocess.PIPE, creationflags=CREATION_FLAGS) self.queue = Queue() self.stderr_reader = AsynchronousFileReader("Server on port %i - STDERR" % self.port, self.proc.stderr, self.queue) self.stderr_reader.start() sublime.set_timeout_async(self.debug_consume, 1000) print("started server on port %i with %s IN DEBUG MODE" % (self.port, self.python)) else: # standard run of the server in end-user mode self.port = self.get_free_port() proc_args = [self.python, SERVER_SCRIPT, str(self.port)] self.proc = subprocess.Popen(proc_args, cwd=os.path.dirname(self.python), creationflags=CREATION_FLAGS) print("started server on port %i with %s" % (self.port, self.python)) # wait 100 ms to make sure python proc is still running for i in range(10): time.sleep(0.01) if self.proc.poll(): if SERVER_DEBUGGING: print(sys.exc_info()) raise OSError(None, "Python interpretor crashed (using path %s)" % self.python) # in any case, we also need a local client object self.proxy = xmlrpc.client.ServerProxy( 'http://localhost:%i' % self.port, allow_none=True) self.set_heartbeat_timer() except OSError as e: print("error starting server:", e) print("-----------------------------------------------------------------------------------------------") print("Try to use an absolute path to your projects python interpreter. On Windows try to use forward") print("slashes as in C:/Python27/python.exe or properly escape with double-backslashes""") print("-----------------------------------------------------------------------------------------------") raise e def debug_consume(self): ''' If SERVER_DEBUGGING is enabled, is called by ST every 1000ms and prints output from server debugging readers. ''' # Check the queues if we received some output (until there is nothing more to get). while not self.queue.empty(): line = self.queue.get() print(str(line)) # Sleep a bit before asking the readers again. sublime.set_timeout_async(self.debug_consume, 1000) def set_heartbeat_timer(self): sublime.set_timeout_async( self.send_heartbeat, HEARTBEAT_INTERVALL * 1000) def stop(self): self.proxy = None self.queue = Queue() self.proc.terminate() def send_heartbeat(self): if self.proxy: self.proxy.heartbeat() self.set_heartbeat_timer() def __getattr__(self, attr): '''deletegate all other calls to the xmlrpc client. wait if the server process is still runnning, but not responding if the server process has died, restart it''' def wrapper(*args, **kwargs): if not self.proxy: self.restart() time.sleep(0.2) method = getattr(self.proxy, attr) result = None tries = 0 while tries < RETRY_CONNECTION_LIMIT: try: result = method(*args, **kwargs) break except Exception: tries += 1 if self.proc.poll() is None: # just retry time.sleep(0.2) else: # died, restart and retry self.restart() time.sleep(0.2) return result return wrapper