From c96ffe52c07f019b5ef656698772cf6cc163d609 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Tue, 29 Oct 2019 11:25:03 +0100 Subject: [PATCH] Add Process and Signals support Signed-off-by: Jan Lindemann --- tools/python/jwutils/Process.py | 68 ++++++++++++++++++++++++ tools/python/jwutils/Signals.py | 34 ++++++++++++ tools/python/jwutils/asyncio/Makefile | 4 ++ tools/python/jwutils/asyncio/Process.py | 20 +++++++ tools/python/jwutils/asyncio/Signals.py | 10 ++++ tools/python/jwutils/asyncio/__init__.py | 3 ++ 6 files changed, 139 insertions(+) create mode 100644 tools/python/jwutils/Process.py create mode 100644 tools/python/jwutils/Signals.py create mode 100644 tools/python/jwutils/asyncio/Makefile create mode 100644 tools/python/jwutils/asyncio/Process.py create mode 100644 tools/python/jwutils/asyncio/Signals.py create mode 100644 tools/python/jwutils/asyncio/__init__.py diff --git a/tools/python/jwutils/Process.py b/tools/python/jwutils/Process.py new file mode 100644 index 0000000..faf0084 --- /dev/null +++ b/tools/python/jwutils/Process.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod +from enum import Enum, Flag, auto + +def _sigchld_handler(signum, process): + if not signum == signal.SIGCHLD: + return + Process.propagate_signal(signum) + +class Process(ABC): # export + + __processes = [] + + class State(Enum): + Running = auto() + Shutdown = auto() + Done = auto() + + class Flags(Flag): + FailOnExitWithoutShutdown = auto() + + def __init__(self): + self.__state = Running + self.__flags = Flags.FailOnExitWithoutShutdown + if len(self.__processes) == 0: + self._signals().add_handler(signals.SIGCHLD, _sigchld_handler) + self.__processes.add(self) + + @classmethod + def propagate_signal(cls, signum): + for p in cls.__processes: + p.__signal(signum) + + def signal(self, signum): + if signum == signals.SIGCHLD: + self.exited() + + @abstractmethod + def _pid(self): + pass + + @classmethod + @abstractmethod + def signals(cls): + pass + + # to be reimplemented + def _request_shutdown(self): + pass + + # to be reimplemented + def name(self): + return str(self._pid()) + + def request_shutdown(self): + if not self.__state == Shutdown: + self.__state = Shutdown + self._request_shutdown() + + def exited(self): + if self.__state == Process.State.Running: + slog(ERR, 'process "{}" exited unexpectedly'.format(process.name())) + if __flags & Process.Flags.FailOnExitWithoutShutdown: + slog(ERR, 'exiting') + exit(1) + self.__state = Process.State.Done + self.__processes.erase(self) + if len(self.__processes) == 0: + self._signals().remove_handler(signals.SIGCHLD) # FIXME: broken logic diff --git a/tools/python/jwutils/Signals.py b/tools/python/jwutils/Signals.py new file mode 100644 index 0000000..52d7819 --- /dev/null +++ b/tools/python/jwutils/Signals.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod + +_handled_signals = {} + +def _signal_handler(signal, frame): + if not signal in _handled_signals.keys(): + return + for h in _handled_signals[signal]: + h.func(signal, *h.args) + +class Signals: + + class Handler: + def __init__(self, func, args): + self.func = func + self.args = args + + def __init(self): + pass + + @classmethod + @abstractmethod + def _add_handler(self, signal, handler): + raise Exception("_add_handler() is not reimplemented") + + @classmethod + def add_handler(cls, signals, handler, *args): + for signal in signals: + h = Signals.Handler(handler, args) + if not signal in _handled_signals.keys(): + _handled_signals[signal] = [h] + cls._add_signal_handler(signal, _signal_handler) + else: + _handled_signals[signal].add(h) diff --git a/tools/python/jwutils/asyncio/Makefile b/tools/python/jwutils/asyncio/Makefile new file mode 100644 index 0000000..59b3ac1 --- /dev/null +++ b/tools/python/jwutils/asyncio/Makefile @@ -0,0 +1,4 @@ +TOPDIR = ../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-mod.mk diff --git a/tools/python/jwutils/asyncio/Process.py b/tools/python/jwutils/asyncio/Process.py new file mode 100644 index 0000000..d654da8 --- /dev/null +++ b/tools/python/jwutils/asyncio/Process.py @@ -0,0 +1,20 @@ +from abc import abstractmethod + +from ..Process import Process as ProcessBase +from .Signals import Signals + +class Process(ProcessBase): # export + + __signals = Signals() + + def __init__(self, aio): + super().__init() + self.aio = aio + + @classmethod + @abstractmethod + def signals(cls): + return cls.__signals + + def wait(self): + return self.aio.wait() diff --git a/tools/python/jwutils/asyncio/Signals.py b/tools/python/jwutils/asyncio/Signals.py new file mode 100644 index 0000000..b6b43ac --- /dev/null +++ b/tools/python/jwutils/asyncio/Signals.py @@ -0,0 +1,10 @@ +import asyncio +from ..Signals import Signals as SignalsBase + +class Signals(SignalsBase): + + # reimplemented from Signals + @classmethod + def _add_handler(cls, signal, handler): + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal, handler, None) # None = *args diff --git a/tools/python/jwutils/asyncio/__init__.py b/tools/python/jwutils/asyncio/__init__.py new file mode 100644 index 0000000..fcdc905 --- /dev/null +++ b/tools/python/jwutils/asyncio/__init__.py @@ -0,0 +1,3 @@ +# >> -------------------------- generated by python-tools.sh >> +from jwutils.asyncio.Process import Process +# << -------------------------- generated by python-tools.sh <<