Skip to content

Unable to send CTRL_BREAK_EVENT #77426

Closed
Closed

Description

@ofek
mannequin
BPO 33245
Nosy @pfmoore, @tjguk, @zware, @eryksun, @zooba, @ofek
Superseder
  • bpo-42962: Windows: SystemError during os.kill(..., signal.CTRL_C_EVENT)
  • 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-03-01.13:47:24.623>
    created_at = <Date 2018-04-09.03:47:30.298>
    labels = ['3.8', 'type-bug', '3.7', 'OS-windows']
    title = 'Unable to send CTRL_BREAK_EVENT'
    updated_at = <Date 2021-03-01.13:47:24.622>
    user = 'https://github.com/ofek'

    bugs.python.org fields:

    activity = <Date 2021-03-01.13:47:24.622>
    actor = 'eryksun'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-03-01.13:47:24.623>
    closer = 'eryksun'
    components = ['Windows']
    creation = <Date 2018-04-09.03:47:30.298>
    creator = 'Ofekmeister'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 33245
    keywords = []
    message_count = 2.0
    messages = ['315108', '315109']
    nosy_count = 6.0
    nosy_names = ['paul.moore', 'tim.golden', 'zach.ware', 'eryksun', 'steve.dower', 'Ofekmeister']
    pr_nums = []
    priority = 'normal'
    resolution = 'duplicate'
    stage = 'resolved'
    status = 'closed'
    superseder = '42962'
    type = 'behavior'
    url = 'https://bugs.python.org/issue33245'
    versions = ['Python 2.7', 'Python 3.7', 'Python 3.8']

    Activity

    1. ofek commented on Apr 9, 2018

      ofekmannequin
      MannequinAuthor

      Vault (https://github.com/hashicorp/vault) requires the use of signals to trigger certain output https://www.vaultproject.io/docs/internals/telemetry.html.

      The required signal isn't sent on py2.7:

      >>> import os
      >>> import signal
      >>> import psutil
      >>> p = psutil.Process([p.info for p in psutil.process_iter(attrs=['pid', 'name']) if 'vault' in p.info['name']][0]['pid'])
      >>> p.exe()
      'C:\\Users\\Ofek\\Desktop\\vault.exe'
      >>> p.pid
      15536
      >>> os.kill(p.pid, signal.CTRL_BREAK_EVENT)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      WindowsError: [Error 87] The parameter is incorrect

      Interestingly, on py3.6 that code works but instead produces this:

      OSError: [WinError 87] The parameter is incorrect

      The above exception was the direct cause of the following exception:
      
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      SystemError: <built-in function kill> returned a result with an error set
    2. eryksun commented on Apr 9, 2018

      @eryksun
      Contributor

      Unless you're willing to step through hoops using ctypes or PyWin32, sending Ctrl+Break requires already being attached to the same console as the target. Note that the target isn't necessarily a single process that's attached to the console. Sending a control event is implemented by calling WinAPI GenerateConsoleCtrlEvent, which targets process group identifiers (i.e. like Unix killpg), not process identifiers (i.e. not like Unix kill).

      FYI, to generate the event, the console host (conhost.exe) uses an LPC port to relay the request to the session Windows server (csrss.exe), which is privileged to inject a thread in each target process. This control thread starts executing at a known entry point named "CtrlRoutine" in kernelbase.dll. (Prior to Windows 7 the console is actually hosted in csrss.exe, so there's no need to relay the request, and "CtrlRoutine" is implemented in kernel32.dll instead.)

      If you don't know the group ID, then the only option is to broadcast Ctrl+Break to group 0, which includes every process that's attached to the console. First ignore SIGBREAK (the C signal for CTRL_BREAK_EVENT) via signal.signal(signal.SIGBREAK, signal.SIG_IGN). Otherwise the default handler will run, which exits the current process. Then send Ctrl+Break via os.kill(0, signal.CTRL_BREAK_EVENT).

      On the other hand, if you started the process as a new group, then its ID is also the group ID. For example, p = subprocess.Popen('vault.exe', creationflags=subprocess.CREATE_NEW_PROCESS_GROUP). In this case you can send Ctrl+Break via os.kill(p.pid, signal.CTRL_BREAK_EVENT). Windows will generate the event only in the subset of processes that are both in the target process group and currently attached to the console that originated the request.

      The current implementation of os.kill() is behaving as designed and documented in Python 2.7. There is no bug in this case. However, the docs should clarify that the target when sending a console control event is a process group, not a process. They should also refer to the subprocess docs to explain how to create a new group via the CREATE_NEW_PROCESS_GROUP creation flag. Also, the line about accepting process handles should be stricken. A process handle is an integer that's indistinguishable from a process/group identifier, so the statement makes no sense, and thankfully the code doesn't try to implement anything that crazy.

      In Python 3, someone decided that if GenerateConsoleCtrlEvent fails, os.kill() should also try TerminateProcess, to allow terminating the process explicitly with exit codes 0 (CTRL_C_EVENT) and 1 (CTRL_BREAK_EVENT). Of course, killing a process using a console control event is nothing at all like terminating it abruptly, so to me this looks quite strange; it's coding with a sledgehammer. Anyway, if TerminateProcess succeeds it should, but currently does not, clear the error from the failed GenerateConsoleCtrlEvent call. That's a bug.

      I suggest resolving the bug by partially backing out of the change. It should only call GenerateConsoleCtrlEvent for the enum values signal.CTRL_C_EVENT and signal.CTRL_BREAK_EVENT, which I think was suggested by Steve Dower at one point. TerminateProcess can be called otherwise for 0 and 1 values. In this case, os.kill() would no longer magically switch between fundamentally different functions in a single call. An exception should always be raised if GenerateConsoleCtrlEvent fails. Possibly in 3.8, os.killpg() could be added on Windows for sending console control events to process groups, while deprecating or discouraging using os.kill() for this.

    3. 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

      Labels

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions