def test_exit_with_parent_process_died( language_server_process: IRobocorpLanguageServerClient, language_server_io, ws_root_path, initialization_options, ): """ :note: Only check with the language_server_io (because that's in another process). """ from robocorp_ls_core.subprocess_wrapper import subprocess from robocorp_ls_core.basic import is_process_alive from robocorp_ls_core.basic import kill_process_and_subprocesses from robocorp_ls_core.unittest_tools.fixtures import wait_for_test_condition language_server = language_server_io dummy_process = subprocess.Popen( [sys.executable, "-c", "import time;time.sleep(10000)"]) language_server.initialize( ws_root_path, process_id=dummy_process.pid, initialization_options=initialization_options, ) assert is_process_alive(dummy_process.pid) assert is_process_alive(language_server_process.pid) kill_process_and_subprocesses(dummy_process.pid) wait_for_test_condition(lambda: not is_process_alive(dummy_process.pid)) wait_for_test_condition( lambda: not is_process_alive(language_server_process.pid)) language_server_io.require_exit_messages = False
def _dispose_server_process(self): self._check_in_main_thread() try: log.debug("Dispose server process.") if self._server_process is not None: if is_process_alive(self._server_process.pid): kill_process_and_subprocesses(self._server_process.pid) finally: self._server_process = None self._robotframework_api_client = None self._used_environ = None self._used_python_executable = None
def _dispose_server_process(self): from robocorp_ls_core.basic import kill_process_and_subprocesses self._check_thread() try: log.debug("Dispose server process.") if self._server_process is not None: if is_process_alive(self._server_process.pid): kill_process_and_subprocesses(self._server_process.pid) finally: self._server_process = None self._locators_api_client = None
def _dispose_server_process(self): from robocorp_ls_core.basic import kill_process_and_subprocesses with self._lock_api_client: try: log.debug("Dispose server process.") if self._server_process is not None: if is_process_alive(self._server_process.pid): kill_process_and_subprocesses(self._server_process.pid) finally: self._disposed = True self._server_process = None self._rf_interpreter_api_client = None
def _notify_on_exited_pid(on_exit, pid): try: from robocorp_ls_core.basic import is_process_alive import time log.debug("Waiting for pid to exit (_notify_on_exited_pid).") while True: if not is_process_alive(pid): break time.sleep(0.5) log.debug("pid exited (_notify_on_exited_pid).") on_exit() except: log.exception("Error")
def get_robotframework_api_client( self) -> Optional[IRobotFrameworkApiClient]: self._check_in_main_thread() workspace = self.workspace assert ( workspace ), "The workspace must be already set when getting the server api." server_process = self._server_process if server_process is not None: # If someone killed it, dispose of internal references # and create a new process. if not is_process_alive(server_process.pid): server_process = None self._dispose_server_process() if server_process is None: try: from robotframework_ls.options import Setup from robotframework_ls.server_api.client import RobotFrameworkApiClient from robotframework_ls.server_api.server__main__ import ( start_server_process, ) from robocorp_ls_core.jsonrpc.streams import ( JsonRpcStreamWriter, JsonRpcStreamReader, ) args = [] if Setup.options.verbose: args.append("-" + "v" * int(Setup.options.verbose)) if Setup.options.log_file: log_id = _next_id() # i.e.: use a log id in case we create more than one in the # same session. if log_id == 0: args.append("--log-file=" + Setup.options.log_file + self._log_extension) else: args.append("--log-file=" + Setup.options.log_file + (".%s" % (log_id, )) + self._log_extension) python_exe = self._get_python_executable() environ = self._get_environ() self._used_python_executable = python_exe self._used_environ = environ server_process = start_server_process(args=args, python_exe=python_exe, env=environ) self._server_process = server_process write_to = server_process.stdin read_from = server_process.stdout w = JsonRpcStreamWriter(write_to, sort_keys=True) r = JsonRpcStreamReader(read_from) api = self._robotframework_api_client = RobotFrameworkApiClient( w, r, server_process) log.debug( "Initializing api... (this pid: %s, api pid: %s).", os.getpid(), server_process.pid, ) api.initialize( process_id=os.getpid(), root_uri=workspace.root_uri, workspace_folders=list({ "uri": folder.uri, "name": folder.name } for folder in workspace.iter_folders()), ) config = self._config log.debug("Forwarding config to api...") if config is not None: api.forward( "workspace/didChangeConfiguration", {"settings": config.get_full_settings()}, ) # Open existing documents in the API. source: Optional[str] for document in workspace.iter_documents(): log.debug("Forwarding doc: %s to api...", document.uri) try: source = document.source except Exception: source = None api.forward( "textDocument/didOpen", { "textDocument": { "uri": document.uri, "version": document.version, "text": source, } }, ) except Exception as e: if server_process is None: log.exception( "Error starting robotframework server api (server_process=None)." ) else: exitcode = server_process.poll() if exitcode is not None: # Note: only read() if the process exited. log.exception( "Error starting robotframework server api. Exit code: %s Base exception: %s. Stderr: %s", exitcode, e, server_process.stderr.read(), ) else: log.exception( "Error (%s) starting robotframework server api (still running). Base exception: %s.", exitcode, e, ) self._dispose_server_process() finally: if server_process is not None: log.debug("Server api (%s) created pid: %s", self, server_process.pid) else: log.debug( "server_process == None in get_robotframework_api_client()" ) return self._robotframework_api_client
def _get_api_client(self) -> Any: self._check_thread() server_process = self._server_process if server_process is not None: # If someone killed it, dispose of internal references # and create a new process. if not is_process_alive(server_process.pid): server_process = None self._dispose_server_process() if server_process is None: try: from robocorp_code.options import Setup from robocorp_code.locators.server.locator__main__ import ( start_server_process, ) from robocorp_ls_core.jsonrpc.streams import ( JsonRpcStreamWriter, JsonRpcStreamReader, ) from robocorp_code.locators.server.locator_client import ( LocatorsApiClient, ) args = [] if Setup.options.verbose: args.append("-" + "v" * int(Setup.options.verbose)) if Setup.options.log_file: log_id = _next_id() # i.e.: use a log id in case we create more than one in the # same session. if log_id == 0: args.append("--log-file=" + Setup.options.log_file + self._log_extension) else: args.append("--log-file=" + Setup.options.log_file + (".%s" % (log_id, )) + self._log_extension) python_exe = sys.executable server_process = start_server_process(args=args, python_exe=python_exe) self._server_process = server_process write_to = server_process.stdin read_from = server_process.stdout w = JsonRpcStreamWriter(write_to, sort_keys=True) r = JsonRpcStreamReader(read_from) api = self._locators_api_client = LocatorsApiClient( w, r, server_process) log.debug( "Initializing locators api... (this pid: %s, api pid: %s).", os.getpid(), server_process.pid, ) api.initialize(process_id=os.getpid()) except Exception as e: if server_process is None: log.exception( "Error starting locators server api (server_process=None)." ) else: exitcode = server_process.poll() if exitcode is not None: # Note: only read() if the process exited. log.exception( "Error starting locators server api. Exit code: %s Base exception: %s. Stderr: %s", exitcode, e, server_process.stderr.read(), ) else: log.exception( "Error (%s) starting locators server api (still running). Base exception: %s.", exitcode, e, ) self._dispose_server_process() finally: if server_process is not None: log.debug("Server api (%s) created pid: %s", self, server_process.pid) else: log.debug("server_process == None in _get_api_client()") return self._locators_api_client
def _get_api_client(self) -> Any: with self._lock_api_client: server_process = self._server_process if server_process is not None: # If someone killed it, dispose of internal references # and create a new process. if not is_process_alive(server_process.pid): self._dispose_server_process() if self._disposed: log.info("Robot Framework Interpreter server already disposed.") return None if server_process is None: try: from robotframework_interactive.server.rf_interpreter__main__ import ( start_server_process, ) from robocorp_ls_core.jsonrpc.streams import ( JsonRpcStreamWriter, JsonRpcStreamReader, ) from robotframework_interactive.server.rf_interpreter_client import ( RfInterpreterApiClient, ) args = [] if self._verbose: args.append("-" + "v" * int(self._verbose)) if self._base_log_file: log_id = _next_id() # i.e.: use a log id in case we create more than one in the # same session. if log_id == 0: args.append( "--log-file=" + self._base_log_file + self._log_extension ) else: args.append( "--log-file=" + self._base_log_file + (".%s" % (log_id,)) + self._log_extension ) python_exe = self._get_python_executable() environ = self._get_environ() connect_event = threading.Event() s = create_server_socket(host="127.0.0.1", port=0) import socket as socket_module new_socket: Optional[socket_module.socket] = None connect_event = threading.Event() def wait_for_connection(): nonlocal new_socket try: s.settimeout( DEFAULT_TIMEOUT if USE_TIMEOUTS else NO_TIMEOUT ) s.listen(1) new_socket, _addr = s.accept() log.info("Connection accepted") except: log.exception("Server did not connect.") finally: connect_event.set() s.close() t = threading.Thread(target=wait_for_connection) t.start() # Now, we're listening, let's start up the interpreter to connect back. _, port = s.getsockname() args.append("--tcp") args.append("--host") args.append("127.0.0.1") args.append("--port") args.append(str(port)) cwd = os.path.dirname(self._filename) if not os.path.isdir(cwd): raise AssertionError(f"CWD passed is not a directory: {cwd}") server_process = start_server_process( args=args, python_exe=python_exe, env=environ, cwd=cwd ) self._server_process = server_process connect_event.wait() if new_socket is None: raise RuntimeError( "Timed out while waiting for interpreter server to connect." ) read_from = new_socket.makefile("rb") write_to = new_socket.makefile("wb") w = JsonRpcStreamWriter(write_to, sort_keys=True) r = JsonRpcStreamReader(read_from) api = self._rf_interpreter_api_client = RfInterpreterApiClient( w, r, server_process, on_interpreter_message=self._on_interpreter_message, ) log.debug( "Initializing rf interpreter api... (this pid: %s, api pid: %s).", os.getpid(), server_process.pid, ) api.initialize(process_id=os.getpid()) except Exception as e: if server_process is None: log.exception( "Error starting rf interpreter server api (server_process=None)." ) else: exitcode = server_process.poll() if exitcode is not None: # Note: only read() if the process exited. log.exception( "Error starting rf interpreter server api. Exit code: %s Base exception: %s. Stderr: %s", exitcode, e, server_process.stderr.read(), ) else: log.exception( "Error (%s) starting rf interpreter server api (still running). Base exception: %s.", exitcode, e, ) self._dispose_server_process() finally: if server_process is not None: log.debug( "Server api (%s) created pid: %s", self, server_process.pid ) else: log.debug("server_process == None in _get_api_client()") return self._rf_interpreter_api_client
def __init__(self, mutex_name, check_reentrant=True, log_info=False): """ :param check_reentrant: Should only be False if this mutex is expected to be released in a different thread. """ check_valid_mutex_name(mutex_name) self.mutex_name = mutex_name self.thread_id = get_tid() filename = os.path.join(tempfile.gettempdir(), mutex_name) try: handle = open(filename, "a+") fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) handle.write(str(os.getpid()) + "\n") handle.flush() except Exception: self._release_mutex = NULL self._acquired = False if check_reentrant: _verify_prev_acquired_in_thread(mutex_name) try: handle.close() except Exception: pass # It was unable to get the lock if log_info: try: try: with open(filename, "r") as stream: curr_pid = stream.read().strip().split( "\n")[-1] except: log.exception("Unable to get locking pid.") curr_pid = "<unable to get locking pid>" try: from robocorp_ls_core.basic import is_process_alive is_alive = is_process_alive(int(curr_pid)) except: is_alive = "<unknown>" log.info( "Current pid holding lock: %s. Alive: %s. This pid: %s Filename: %s", curr_pid, is_alive, os.getpid(), filename, ) except: from robocorp_ls_core.robotframework_log import get_log_level if get_log_level() >= 2: log.exception( "Error getting lock info on failure.") else: def release_mutex(*args, **kwargs): # Note: can't use self here! if not getattr(release_mutex, "called", False): release_mutex.called = True try: fcntl.flock(handle, fcntl.LOCK_UN) except Exception: traceback.print_exc() try: handle.close() except Exception: traceback.print_exc() try: # Removing is pretty much optional (but let's do it to keep the # filesystem cleaner). os.unlink(filename) except Exception: pass # Don't use __del__: this approach doesn't have as many pitfalls. self._ref = weakref.ref(self, release_mutex) self._release_mutex = release_mutex self._acquired = True _mark_prev_acquired_in_thread(self)