diff --git a/test/pyhttpd/env.py b/test/pyhttpd/env.py index 514f30b99b3..8919632ef01 100644 --- a/test/pyhttpd/env.py +++ b/test/pyhttpd/env.py @@ -920,15 +920,31 @@ def _win_stop(self) -> int: log.warning("port still in use after stop") return 0 + # Logged by every MPM once the new generation is serving. + RE_RESUMING = re.compile(r'.* configured -- resuming normal operations$') + + def _apache_signal_restart(self, cmd: str) -> int: + """Have the running parent restart, gracefully or not, and wait + for the new generation to be serving. "httpd -k restart" only + sends the signal; a request answered straight afterwards may have + been served by the old generation, with the old config, and about + to be killed.""" + log_pos = self.httpd_error_log.current_pos() + r = self._run_apachectl(cmd) + if r.exit_code != 0: + return r.exit_code + timeout = timedelta(seconds=10) + if not self.httpd_error_log.wait_for(self.RE_RESUMING, log_pos, + timeout=timeout.total_seconds()): + log.warning(f"no restart logged after '{cmd}' within {timeout}") + return -1 + return 0 if self.is_live(self._http_base, timeout=timeout) else -1 + def apache_reload(self): if self.isWindows: self._win_stop() return self._win_start() - r = self._run_apachectl("graceful") - if r.exit_code == 0: - timeout = timedelta(seconds=10) - return 0 if self.is_live(self._http_base, timeout=timeout) else -1 - return r.exit_code + return self._apache_signal_restart("graceful") def apache_restart(self): if self.isWindows: @@ -976,11 +992,7 @@ def apache_fail(self): def apache_hard_restart(self) -> int: """Restart without the "graceful" flag, so the MPM starts over.""" - r = self._run_apachectl("restart") - if r.exit_code == 0: - return 0 if self.is_live(self._http_base, - timeout=timedelta(seconds=10)) else -1 - return r.exit_code + return self._apache_signal_restart("restart") def read_pid_file(self, name: str = 'httpd.pid') -> Optional[int]: # Where PidFile lands depends on how the httpd under test resolves diff --git a/test/pyhttpd/log.py b/test/pyhttpd/log.py index 75d6980f0b7..1e89ccdc388 100644 --- a/test/pyhttpd/log.py +++ b/test/pyhttpd/log.py @@ -146,6 +146,31 @@ def get_missed(self) -> Tuple[List[str], List[str]]: self._caught_matches = set() return errors, warnings + def current_pos(self) -> int: + """The end of the log as it stands now, to wait_for() lines + logged after it. + """ + if not os.path.isfile(self._path): + return 0 + with open(self._path) as fd: + return fd.seek(0, SEEK_END) + + def wait_for(self, pattern: re.Pattern, start_pos: int, timeout=10) -> bool: + """Wait for a line matching pattern to be logged after start_pos, + as returned by current_pos(). + """ + end = datetime.now() + timedelta(seconds=timeout) + while True: + if os.path.isfile(self._path): + with open(self._path) as fd: + fd.seek(start_pos, os.SEEK_SET) + for line in fd: + if pattern.match(line): + return True + if datetime.now() > end: + return False + time.sleep(.1) + def scan_recent(self, pattern: re.Pattern, timeout=10): if not os.path.isfile(self.path): return False