mirror of
https://github.com/apache/httpd.git
synced 2025-08-01 16:41:19 +00:00

changes on load and graceful reload of the server. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899885 13f79535-47bb-0310-9956-ffa450edef68
151 lines
5.6 KiB
Python
151 lines
5.6 KiB
Python
import os
|
|
import re
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from threading import Thread
|
|
|
|
import pytest
|
|
|
|
from .env import CoreTestEnv
|
|
from pyhttpd.conf import HttpdConf
|
|
|
|
|
|
class Loader:
|
|
|
|
def __init__(self, env, url: str, clients: int, req_per_client: int = 10):
|
|
self.env = env
|
|
self.url = url
|
|
self.clients = clients
|
|
self.req_per_client = req_per_client
|
|
self.result = None
|
|
self.total_request = 0
|
|
self._thread = None
|
|
|
|
def run(self):
|
|
self.total_requests = self.clients * self.req_per_client
|
|
conn_per_client = 5
|
|
args = [self.env.h2load, f"--connect-to=localhost:{self.env.https_port}",
|
|
"--h1", # use only http/1.1
|
|
"-n", str(self.total_requests), # total # of requests to make
|
|
"-c", str(conn_per_client * self.clients), # total # of connections to make
|
|
"-r", str(self.clients), # connections at a time
|
|
"--rate-period", "2", # create conns every 2 sec
|
|
self.url,
|
|
]
|
|
self.result = self.env.run(args)
|
|
|
|
def start(self):
|
|
self._thread = Thread(target=self.run)
|
|
self._thread.start()
|
|
|
|
def join(self):
|
|
self._thread.join()
|
|
|
|
|
|
class ChildDynamics:
|
|
|
|
RE_DATE_TIME = re.compile(r'\[(?P<date_time>[^\]]+)\] .*')
|
|
RE_TIME_FRAC = re.compile(r'(?P<dt>.* \d\d:\d\d:\d\d)(?P<frac>.(?P<micros>.\d+)) (?P<year>\d+)')
|
|
RE_CHILD_CHANGE = re.compile(r'\[(?P<date_time>[^\]]+)\] '
|
|
r'\[mpm_event:\w+\]'
|
|
r' \[pid (?P<main_pid>\d+):tid \w+\] '
|
|
r'.* Child (?P<child_no>\d+) (?P<action>\w+): '
|
|
r'pid (?P<pid>\d+), gen (?P<generation>\d+), .*')
|
|
|
|
def __init__(self, env: CoreTestEnv):
|
|
self.env = env
|
|
self.changes = list()
|
|
self._start = None
|
|
for l in open(env.httpd_error_log.path):
|
|
m = self.RE_CHILD_CHANGE.match(l)
|
|
if m:
|
|
self.changes.append({
|
|
'pid': int(m.group('pid')),
|
|
'child_no': int(m.group('child_no')),
|
|
'gen': int(m.group('generation')),
|
|
'action': m.group('action'),
|
|
'rtime' : self._rtime(m.group('date_time'))
|
|
})
|
|
continue
|
|
if self._start is None:
|
|
m = self.RE_DATE_TIME.match(l)
|
|
if m:
|
|
self._rtime(m.group('date_time'))
|
|
|
|
def _rtime(self, s: str) -> timedelta:
|
|
micros = 0
|
|
m = self.RE_TIME_FRAC.match(s)
|
|
if m:
|
|
micros = int(m.group('micros'))
|
|
s = f"{m.group('dt')} {m.group('year')}"
|
|
d = datetime.strptime(s, '%a %b %d %H:%M:%S %Y') + timedelta(microseconds=micros)
|
|
if self._start is None:
|
|
self._start = d
|
|
delta = d - self._start
|
|
return f"{delta.seconds:+02d}.{delta.microseconds:06d}"
|
|
|
|
|
|
|
|
@pytest.mark.skipif(condition='STRESS_TEST' not in os.environ,
|
|
reason="STRESS_TEST not set in env")
|
|
@pytest.mark.skipif(condition=not CoreTestEnv().h2load_is_at_least('1.41.0'),
|
|
reason="h2load unavailable or misses --connect-to option")
|
|
class TestRestarts:
|
|
|
|
def test_core_002_01(self, env):
|
|
# Lets make a tight config that triggers dynamic child behaviour
|
|
conf = HttpdConf(env, extras={
|
|
'base': f"""
|
|
StartServers 1
|
|
ServerLimit 3
|
|
ThreadLimit 4
|
|
ThreadsPerChild 4
|
|
MinSpareThreads 4
|
|
MaxSpareThreads 6
|
|
MaxRequestWorkers 12
|
|
MaxConnectionsPerChild 0
|
|
|
|
LogLevel mpm_event:trace6
|
|
""",
|
|
})
|
|
conf.add_vhost_cgi()
|
|
conf.install()
|
|
|
|
# clear logs and start server, start load
|
|
env.httpd_error_log.clear_log()
|
|
assert env.apache_restart() == 0
|
|
# we should see a single child started
|
|
cd = ChildDynamics(env)
|
|
assert len(cd.changes) == 1, f"{cd.changes}"
|
|
assert cd.changes[0]['action'] == 'started'
|
|
# This loader simulates 6 clients, each making 10 requests.
|
|
# delay.py sleeps for 1sec, so this should run for about 10 seconds
|
|
loader = Loader(env=env, url=env.mkurl("https", "cgi", "/delay.py"),
|
|
clients=6, req_per_client=10)
|
|
loader.start()
|
|
# Expect 2 more children to have been started after half time
|
|
time.sleep(5)
|
|
cd = ChildDynamics(env)
|
|
assert len(cd.changes) == 3, f"{cd.changes}"
|
|
assert len([x for x in cd.changes if x['action'] == 'started']) == 3, f"{cd.changes}"
|
|
|
|
# Trigger a server reload
|
|
assert env.apache_reload() == 0
|
|
# a graceful reload lets ongoing requests continue, but
|
|
# after a while all gen 0 children should have stopped
|
|
time.sleep(3) # FIXME: this pbly depends on the runtime a lot, do we have expectations?
|
|
cd = ChildDynamics(env)
|
|
gen0 = [x for x in cd.changes if x['gen'] == 0]
|
|
assert len([x for x in gen0 if x['action'] == 'stopped']) == 3
|
|
|
|
# wait for the loader to finish and stop the server
|
|
loader.join()
|
|
env.apache_stop()
|
|
|
|
# Similar to before the reload, we expect 3 children to have
|
|
# been started and stopped again on server stop
|
|
cd = ChildDynamics(env)
|
|
gen1 = [x for x in cd.changes if x['gen'] == 1]
|
|
assert len([x for x in gen1 if x['action'] == 'started']) == 3
|
|
assert len([x for x in gen1 if x['action'] == 'stopped']) == 3
|