<gh_stars>10-100
# coding: utf-8
"""
Daemons.
"""

# Import python libs
from __future__ import absolute_import, with_statement, print_function, unicode_literals
import os
import signal
import contextlib
import subprocess
import multiprocessing
import multiprocessing.util

from sugar.lib.logger.manager import get_logger

log = get_logger(__name__)  # pylint: disable=C0103

# pylint: disable=W0212,W0613,import-error
HAS_PSUTIL = False
try:
    import psutil
    HAS_PSUTIL = True
except ImportError:
    pass

try:
    import setproctitle
    HAS_SETPROCTITLE = True
except ImportError:
    HAS_SETPROCTITLE = False


def appendproctitle(name):
    """
    Append 'name' to the current process title

    :param name: name of the title
    :return: None
    """
    if HAS_SETPROCTITLE:
        setproctitle.setproctitle(setproctitle.getproctitle() + ' ' + name)


def systemd_notify_call(action):
    """
    Notify call to the systemd.

    :param action: string
    :return: bool
    """
    process = subprocess.Popen(['systemd-notify', action], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    process.communicate()
    status = process.poll()

    return status == 0


class MultiprocessingProcess(multiprocessing.Process):
    """
    Multiprocessing process
    """
    def __new__(cls, *args, **kwargs):
        instance = super(MultiprocessingProcess, cls).__new__(cls)
        # Patch the run method at runtime because decorating the run method
        # with a function with a similar behavior would be ignored once this
        # class'es run method is overridden.
        instance._original_run = instance.run
        instance.run = instance._run

        return instance

    # __setstate__ and __getstate__ are only used on Windows.
    # We do this so that __init__ will be invoked on Windows in the child
    # process so that a register_after_fork() equivalent will work on Windows.
    def __setstate__(self, state):
        self._is_child = True  # pylint: disable=W0201
        args = state['args']
        kwargs = state['kwargs']
        # This will invoke __init__ of the most derived class.
        self.__init__(*args, **kwargs)

    def _run(self):
        try:
            return self._original_run()
        except SystemExit:
            # These are handled by multiprocessing.Process._bootstrap()
            log.error("System exit")
            raise
        except Exception:
            log.error("An un-handled exception from the multiprocessing process '{}' was caught:\n".format(self.name,))
            raise


class SignalHandlingMultiprocessingProcess(MultiprocessingProcess):
    """
    Signal handling multiprocess
    """
    def __init__(self, *args, **kwargs):
        super(SignalHandlingMultiprocessingProcess, self).__init__(*args, **kwargs)
        multiprocessing.util.register_after_fork(self, SignalHandlingMultiprocessingProcess.__setup_signals)

    def __setup_signals(self):
        signal.signal(signal.SIGINT, self._handle_signals)
        signal.signal(signal.SIGTERM, self._handle_signals)

    def _handle_signals(self, signum, sigframe):
        signal.signal(signal.SIGTERM, signal.SIG_IGN)
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        msg = '{0} received a '.format(self.__class__.__name__)
        if signum == signal.SIGINT:
            msg += 'SIGINT'
        elif signum == signal.SIGTERM:
            msg += 'SIGTERM'
        msg += '. Exiting'
        log.info(msg)
        if HAS_PSUTIL:
            process = psutil.Process(self.pid)
            if hasattr(process, 'children'):
                for child in process.children(recursive=True):
                    if child.is_running():
                        child.terminate()
        os.kill(self.pid, signal.SIGKILL)

    def kill(self) -> None:
        """
        Kill current process gracefully, via SIGTERM chain.

        :return: None
        """
        os.kill(self.pid, signal.SIGTERM)

    def start(self):
        """
        Start process.

        :return: None
        """
        with default_signals(signal.SIGINT, signal.SIGTERM):
            super(SignalHandlingMultiprocessingProcess, self).start()


@contextlib.contextmanager
def default_signals(*signals):
    """
    Set default signals.

    :param signals:
    :return: signal
    """
    old_signals = {}
    for signum in signals:
        try:
            old_signals[signum] = signal.getsignal(signum)
            signal.signal(signum, signal.SIG_DFL)
        except ValueError as exc:
            # This happens when a netapi module attempts to run a function
            # using wheel_async, because the process trying to register signals
            # will not be the main PID.
            log.trace(
                'Failed to register signal for signum %d: %s',
                signum, exc
            )

    # Do whatever is needed with the reset signals
    yield

    # Restore signals
    for signum in old_signals:
        signal.signal(signum, old_signals[signum])

    del old_signals

# coding: utf-8
"""
Daemons.
"""

# Import python libs
from __future__ import absolute_import, with_statement, print_function, unicode_literals
import os
import signal
import contextlib
import subprocess
import multiprocessing
import multiprocessing.util

from sugar.lib.logger.manager import get_logger

log = get_logger(__name__)  # pylint: disable=C0103

# pylint: disable=W0212,W0613,import-error
HAS_PSUTIL = False
try:
    import psutil
    HAS_PSUTIL = True
except ImportError:
    pass

try:
    import setproctitle
    HAS_SETPROCTITLE = True
except ImportError:
    HAS_SETPROCTITLE = False


def appendproctitle(name):
    """
    Append 'name' to the current process title

    :param name: name of the title
    :return: None
    """
    if HAS_SETPROCTITLE:
        setproctitle.setproctitle(setproctitle.getproctitle() + ' ' + name)


def systemd_notify_call(action):
    """
    Notify call to the systemd.

    :param action: string
    :return: bool
    """
    process = subprocess.Popen(['systemd-notify', action], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    process.communicate()
    status = process.poll()

    return status == 0


class MultiprocessingProcess(multiprocessing.Process):
    """
    Multiprocessing process
    """
    def __new__(cls, *args, **kwargs):
        instance = super(MultiprocessingProcess, cls).__new__(cls)
        # Patch the run method at runtime because decorating the run method
        # with a function with a similar behavior would be ignored once this
        # class'es run method is overridden.
        instance._original_run = instance.run
        instance.run = instance._run

        return instance

    # __setstate__ and __getstate__ are only used on Windows.
    # We do this so that __init__ will be invoked on Windows in the child
    # process so that a register_after_fork() equivalent will work on Windows.
    def __setstate__(self, state):
        self._is_child = True  # pylint: disable=W0201
        args = state['args']
        kwargs = state['kwargs']
        # This will invoke __init__ of the most derived class.
        self.__init__(*args, **kwargs)

    def _run(self):
        try:
            return self._original_run()
        except SystemExit:
            # These are handled by multiprocessing.Process._bootstrap()
            log.error("System exit")
            raise
        except Exception:
            log.error("An un-handled exception from the multiprocessing process '{}' was caught:\n".format(self.name,))
            raise


class SignalHandlingMultiprocessingProcess(MultiprocessingProcess):
    """
    Signal handling multiprocess
    """
    def __init__(self, *args, **kwargs):
        super(SignalHandlingMultiprocessingProcess, self).__init__(*args, **kwargs)
        multiprocessing.util.register_after_fork(self, SignalHandlingMultiprocessingProcess.__setup_signals)

    def __setup_signals(self):
        signal.signal(signal.SIGINT, self._handle_signals)
        signal.signal(signal.SIGTERM, self._handle_signals)

    def _handle_signals(self, signum, sigframe):
        signal.signal(signal.SIGTERM, signal.SIG_IGN)
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        msg = '{0} received a '.format(self.__class__.__name__)
        if signum == signal.SIGINT:
            msg += 'SIGINT'
        elif signum == signal.SIGTERM:
            msg += 'SIGTERM'
        msg += '. Exiting'
        log.info(msg)
        if HAS_PSUTIL:
            process = psutil.Process(self.pid)
            if hasattr(process, 'children'):
                for child in process.children(recursive=True):
                    if child.is_running():
                        child.terminate()
        os.kill(self.pid, signal.SIGKILL)

    def kill(self) -> None:
        """
        Kill current process gracefully, via SIGTERM chain.

        :return: None
        """
        os.kill(self.pid, signal.SIGTERM)

    def start(self):
        """
        Start process.

        :return: None
        """
        with default_signals(signal.SIGINT, signal.SIGTERM):
            super(SignalHandlingMultiprocessingProcess, self).start()


@contextlib.contextmanager
def default_signals(*signals):
    """
    Set default signals.

    :param signals:
    :return: signal
    """
    old_signals = {}
    for signum in signals:
        try:
            old_signals[signum] = signal.getsignal(signum)
            signal.signal(signum, signal.SIG_DFL)
        except ValueError as exc:
            # This happens when a netapi module attempts to run a function
            # using wheel_async, because the process trying to register signals
            # will not be the main PID.
            log.trace(
                'Failed to register signal for signum %d: %s',
                signum, exc
            )

    # Do whatever is needed with the reset signals
    yield

    # Restore signals
    for signum in old_signals:
        signal.signal(signum, old_signals[signum])

    del old_signals
# coding: utf-8 Daemons. # Import python libs # pylint: disable=C0103 # pylint: disable=W0212,W0613,import-error Append 'name' to the current process title      :param name: name of the title     :return: None Notify call to the systemd.      :param action: string     :return: bool Multiprocessing process # Patch the run method at runtime because decorating the run method # with a function with a similar behavior would be ignored once this # class'es run method is overridden. # __setstate__ and __getstate__ are only used on Windows. # We do this so that __init__ will be invoked on Windows in the child # process so that a register_after_fork() equivalent will work on Windows. # pylint: disable=W0201 # This will invoke __init__ of the most derived class. # These are handled by multiprocessing.Process._bootstrap() Signal handling multiprocess Kill current process gracefully, via SIGTERM chain.          :return: None Start process.          :return: None Set default signals.      :param signals:     :return: signal # This happens when a netapi module attempts to run a function # using wheel_async, because the process trying to register signals # will not be the main PID. # Do whatever is needed with the reset signals # Restore signals