docs: 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")