# Buggy code
def kill_proc_tree(
    pid, sig=signal.SIGTERM, include_parent=True, timeout=None, on_terminate=None
):
    """
    Kill a process tree (including grandchildren) with sig and return a
    (gone, still_alive) tuple.

    "on_terminate", if specified, is a callabck function which is called
    as soon as a child terminates.

    This is an new method not present in QtKernelManager.
    """
    assert pid != os.getpid()  # Won't kill myself!

    # This is necessary to avoid showing an error when restarting the
    # kernel after it failed to start in the first place.
    # Fixes spyder-ide/spyder#11872
    try:
        parent = psutil.Process(pid)
    except psutil.NoSuchProcess:
        return ([], [])

    children = parent.children(recursive=True)

    if include_parent:
        children.append(parent)

    for child_process in children:
        child_process.send_signal(sig)

    gone, alive = psutil.wait_procs(
        children,
        timeout=timeout,
        callback=on_terminate,
    )

    return (gone, alive)


# Traceback (PermissionError)
File "C:\ProgramData\Anaconda3\lib\site-packages\psutil\_pswindows.py", line 679, in wrapper
return fun(self, *args, **kwargs)
File "C:\ProgramData\Anaconda3\lib\site-packages\psutil\_pswindows.py", line 868, in send_signal
cext.proc_kill(self.pid)
PermissionError: [WinError 5] Zugriff verweigert

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\site-packages\jupyter_client\manager.py", line 394, in shutdown_kernel
self.finish_shutdown()
File "C:\ProgramData\Anaconda3\lib\site-packages\jupyter_client\manager.py", line 346, in finish_shutdown
self._kill_kernel()
File "C:\ProgramData\Anaconda3\lib\site-packages\spyder\plugins\ipythonconsole\utils\manager.py", line 86, in _kill_kernel
self.kill_proc_tree(self.kernel.pid)
File "C:\ProgramData\Anaconda3\lib\site-packages\spyder\plugins\ipythonconsole\utils\manager.py", line 59, in kill_proc_tree
child_process.send_signal(sig)
File "C:\ProgramData\Anaconda3\lib\site-packages\psutil\__init__.py", line 289, in wrapper
return fun(self, *args, **kwargs)
File "C:\ProgramData\Anaconda3\lib\site-packages\psutil\__init__.py", line 1218, in send_signal
self._proc.send_signal(sig)
File "C:\ProgramData\Anaconda3\lib\site-packages\psutil\_pswindows.py", line 681, in wrapper
raise convert_oserror(err, pid=self.pid, name=self._name)
psutil.AccessDenied: psutil.AccessDenied (pid=29604)

# Fixed code
def kill_proc_tree(
    pid, sig=signal.SIGTERM, include_parent=True, timeout=None, on_terminate=None
):
    """
    Kill a process tree (including grandchildren) with sig and return a
    (gone, still_alive) tuple.

    "on_terminate", if specified, is a callabck function which is called
    as soon as a child terminates.

    This is an new method not present in QtKernelManager.
    """
    assert pid != os.getpid()  # Won't kill myself!

    # This is necessary to avoid showing an error when restarting the
    # kernel after it failed to start in the first place.
    # Fixes spyder-ide/spyder#11872
    try:
        parent = psutil.Process(pid)
    except psutil.NoSuchProcess:
        return ([], [])

    children = parent.children(recursive=True)

    if include_parent:
        children.append(parent)

    for child_process in children:
        # This is necessary to avoid an error when restarting the
        # kernel that started a PyQt5 application in the background.
        # Fixes spyder-ide/spyder#13999
        try:
            child_process.send_signal(sig)
        except psutil.AccessDenied:
            return ([], [])

    gone, alive = psutil.wait_procs(
        children,
        timeout=timeout,
        callback=on_terminate,
    )

    return (gone, alive)