Skip to content

Deprecate os.kill() on Windows #68136

Closed
Closed

Description

@jpe
mannequin
BPO 23948
Nosy @pfmoore, @giampaolo, @tjguk, @zware, @eryksun, @zooba

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = <Date 2021-12-11.21:00:22.674>
created_at = <Date 2015-04-14.15:24:21.308>
labels = ['type-bug', '3.8', '3.9', '3.10', 'extension-modules', 'OS-windows']
title = 'Deprecate os.kill() on Windows'
updated_at = <Date 2021-12-11.21:00:22.673>
user = 'https://bugs.python.org/jpe'

bugs.python.org fields:

activity = <Date 2021-12-11.21:00:22.673>
actor = 'eryksun'
assignee = 'none'
closed = True
closed_date = <Date 2021-12-11.21:00:22.674>
closer = 'eryksun'
components = ['Extension Modules', 'Windows']
creation = <Date 2015-04-14.15:24:21.308>
creator = 'jpe'
dependencies = []
files = []
hgrepos = []
issue_num = 23948
keywords = []
message_count = 10.0
messages = ['240905', '241067', '241118', '241134', '241135', '241156', '241158', '260183', '389143', '389166']
nosy_count = 7.0
nosy_names = ['jpe', 'paul.moore', 'giampaolo.rodola', 'tim.golden', 'zach.ware', 'eryksun', 'steve.dower']
pr_nums = []
priority = 'normal'
resolution = 'rejected'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue23948'
versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']

