Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signal handler callables take int, not Signals #5622

Merged
merged 1 commit into from
Jun 11, 2021

Conversation

9999years
Copy link
Contributor

Closes #5621.

In the Callable variant of signal._HANDLER, change Signals to int and FrameType to Optional[FrameType].

The documentation for [`signal.signal`][1] points out that the current
annotation for signal handlers might be wrong (emphasis my own):

> The handler is called with two arguments: the signal **number** and the
> current stack frame (**`None` or** a frame object; for a description of frame
> objects, see the description in the type hierarchy or see the attribute
> descriptions in the `inspect` module).

And when we use them, we can see that the signal number is passed as an `int`,
not a `signal.Signals` member:

import signal

    def handler(signal_number, frame):
        print("In signal handler!")
        print("Signal number:", signal_number, type(signal_number))
        print("Stack frame:  ", frame, type(frame))

    # Set signal handler:
    signal.signal(signal.SIGHUP, handler)

    # Use it:
    signal.raise_signal(signal.SIGHUP)

Which prints:

    In signal handler!
    Signal number: 1 <class 'int'>
    Stack frame:   <frame at 0x7f804402abe0, file '<stdin>', line 12, code <module>> <class 'frame'>

[1]: https://docs.python.org/3/library/signal.html#signal.signal
@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pip (https://github.com/pypa/pip.git)
+ src/pip/_internal/cli/progress_bars.py:86: error: Incompatible types in assignment (expression has type "Callable[[int, FrameType], None]", variable has type "Union[Callable[[int, Optional[FrameType]], Any], int, Handlers, None]")

@Akuli
Copy link
Collaborator

Akuli commented Jun 11, 2021

I can't figure out when the frame would actually be None. I tried catching a few different signals (SIGHUP, SIGCONT, SIGINT). The docs don't mention when it happens, besides mentioning that it can happen. Can we just use FrameType?

@9999years
Copy link
Contributor Author

The CPython signalmodule.c is here. If you'd like to read it to prove that a signal handler never receives a None frame-argument, go ahead, but I personally am not familiar enough with CPython to do so, which is why I decided to trust the documentation.

On a higher level, isn't the whole point of a type system to describe everything that can happen so that programmers can be proactively defensive against all possible states? I'm very wary of more convenient but less accurate type annotations, especially in code as low-level as this.

@JelleZijlstra
Copy link
Member

I looked a bit and it happens if there is no frame in the current PyThreadState. I'm not sure when that would happen, perhaps during very early setup.

@Akuli
Copy link
Collaborator

Akuli commented Jun 11, 2021

The docs for inspect.currentframe say that on non-CPython, frames might not exist at all, so maybe FrameType would be fine on CPython. (Typeshed tests are very much CPython-focused anyway.) But if someone really needs a frame, it's not too bad to assert frame is not None.

@@ -80,7 +80,7 @@ if sys.platform != "win32":
SIG_SETMASK = Sigmasks.SIG_SETMASK

_SIGNUM = Union[int, Signals]
_HANDLER = Union[Callable[[Signals, FrameType], Any], int, Handlers, None]
_HANDLER = Union[Callable[[int, Optional[FrameType]], Any], int, Handlers, None]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit for future reference: we now prefer the new Foo | None syntax instead of Optional[Foo].

@Akuli Akuli merged commit 72f4057 into python:master Jun 11, 2021
jenshnielsen added a commit to jenshnielsen/typeshed that referenced this pull request Dec 16, 2021
Following the changes in python#5622
handlers are typed as taking an Optional frame but the
default_int_handler does not take None. This seems inconsistent
and pervents you from calling the default handler from a custom handler
without checking that the frame is not None first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect type annotations for signal handlers
3 participants