1
0
mirror of https://github.com/apache/httpd.git synced 2026-10-23 22:49:01 +03:00

Attempt to fix new, flaky pyhttpd test core/test_008_restart:

* test/pyhttpd/log.py (HttpdErrorLog.current_pos, .wait_for): New
  methods, to wait for a line logged after a noted position.

* test/pyhttpd/env.py (HttpdTestEnv._apache_signal_restart): New method:
  run "httpd -k restart" or "-k graceful", which only signal the parent,
  then wait for the MPM to log "resuming normal operations" before
  checking is_live().  Until then the answer may come from the old
  generation, with the old config and about to be killed, which showed
  up as an empty reply in test_core_008_03_repeated under ASan.
  (apache_reload, apache_hard_restart): Use it.

Fixes: 7172909eac ("test/modules/core/test_008_restart.py: New test suite for server restart handling.")
GitHub: see also #733

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GitHub: closes #737


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1937877 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Joe Orton
2026-09-04 17:15:28 +00:00
parent c7f3242526
commit f723596df6
2 changed files with 47 additions and 10 deletions

View File

@@ -920,15 +920,31 @@ class HttpdTestEnv:
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 @@ class HttpdTestEnv:
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

View File

@@ -146,6 +146,31 @@ class HttpdErrorLog:
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