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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user