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
示例#2
0
 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
示例#4
0
    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
示例#5
0
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")
示例#6
0
    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
示例#8
0
    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
示例#9
0
        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)