from typing import TYPE_CHECKING, Optional
from knot_resolver_manager.utils import which
-from knot_resolver_manager.utils.functional import Result
if TYPE_CHECKING:
from knot_resolver_manager.config_store import ConfigStore
from knot_resolver_manager.datamodel.slice_schema import SliceSchema
from knot_resolver_manager.datamodel.static_hints_schema import StaticHintsSchema
from knot_resolver_manager.datamodel.stub_zone_schema import StubZoneSchema
-from knot_resolver_manager.datamodel.types.types import IDPattern, IntPositive, UncheckedPath
+from knot_resolver_manager.datamodel.types.types import IntPositive, UncheckedPath
from knot_resolver_manager.datamodel.view_schema import ViewSchema
from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema
from knot_resolver_manager.exceptions import DataException
controllers such as supervisord to allow them to run as top-level
process in a process tree.
"""
+
def __init__(self, exec_args: List[str], *args: object) -> None:
self.exec_args = exec_args
super().__init__(*args)
import logging
-from os import execl, kill
+from os import kill
from pathlib import Path
-from time import sleep
from typing import Any, Dict, Iterable, NoReturn, Optional, Union, cast
from xmlrpc.client import Fault, ServerProxy
logger.debug("Writing supervisord config")
await write_config_file(config)
logger.debug("Execing supervisord")
- raise CancelStartupExecInsteadException([str(which.which("supervisord")), "supervisord", "--configuration", str(supervisord_config_file(config).absolute()), "-n"])
+ raise CancelStartupExecInsteadException(
+ [
+ str(which.which("supervisord")),
+ "supervisord",
+ "--configuration",
+ str(supervisord_config_file(config).absolute()),
+ ]
+ )
async def _reload_supervisord(config: KresConfig) -> None:
await write_config_file(config)
- res = await call(f'supervisorctl --configuration="{supervisord_config_file(config).absolute()}" update', shell=True)
- if res != 0:
- raise SubprocessControllerException(f"Supervisord reload failed with exit code {res}")
+ try:
+ supervisord = _create_supervisord_proxy(config)
+ supervisord.reloadConfig()
+ except Fault as e:
+ raise SubprocessControllerException("supervisord reload failed") from e
@async_in_a_thread
def _stop_supervisord(config: KresConfig) -> None:
supervisord = _create_supervisord_proxy(config)
- pid = supervisord.getPID()
- supervisord.shutdown()
+ # pid = supervisord.getPID()
try:
- while True:
- kill(pid, 0)
- sleep(0.1)
- except ProcessLookupError:
- pass # there is finally no supervisord process
- supervisord_config_file(config).unlink()
+ # we might be trying to shut down supervisord at a moment, when it's waiting
+ # for us to stop. Therefore, this shutdown request for supervisord might
+ # die and it's not a problem.
+ supervisord.shutdown()
+ except Fault as e:
+ if e.faultCode == 6 and e.faultString == "SHUTDOWN_STATE":
+ # supervisord is already stopping, so it's fine
+ pass
+ else:
+ # something wrong happened, let's be loud about it
+ raise
+
+ # We could remove the configuration, but there is actually no specific need to do so.
+ # If we leave it behind, someone might find it and use it to start us from scratch again,
+ # which is perfectly fine.
+ # supervisord_config_file(config).unlink()
async def _is_supervisord_available() -> bool:
self._controller_config = config
if not await _is_supervisord_running(config):
- #await _start_supervisord(config)
+ logger.info(
+ "We want supervisord to restart us when needed, we will therefore exec() it and let it start us again."
+ )
await _exec_supervisord(config)
else:
+ logger.info("Supervisord is already running, we will just update its config...")
await _reload_supervisord(config)
async def shutdown_controller(self) -> None:
)
@staticmethod
- def create_manager_config(config: KresConfig) -> "ProcessTypeConfig":
+ def create_manager_config(_config: KresConfig) -> "ProcessTypeConfig":
# read original command from /proc
- with open("/proc/self/cmdline", 'rb') as f:
- args = [s.decode('utf-8') for s in f.read()[:-1].split(b'\0')]
+ with open("/proc/self/cmdline", "rb") as f:
+ args = [s.decode("utf-8") for s in f.read()[:-1].split(b"\0")]
cmd = '"' + '" "'.join(args) + '"'
return ProcessTypeConfig( # type: ignore[call-arg]
workdir=user_constants().working_directory_on_startup,
command=cmd,
- environment='X-SUPERVISORD-TYPE=notify',
- logfile="" # this will be ignored
+ environment="X-SUPERVISORD-TYPE=notify",
+ logfile="", # this will be ignored
)
[supervisord]
pidfile = {{ config.pid_file }}
directory = {{ config.workdir }}
-nodaemon = false
+nodaemon = true
logfile = {{ config.logfile }}
logfile_maxbytes = 50MB
loglevel = info
+silent=true
{# user=root #}
[unix_http_server]
import asyncio
-import atexit
import errno
import logging
import os
from aiohttp.web_response import json_response
from aiohttp.web_runner import AppRunner, TCPSite, UnixSite
+import knot_resolver_manager.utils.custom_atexit as atexit
from knot_resolver_manager import log, statistics
from knot_resolver_manager.compat import asyncio as asyncio_compat
from knot_resolver_manager.config_store import ConfigStore
from knot_resolver_manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants
from knot_resolver_manager.datamodel.config_schema import KresConfig
from knot_resolver_manager.datamodel.management_schema import ManagementSchema
-from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, DataException, KresManagerException, SchemaException
+from knot_resolver_manager.exceptions import (
+ CancelStartupExecInsteadException,
+ DataException,
+ KresManagerException,
+ SchemaException,
+)
from knot_resolver_manager.kresd_controller import get_best_controller_implementation
from knot_resolver_manager.utils.async_utils import readfile
from knot_resolver_manager.utils.functional import Result
from knot_resolver_manager.utils.parsing import ParsedTree, parse, parse_yaml
-from knot_resolver_manager.utils.types import NoneType
from knot_resolver_manager.utils.systemd_notify import systemd_notify
+from knot_resolver_manager.utils.types import NoneType
from .kres_manager import KresManager
async def start_server(config: Union[Path, ParsedTree] = DEFAULT_MANAGER_CONFIG_FILE) -> int:
+ # This function is quite long, but it describes how manager runs. So let's silence pylint
+ # pylint: disable=too-many-statements
+
start_time = time()
working_directory_on_startup = os.getcwd()
manager: Optional[KresManager] = None
signal.pthread_sigmask(signal.SIG_UNBLOCK, Server.all_handled_signals())
# run exit functions
- atexit._run_exitfuncs()
+ atexit.run_callbacks()
- # and finally exec what we we told to exec
+ # and finally exec what we were told to exec
os.execl(*e.exec_args)
except KresManagerException as e:
--- /dev/null
+"""
+Custom replacement for standard module `atexit`. We use `atexit` behind the scenes, we just add the option
+to invoke the exit functions manually.
+"""
+
+import atexit
+from typing import Callable, List
+
+_at_exit_functions: List[Callable[[], None]] = []
+
+
+def register(func: Callable[[], None]) -> None:
+ _at_exit_functions.append(func)
+ atexit.register(func)
+
+
+def run_callbacks() -> None:
+ for func in _at_exit_functions:
+ func()
+ atexit.unregister(func)