forked from platypush/platypush
Added ControllableProcess
class.
This class can be used to easily control the execution of underlying processes.
This commit is contained in:
parent
efef9d7bc0
commit
b1f244a812
1 changed files with 135 additions and 0 deletions
135
platypush/process/__init__.py
Normal file
135
platypush/process/__init__.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from logging import getLogger
|
||||
from multiprocessing import Event, Process, RLock
|
||||
from os import getpid
|
||||
from typing import Optional
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
class ControllableProcess(Process, ABC):
|
||||
"""
|
||||
Extends the ``Process`` class to allow for external extended control of the
|
||||
underlying process.
|
||||
"""
|
||||
|
||||
_kill_timeout: float = 10.0
|
||||
|
||||
def __init__(self, *args, timeout: Optional[float] = None, **kwargs):
|
||||
kwargs['name'] = kwargs.get('name', self.__class__.__name__)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.timeout = timeout
|
||||
self.logger = getLogger(self.name)
|
||||
self._should_stop = Event()
|
||||
self._stop_lock = RLock()
|
||||
self._should_restart = False
|
||||
|
||||
@property
|
||||
def _timeout_err(self) -> TimeoutError:
|
||||
return TimeoutError(f'Process {self.name} timed out')
|
||||
|
||||
@property
|
||||
def should_stop(self) -> bool:
|
||||
"""
|
||||
:return: ``True`` if the process is scheduled for stop, ``False``
|
||||
otherwise.
|
||||
"""
|
||||
return self._should_stop.is_set()
|
||||
|
||||
def wait_stop(self, timeout: Optional[float] = None) -> None:
|
||||
"""
|
||||
Waits for the process to stop.
|
||||
|
||||
:param timeout: The maximum time to wait for the process to stop.
|
||||
"""
|
||||
timeout = timeout if timeout is not None else self.timeout
|
||||
stopped = self._should_stop.wait(timeout=timeout)
|
||||
|
||||
if not stopped:
|
||||
raise self._timeout_err
|
||||
|
||||
if self.pid == getpid():
|
||||
return # Prevent termination deadlock
|
||||
|
||||
self.join(timeout=timeout)
|
||||
if self.is_alive():
|
||||
raise self._timeout_err
|
||||
|
||||
def stop(self, timeout: Optional[float] = None) -> None:
|
||||
"""
|
||||
Stops the process.
|
||||
|
||||
:param timeout: The maximum time to wait for the process to stop.
|
||||
"""
|
||||
timeout = timeout if timeout is not None else self.timeout
|
||||
with self._stop_lock:
|
||||
self._should_stop.set()
|
||||
self.on_stop()
|
||||
|
||||
if self.pid == getpid():
|
||||
return # Prevent termination deadlock
|
||||
|
||||
try:
|
||||
self.wait_stop(timeout=timeout)
|
||||
except TimeoutError:
|
||||
pass
|
||||
finally:
|
||||
self.terminate()
|
||||
|
||||
try:
|
||||
self.wait_stop(timeout=self._kill_timeout)
|
||||
except TimeoutError:
|
||||
self.logger.warning(
|
||||
'The process %s is still alive after %f seconds, killing it',
|
||||
self.name,
|
||||
self._kill_timeout,
|
||||
)
|
||||
self.kill()
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""
|
||||
Handler called when the process is stopped.
|
||||
|
||||
It can be implemented by subclasses.
|
||||
"""
|
||||
|
||||
@property
|
||||
def should_restart(self) -> bool:
|
||||
"""
|
||||
:return: ``True`` if the process is marked for restart after stop,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
return self._should_restart
|
||||
|
||||
def mark_for_restart(self):
|
||||
"""
|
||||
Marks the process for restart after stop.
|
||||
"""
|
||||
self._should_restart = True
|
||||
|
||||
@abstractmethod
|
||||
def main(self):
|
||||
"""
|
||||
The main function of the process.
|
||||
|
||||
It must be implemented by subclasses.
|
||||
"""
|
||||
|
||||
def _main(self):
|
||||
"""
|
||||
Wrapper for the main function of the process.
|
||||
"""
|
||||
self._should_restart = False
|
||||
return self.main()
|
||||
|
||||
@override
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Executes the process.
|
||||
"""
|
||||
super().run()
|
||||
|
||||
try:
|
||||
self._main()
|
||||
finally:
|
||||
self._should_stop.set()
|
||||
self.logger.info('Process terminated')
|
Loading…
Reference in a new issue