mirror of
https://github.com/progval/irctest.git
synced 2025-04-07 15:59:49 +00:00
unrealircd: Use lock around startup/shutdown instead of proot
to ensure no unrealircd instance is starting up while another clears $PREFIX/tmp/ While proot allows full parallelism and is less error-prone, it takes a long time to start; and segfaults on my Armbian system.
This commit is contained in:
@ -1,11 +1,11 @@
|
|||||||
|
import contextlib
|
||||||
|
import fcntl
|
||||||
import functools
|
import functools
|
||||||
import os
|
from pathlib import Path
|
||||||
import pathlib
|
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import List, Optional, Set, Type, Union
|
from typing import Callable, ContextManager, Iterator, Optional, Set, Type
|
||||||
|
|
||||||
from irctest.basecontrollers import (
|
from irctest.basecontrollers import (
|
||||||
BaseServerController,
|
BaseServerController,
|
||||||
@ -125,6 +125,35 @@ oper "operuser" {{
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _filelock(path: Path) -> Callable[[], ContextManager]:
|
||||||
|
"""Alternative to :cls:`multiprocessing.Lock` that works with pytest-xdist"""
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def f() -> Iterator[None]:
|
||||||
|
with open(path, "a") as fd:
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_EX)
|
||||||
|
yield
|
||||||
|
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
_UNREALIRCD_BIN = shutil.which("unrealircd")
|
||||||
|
if _UNREALIRCD_BIN:
|
||||||
|
_UNREALIRCD_PREFIX = Path(_UNREALIRCD_BIN).parent.parent
|
||||||
|
|
||||||
|
# Try to keep that lock file specific to this Unrealircd instance
|
||||||
|
_LOCK_PATH = _UNREALIRCD_PREFIX / "irctest-unrealircd-startstop.lock"
|
||||||
|
else:
|
||||||
|
# unrealircd not found; we are probably going to crash later anyway...
|
||||||
|
_LOCK_PATH = Path("/tmp/irctest-unrealircd-startstop.lock")
|
||||||
|
|
||||||
|
_STARTSTOP_LOCK = _filelock(_LOCK_PATH)
|
||||||
|
"""
|
||||||
|
Unreal cleans its tmp/ directory after each run, which prevents
|
||||||
|
multiple processes from starting/stopping at the same time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def installed_version() -> int:
|
def installed_version() -> int:
|
||||||
output = subprocess.check_output(["unrealircd", "-v"], universal_newlines=True)
|
output = subprocess.check_output(["unrealircd", "-v"], universal_newlines=True)
|
||||||
@ -170,18 +199,6 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
self.port = port
|
self.port = port
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.create_config()
|
self.create_config()
|
||||||
(unused_hostname, unused_port) = find_hostname_and_port()
|
|
||||||
(services_hostname, services_port) = find_hostname_and_port()
|
|
||||||
|
|
||||||
password_field = 'password "{}";'.format(password) if password else ""
|
|
||||||
|
|
||||||
self.gen_ssl()
|
|
||||||
if ssl:
|
|
||||||
(tls_hostname, tls_port) = (hostname, port)
|
|
||||||
(hostname, port) = (unused_hostname, unused_port)
|
|
||||||
else:
|
|
||||||
# Unreal refuses to start without TLS enabled
|
|
||||||
(tls_hostname, tls_port) = (unused_hostname, unused_port)
|
|
||||||
|
|
||||||
if installed_version() >= 6:
|
if installed_version() >= 6:
|
||||||
extras = textwrap.dedent(
|
extras = textwrap.dedent(
|
||||||
@ -208,6 +225,20 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
with self.open_file("empty.txt") as fd:
|
with self.open_file("empty.txt") as fd:
|
||||||
fd.write("\n")
|
fd.write("\n")
|
||||||
|
|
||||||
|
password_field = 'password "{}";'.format(password) if password else ""
|
||||||
|
|
||||||
|
with _STARTSTOP_LOCK():
|
||||||
|
(services_hostname, services_port) = find_hostname_and_port()
|
||||||
|
(unused_hostname, unused_port) = find_hostname_and_port()
|
||||||
|
|
||||||
|
self.gen_ssl()
|
||||||
|
if ssl:
|
||||||
|
(tls_hostname, tls_port) = (hostname, port)
|
||||||
|
(hostname, port) = (unused_hostname, unused_port)
|
||||||
|
else:
|
||||||
|
# Unreal refuses to start without TLS enabled
|
||||||
|
(tls_hostname, tls_port) = (unused_hostname, unused_port)
|
||||||
|
|
||||||
assert self.directory
|
assert self.directory
|
||||||
|
|
||||||
with self.open_file("unrealircd.conf") as fd:
|
with self.open_file("unrealircd.conf") as fd:
|
||||||
@ -228,22 +259,6 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
proot_cmd: List[Union[str, pathlib.Path]] = []
|
|
||||||
self.using_proot = False
|
|
||||||
if shutil.which("proot"):
|
|
||||||
unrealircd_path = shutil.which("unrealircd")
|
|
||||||
if unrealircd_path:
|
|
||||||
unrealircd_prefix = pathlib.Path(unrealircd_path).parents[1]
|
|
||||||
tmpdir = self.directory / "tmp"
|
|
||||||
tmpdir.mkdir()
|
|
||||||
# Unreal cleans its tmp/ directory after each run, which prevents
|
|
||||||
# multiple processes from running at the same time.
|
|
||||||
# Using PRoot, we can isolate them, with a tmp/ directory for each
|
|
||||||
# process, so they don't interfere with each other, allowing use of
|
|
||||||
# the -n option (of pytest-xdist) to speed-up tests
|
|
||||||
proot_cmd = ["proot", "-b", f"{tmpdir}:{unrealircd_prefix}/tmp"]
|
|
||||||
self.using_proot = True
|
|
||||||
|
|
||||||
if faketime and shutil.which("faketime"):
|
if faketime and shutil.which("faketime"):
|
||||||
faketime_cmd = ["faketime", "-f", faketime]
|
faketime_cmd = ["faketime", "-f", faketime]
|
||||||
self.faketime_enabled = True
|
self.faketime_enabled = True
|
||||||
@ -252,7 +267,6 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
|
|
||||||
self.proc = subprocess.Popen(
|
self.proc = subprocess.Popen(
|
||||||
[
|
[
|
||||||
*proot_cmd,
|
|
||||||
*faketime_cmd,
|
*faketime_cmd,
|
||||||
"unrealircd",
|
"unrealircd",
|
||||||
"-t",
|
"-t",
|
||||||
@ -262,9 +276,9 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
],
|
],
|
||||||
# stdout=subprocess.DEVNULL,
|
# stdout=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
|
self.wait_for_port()
|
||||||
|
|
||||||
if run_services:
|
if run_services:
|
||||||
self.wait_for_port()
|
|
||||||
self.services_controller = self.services_controller_class(
|
self.services_controller = self.services_controller_class(
|
||||||
self.test_config, self
|
self.test_config, self
|
||||||
)
|
)
|
||||||
@ -274,17 +288,13 @@ class UnrealircdController(BaseServerController, DirectoryBasedController):
|
|||||||
server_port=services_port,
|
server_port=services_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def kill(self) -> None:
|
def kill_proc(self) -> None:
|
||||||
if self.using_proot:
|
assert self.proc
|
||||||
# Kill grandchild process, instead of killing proot, which takes more
|
|
||||||
# time (and does not seem to always work)
|
with _STARTSTOP_LOCK():
|
||||||
assert self.proc is not None
|
self.proc.kill()
|
||||||
output = subprocess.check_output(
|
self.proc.wait(5) # wait for it to actually die
|
||||||
["ps", "-opid", "--no-headers", "--ppid", str(self.proc.pid)]
|
self.proc = None
|
||||||
)
|
|
||||||
(grandchild_pid,) = [int(line) for line in output.decode().split()]
|
|
||||||
os.kill(grandchild_pid, signal.SIGKILL)
|
|
||||||
super().kill()
|
|
||||||
|
|
||||||
|
|
||||||
def get_irctest_controller_class() -> Type[UnrealircdController]:
|
def get_irctest_controller_class() -> Type[UnrealircdController]:
|
||||||
|
Reference in New Issue
Block a user