def unmanage(self, ifnames): """ Mark network interfaces in 'ifnames' as 'unmanaged'. The managed state can later be restored with the 'restore_managed()'. """ if not Trivial.is_iterable(ifnames): ifnames = [ifnames] for ifname in ifnames: managed = self.is_managed(ifname) if not managed: continue self._toggle_managed(ifname, False) if ifname not in self._saved_managed: self._saved_managed[ifname] = managed
def kill_pids(pids, sig: str = "SIGTERM", kill_children: bool = False, must_die: bool = False, proc=None): """ This function kills or signals processes with PIDs in 'pids' on the host defined by 'procs'. The 'pids' argument can be a collection of PID numbers ('int' or 'str' types) or a single PID number. By default the processes are killed (SIGTERM), but you can specify any signal either by name or by number. The 'children' and 'must_die' arguments must only be used when killing processes (SIGTERM or SIGKILL). The 'children' argument controls whether this function should also try killing the children. If the 'must_die' argument is 'True', then this function also verifies that the process(es) did actually die, and if any of them did not die, it raises an exception. By default this function operates on the local host, but the 'proc' argument can be used to pass a connected 'SSH' object in which case this function will operate on the remote host. """ def collect_zombies(proc): """In case of a local process we need to 'waitpid()' the children.""" if not proc.is_remote: with contextlib.suppress(OSError): os.waitpid(0, os.WNOHANG) if not proc: proc = Procs.Proc() if not pids: return if not Trivial.is_iterable(pids): pids = (pids, ) pids = [str(int(pid)) for pid in pids] if sig is None: sig = "SIGTERM" else: sig = str(sig) killing = _is_sigterm(sig) or _is_sigkill(sig) if (kill_children or must_die) and not killing: raise Error(f"'children' and 'must_die' arguments cannot be used with '{sig}' signal") if kill_children: # Find all the children of the process. for pid in pids: children, _, exitcode = proc.run(f"pgrep -P {pid}", join=False) if exitcode != 0: break pids += [child.strip() for child in children] pids_spc = " ".join(pids) pids_comma = ",".join(pids) _LOG.debug("sending '%s' signal to the following process%s: %s", sig, proc.hostmsg, pids_comma) try: proc.run_verify(f"kill -{sig} -- {pids_spc}") except Error as err: if not killing: raise Error(f"failed to send signal '{sig}' to PIDs '{pids_comma}'{proc.hostmsg}:\n" f"{err}") # Some error happened on the first attempt. We've seen a couple of situations when this # happens. # 1. Most often, a PID does not exist anymore, the process exited already (race condition). # 2 One of the processes in the list is owned by a different user (e.g., root). Let's call # it process A. We have no permissions to kill process A, but we can kill other processes # in the 'pids' list. But often killing other processes in the 'pids' list will make # process A exit. This is why we do not error out just yet. # # So the strategy is to do the second signal sending round and often times it happens # without errors, and all the processes that we want to kill just go away. if not killing: return # Give the processes up to 4 seconds to die. timeout = 4 start_time = time.time() while time.time() - start_time <= timeout: collect_zombies(proc) _, _, exitcode = proc.run(f"kill -0 -- {pids_spc}") if exitcode != 0: return time.sleep(0.2) if _is_sigterm(sig): # Something refused to die, try SIGKILL. try: proc.run_verify(f"kill -9 -- {pids_spc}") except Error as err: # It is fine if one of the processes exited meanwhile. if "No such process" not in str(err): raise collect_zombies(proc) if not must_die: return # Give the processes up to 4 seconds to die. timeout = 4 start_time = time.time() while time.time() - start_time <= timeout: collect_zombies(proc) _, _, exitcode = proc.run(f"kill -0 -- {pids_spc}") if exitcode != 0: return time.sleep(0.2) # Something refused to die, find out what. msg, _, = proc.run_verify(f"ps -f {pids_spc}", join=False) if len(msg) < 2: msg = pids_comma raise Error(f"one of the following processes{proc.hostmsg} did not die after 'SIGKILL': {msg}")