From: Vasek Sraier Date: Tue, 12 Jul 2022 13:46:27 +0000 (+0200) Subject: manager: supervisord plugin for creating PIPE to stdout X-Git-Tag: v6.0.0a1~29^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3f2397dc685fe2a413799d1595aeef12aadb2c62;p=thirdparty%2Fknot-resolver.git manager: supervisord plugin for creating PIPE to stdout --- diff --git a/manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py b/manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py new file mode 100644 index 000000000..542bdde1d --- /dev/null +++ b/manager/knot_resolver_manager/kresd_controller/supervisord/plugin/stdout_pipe_log.py @@ -0,0 +1,52 @@ +# type: ignore +# pylint: disable=protected-access +""" +Plugin which creates a new fd at `NEW_STDOUT_FD` and a thread copying data from there to actual stdout. +Why would we want this? Because when running under systemd, stdout FD is a socket and we can't open it +by calling `open("/proc/self/fd/1")`. We can do this with pipes though. So in order to transparently pass +stdout from manager to stdout of supervisord, we are configuring manager to use /proc/self/fd/42001 as its +logfile. Then we are routing the data to the actual supervisord's stdout. + +Performance should not be a problem as this is not a performance critical component. +""" +import os +import sys +from threading import Thread +from typing import Any + +from supervisor.supervisord import Supervisor + +# when changing this, change it in supervisord.conf.j2 as well +NEW_STDOUT_FD = 42 + + +class SplicingThread(Thread): + def __init__(self, source_fd: int, target_fd: int) -> None: + super().__init__(daemon=True, name=f"FD-splice-{source_fd}->{target_fd}") + self.source_fd = source_fd + self.dest_fd = target_fd + + def run(self) -> None: + if sys.version_info.major >= 3 and sys.version_info.minor >= 10: + while True: + os.splice(self.source_fd, self.dest_fd, 1024) # type: ignore[attr-defined] + else: + while True: + buf = os.read(self.source_fd, 1024) + os.write(self.dest_fd, buf) + + +def make_rpcinterface(_supervisord: Supervisor, **_config: Any) -> Any: # pylint: disable=useless-return + # create pipe + (r, w) = os.pipe() + os.dup2(w, NEW_STDOUT_FD) + os.close(w) + + # start splicing + t = SplicingThread(r, sys.stdout.fileno()) + t.start() + + # this method is called by supervisord when loading the plugin, + # it should return XML-RPC object, which we don't care about + # That's why why are returning just None + return None diff --git a/manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2 b/manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2 index c77913eaa..1171dd1c9 100644 --- a/manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2 +++ b/manager/knot_resolver_manager/kresd_controller/supervisord/supervisord.conf.j2 @@ -28,10 +28,13 @@ supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervi [rpcinterface:notify] supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervisord.plugin:make_rpcinterface +[rpcinterface:stdout_pipe] +supervisor.rpcinterface_factory = knot_resolver_manager.kresd_controller.supervisord.plugin.stdout_pipe_log:make_rpcinterface + [program:manager] redirect_stderr=false -stdout_logfile=/proc/self/fd/1 +stdout_logfile=/proc/self/fd/42 # handled by stdout_pipe plugin stdout_logfile_maxbytes=0 directory={{ manager.workdir }} command={{ manager.command }}