def run_processes(self,
                      procdetails: List[ProcessDetails],
                      subproc_run_timeout_sec: float = 1,
                      stop_event_timeout_ms: int = 1000,
                      kill_timeout_sec: float = 5) -> None:
        """
        Run multiple child processes.

        Args:

            procdetails: list of :class:`ProcessDetails` objects (q.v.)
            subproc_run_timeout_sec: time (in seconds) to wait for each process
                when polling child processes to see how they're getting on
                (default ``1``)
            stop_event_timeout_ms: time to wait (in ms) while checking the
                Windows stop event for this service (default ``1000``)
            kill_timeout_sec: how long (in seconds) will we wait for the
                subprocesses to end peacefully, before we try to kill them?

        .. todo::
            cardinal_pythonlib.winservice.WindowsService: NOT YET IMPLEMENTED:
            Windows service autorestart
        """

        # https://stackoverflow.com/questions/16333054
        def cleanup():
            self.debug("atexit function called: cleaning up")
            for pmgr_ in self.process_managers:
                pmgr_.stop()

        atexit.register(cleanup)

        # Set up process info
        self.process_managers = []  # type: List[ProcessManager]
        n = len(procdetails)
        for i, details in enumerate(procdetails):
            pmgr = ProcessManager(details, i + 1, n,
                                  kill_timeout_sec=kill_timeout_sec,
                                  debugging=self.debugging)
            self.process_managers.append(pmgr)

        # Start processes
        for pmgr in self.process_managers:
            pmgr.start()
        self.info("All started")

        # Run processes
        something_running = True
        stop_requested = False
        subproc_failed = False
        while something_running and not stop_requested and not subproc_failed:
            if (win32event.WaitForSingleObject(
                    self.h_stop_event,
                    stop_event_timeout_ms) == win32event.WAIT_OBJECT_0):
                stop_requested = True
                self.info("Stop requested; stopping")
            else:
                something_running = False
                for pmgr in self.process_managers:
                    if subproc_failed:
                        break
                    try:
                        retcode = pmgr.wait(timeout_s=subproc_run_timeout_sec)
                        if retcode != 0:
                            subproc_failed = True
                    except subprocess.TimeoutExpired:
                        something_running = True

        # Kill any outstanding processes
        #
        # (a) Slow way
        # for pmgr in self.process_managers:
        #     pmgr.stop()
        #
        # (b) Faster (slightly more parallel) way
        # for pmgr in self.process_managers:
        #     pmgr.terminate()
        # for pmgr in self.process_managers:
        #     pmgr.stop_having_terminated()
        #
        # ... No, it's bad if we leave things orphaned.
        # Let's go for slow, clean code.

        for pmgr in self.process_managers:
            pmgr.stop()
        self.info("All stopped")
def run_processes ( self , procdetails : List [ ProcessDetails ] , subproc_run_timeout_sec : float = 1 , stop_event_timeout_ms : int = 1000 , kill_timeout_sec : float = 5 ) -> None : def cleanup ( ) : self . debug ( "atexit function called: cleaning up" ) for pmgr_ in self . process_managers : pmgr_ . stop ( ) atexit . register ( cleanup ) self . process_managers = [ ] n = len ( procdetails ) for i , details in enumerate ( procdetails ) : pmgr = ProcessManager ( details , i + 1 , n , kill_timeout_sec = kill_timeout_sec , debugging = self . debugging ) self . process_managers . append ( pmgr ) for pmgr in self . process_managers : pmgr . start ( ) self . info ( "All started" ) something_running = True stop_requested = False subproc_failed = False while something_running and not stop_requested and not subproc_failed : if ( win32event . WaitForSingleObject ( self . h_stop_event , stop_event_timeout_ms ) == win32event . WAIT_OBJECT_0 ) : stop_requested = True self . info ( "Stop requested; stopping" ) else : something_running = False for pmgr in self . process_managers : if subproc_failed : break try : retcode = pmgr . wait ( timeout_s = subproc_run_timeout_sec ) if retcode != 0 : subproc_failed = True except subprocess . TimeoutExpired : something_running = True for pmgr in self . process_managers : pmgr . stop ( ) self . info ( "All stopped" )
Run multiple child processes.

        Args:

            procdetails: list of :class:`ProcessDetails` objects (q.v.)
            subproc_run_timeout_sec: time (in seconds) to wait for each process
                when polling child processes to see how they're getting on
                (default ``1``)
            stop_event_timeout_ms: time to wait (in ms) while checking the
                Windows stop event for this service (default ``1000``)
            kill_timeout_sec: how long (in seconds) will we wait for the
                subprocesses to end peacefully, before we try to kill them?

        .. todo::
            cardinal_pythonlib.winservice.WindowsService: NOT YET IMPLEMENTED:
            Windows service autorestart