Activity

  1. jpe commented on Apr 14, 2015

    jpemannequin
    MannequinAuthor

    os.kill() on Windows cannot act like it does on non-windows platforms because of differences in the underlying platforms. I think only kill() with a signal number of 9 (terminate process unconditionally) and a signal number of 0 (test to see if process exists) could be implemented. It isn't possibly to send the INT signal or a HUP signal to another process -- or at least it isn't possible without the use of questionable api's and code.

    Windows specific functions would be added for terminateprocess() and generateconsolectrlevent(). os.kill() on non-windows platforms would be left alone.

  2. zooba commented on Apr 15, 2015

    @zooba
    Member

    This feels like an unnecessary incompatibility between the platforms. I'd rather change the parameter values for CTRL+C events so we can distinguish when someone calls with that and then fix it internally on Windows.

  3. jpe commented on Apr 15, 2015

    jpemannequin
    MannequinAuthor

    Part of the issue here is that GenerateConsoleCtrlEvent doesn't work like kill w/ SIGINT does on unix -- it only works if the the target process id is 0 and then it generates the signal in all processes that share the console. An alternate proposal here is to expose the GenerateConsoleCtrlEvent functionality separately and only accept a signal number of 9 and maybe 0 in kill().

  4. eryksun commented on Apr 15, 2015

    @eryksun
    Contributor

    it only works if the the target process id is 0 and then it
    generates the signal in all processes that share the console.

    You can target a process group, which is a subset of the console's attached processes. That's why the console host, conhost.exe, routes dispatching the event through the session CSRSS.EXE, which handles bookkeeping of Windows processes and threads, including groups.

    CSRSS creates a thread in each target, which starts at kernel32!CtrlRoutine. If a process is being debugged this routine raises a DBG_CONTROL_C or DBG_CONTROL_BREAK exception. Next it calls the registered handlers. The default handler, if called, exits the process with the exit code set to STATUS_CONTROL_C_EXIT (0xC000013A):

    0:000> u kernel32!DefaultHandler l3
    kernel32!DefaultHandler:
    00000000`76f31290 4883ec28        sub     rsp,28h
    00000000`76f31294 b93a0100c0      mov     ecx,0C000013Ah
    00000000`76f31299 ff15e1bb0500    call    qword ptr
                                              [kernel32!
                                               _imp_RtlExitUserProcess 
                                               (00000000`76f8ce80)]
    

    Example:

        import os
        import sys
        import signal
        import subprocess
        import threading
    
        STATUS_CONTROL_C_EXIT = 0xC000013A
    
        p = subprocess.Popen([sys.executable, '-i'],
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
        p.stderr.read(1) # poor man's WaitForInputIdle
    
        os.kill(p.pid, signal.CTRL_BREAK_EVENT)
        watchdog = threading.Timer(5, p.terminate)
        watchdog.start()
        exitcode = p.wait()
        watchdog.cancel()
    
        if exitcode < 0:
            exitcode += 2 ** 32
        assert exitcode == STATUS_CONTROL_C_EXIT
  5. jpe commented on Apr 15, 2015

    jpemannequin
    MannequinAuthor

    GenerateConsoleCtrlEvent has different limitations for CTRL_BREAK_EVENT and CTRL_C_EVENT according to MSDN; I was referring to the CTRL_C_EVENT limitations. Which python level signal handler will CTRL_BREAK_EVENT trigger?

  6. eryksun commented on Apr 15, 2015

    @eryksun
    Contributor

    Which python level signal handler will CTRL_BREAK_EVENT
    trigger?

    The CRT maps it to a non-standard signal, SIGBREAK (21). It's defined in the signal module.

    GenerateConsoleCtrlEvent has different limitations for
    CTRL_BREAK_EVENT and CTRL_C_EVENT according to MSDN;
    I was referring to the CTRL_C_EVENT limitations.

    When creating a process group, the ConsoleFlags value in the process parameters is set to ignore CTRL_C_EVENT. You'll still get a DBG_CONTROL_C exception if a debugger is attached, but otherwise kernel32!CtrlRoutine ignores Ctrl+C. As you say, the docs state that "[CTRL_C_EVENT] cannot be generated for process groups". But it *does* work if you remove the ignore flag. This flag gets set/unset by assigning/removing a NULL handler.

    Here's a revised example to enable Ctrl+C in the child, disable Python's INT handler, and then generate CTRL_C_EVENT to kill the child and verify that the exit code is STATUS_CONTROL_C_EXIT.

        import os
        import sys
        import signal
        import subprocess
        import threading
    
        STATUS_CONTROL_C_EXIT = 0xC000013A
    
        p = subprocess.Popen([sys.executable, '-i', '-c',
                              'import ctypes, signal;'
                              'kernel32 = ctypes.windll.kernel32;'
                              'kernel32.SetConsoleCtrlHandler(None, 0);'
                              'signal.signal(signal.SIGINT, signal.SIG_DFL)'],
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
        p.stderr.read(1) # poor man's WaitForInputIdle
    
        os.kill(p.pid, signal.CTRL_C_EVENT)
        watchdog = threading.Timer(5, p.terminate)
        watchdog.start()
        exitcode = p.wait()
        watchdog.cancel()
    
        if exitcode < 0:
            exitcode += 2 ** 32
        assert exitcode == STATUS_CONTROL_C_EXIT
  7. jpe commented on Apr 15, 2015

    jpemannequin
    MannequinAuthor

    Interesting -- I didn't know about removing the ignore flag in the child process. The question is whether this is close enough to the kill w/ SIGINT behavior on unix to use the same name. I think that there are enough differences to warrant a Windows specific function that calls GenerateConsoleCtrlEvent.

  8. giampaolo commented on Feb 12, 2016

    @giampaolo
    Contributor

    See also bpo-26350.

  9. eryksun commented on Mar 20, 2021

    @eryksun
    Contributor

    I'd prefer to change os.kill() to take the code path that generates a console control event only when the pid value is negative (i.e. a process group ID), with -1 reserved to send the event to all processes in the console session (i.e. console process group 0). Also, in this case, I'd map SIGINT and SIGBREAK to CTRL_C_EVENT and CTRL_BREAK_EVENT, instead of directly using the Windows API constants. Such a design conforms better with POSIX kill() [1], and it cleanly separates the GenerateConsoleCtrlEvent() and TerminateProcess() usage.

    The older usage with kill(non_negative_pid, console_control_event) could be retained by checking for the enum instances signal.CTRL_C_EVENT and signal.CTRL_BREAK_EVENT by object ID instead of by value.

    ---

    [1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html

  10. jpe commented on Mar 20, 2021

    jpemannequin
    MannequinAuthor

    The original idea was to not use kill on win32 because developers often assume it will work like it does on unix-like OS's -- my claim is while kill could be improved on win32, it still won't support all the things kill can do elsewhere.

    I don't think this idea has much, if any support so I suggest closing this bug and opening other specific bugs to improve it on win32 for the ideas mentioned in the comments.

  11. transferred this issue fromon Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions