Repository: servo/servo
File: tests/wpt/tests/tools/wptrunner/wptrunner/browsers/servodriver.py (Python)

Code before:
            args += ["--headless"]

        # Add the shared `wpt-prefs.json` file to the list of arguments.
        args += ["--prefs-file", self.find_wpt_prefs(logger)]

        super().__init__(logger, binary=binary, webdriver_binary=binary,
                         webdriver_args=args, host=webdriver_host, env=env,
                         supports_pac=False, **kwargs)

        self.hosts_path = hosts_path

    def make_command(self):
        return [self.webdriver_binary, f"--webdriver={self.port}"] + self.webdriver_args

    def cleanup(self):
        os.remove(self.hosts_path)

    def is_alive(self):
        if not super().is_alive():
            return False
        try:
            conn = HTTPConnection(self.host, self.port)
            conn.request("GET", "/status")
            res = conn.getresponse()
        except Exception:
            self.logger.info("Servo has shutted down normally.")
            return False

        return True

    def stop(self, force=False):
        if self.is_alive():
            self.logger.info("Shutting down gracefully by extension command")
            conn = HTTPConnection(self.host, self.port)
            conn.request("DELETE", "/session/dummy-session-id/servo/shutdown")
            res = conn.getresponse()
            self.logger.info(f"Got response status for shutdown command: {res.status}")
            # 0.05 is a heuristic value manually tested, after which servo always shutted down.
            time.sleep(0.05)
            while self.is_alive():
                self.logger.info("Still alive. Retry in 0.05 second")
                time.sleep(0.05)

    def find_wpt_prefs(self, logger):
        default_path = os.path.join("resources", "wpt-prefs.json")
        # The cwd is the servo repo for `./mach test-wpt`, but on WPT runners
        # it is the WPT repo. The nightly tar is extracted inside the Python
        # virtual environment within the repo. This means that on WPT runners,
        # the cwd has the `_venv3/servo` directory inside which we find the
        # binary and the 'resources' directory.
        for dir in [".", "./_venv3/servo"]:

Review comment (bug):
```suggestion
            self.logger.info("Servo has shut down normally.")
```

Should this perhaps also be `debug`? `info` seems very verbose.

Code after:
        if headless:
            args += ["--headless"]

        # Add the shared `wpt-prefs.json` file to the list of arguments.
        args += ["--prefs-file", self.find_wpt_prefs(logger)]

        super().__init__(logger, binary=binary, webdriver_binary=binary,
                         webdriver_args=args, host=webdriver_host, env=env,
                         supports_pac=False, **kwargs)

        self.hosts_path = hosts_path

    def make_command(self):
        return [self.webdriver_binary, f"--webdriver={self.port}"] + self.webdriver_args

    def cleanup(self):
        os.remove(self.hosts_path)

    def is_alive(self):
        # This is broken. It is always True.
        if not super().is_alive():
            return False
        try:
            requests.get(f"http://{self.host}:{self.port}/status", timeout=3)    
        except requests.exceptions.Timeout:
            # FIXME: This indicates a hanged browser. Reasons need to be investigated further.
            # It happens with ~0.1% probability in our CI runs.
            self.logger.debug("Servo webdriver status request timed out.")
            return True
        except Exception as exception:
            self.logger.debug(f"Servo has shut down normally. {exception}")
            return False

        return True

    def stop(self, force=False):
        retry_cnt = 0
        while self.is_alive():
            self.logger.info("Trying to shut down gracefully by extension command")
            try:
                requests.delete(
                    f"http://{self.host}:{self.port}/session/dummy-session-id/servo/shutdown",
                    timeout=3
                )
            except requests.exceptions.ConnectionError:
                self.logger.debug("Browser already shut down (connection refused)")
                break
            except requests.exceptions.RequestException as exeception:
                self.logger.debug(f"Request exception: {exeception}")
                break
            except requests.exceptions.Timeout: