Sitelet https://web.archive.org/web/20250519072212/https://github.com/python/cpython/issues/88378
Skip to content

asyncio overrides signal handlers #88378

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

Open
franciscod mannequin opened this issue May 22, 2021 · 14 comments
Open

asyncio overrides signal handlers #88378

franciscod mannequin opened this issue May 22, 2021 · 14 comments
Labels
stdlib Python modules in the Lib dir topic-asyncio type-feature A feature request or enhancement

Comments

@franciscod
Copy link
Mannequin

franciscod mannequin commented May 22, 2021

BPO 44212
Nosy @asvetlov, @1st1, @franciscod
PRs
  • bpo-44212: asyncio: store old signal handlers and call them #26306
  • 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 = None
    created_at = <Date 2021-05-22.03:09:03.053>
    labels = ['type-bug', '3.8', '3.9', '3.10', '3.11', '3.7', 'expert-asyncio']
    title = 'asyncio overrides signal handlers'
    updated_at = <Date 2021-05-22.04:05:46.824>
    user = 'https://github.com/franciscod'

    bugs.python.org fields:

    activity = <Date 2021-05-22.04:05:46.824>
    actor = 'Francisco Demartino'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['asyncio']
    creation = <Date 2021-05-22.03:09:03.053>
    creator = 'Francisco Demartino'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 44212
    keywords = ['patch']
    message_count = 2.0
    messages = ['394174', '394176']
    nosy_count = 3.0
    nosy_names = ['asvetlov', 'yselivanov', 'Francisco Demartino']
    pr_nums = ['26306']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue44212'
    versions = ['Python 3.6', 'Python 3.7', 'Python 3.8', 'Python 3.9', 'Python 3.10', 'Python 3.11']

    @franciscod
    Copy link
    Mannequin Author

    franciscod mannequin commented May 22, 2021

    Hello,

    It looks like when asyncio sets up a signal handler, it forgets about the previous one (if any).

    Here's a patch (was about to create a PR but the default text brought me to bugs.python.org)
    franciscod@bdac885

    Tracked this down to the initial asyncio checkout, in commit 27b7c7e, a few commits before v3.4.0a4.

    Not sure if this is a bug but it surprised me. I would have expected that it registered an "additional" signal handler, or at least that my previously installed signal handler would be called afterwards.

    Also I'm not sure that it's a good idea to suddenly start running handlers that some current code might rely on not running, or have worked around this behaviour otherwise.

    Thanks,
    Francisco

    @franciscod franciscod mannequin added 3.7 (EOL) end of life 3.8 (EOL) end of life 3.9 only security fixes 3.10 only security fixes 3.11 only security fixes topic-asyncio type-bug An unexpected behavior, bug, or error labels May 22, 2021
    @franciscod
    Copy link
    Mannequin Author

    franciscod mannequin commented May 22, 2021

    Looks like Roger Dahl also noted this on https://bugs.python.org/issue39765:

    Second, set_signal_handler()[sic] silently and implicitly removes corresponding handlers set with signal.signal(). [...] I think this should be documented as well.

    (then has a linked PR that apparently doesn't address this second part)

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @ezio-melotti ezio-melotti moved this to Todo in asyncio Jul 17, 2022
    @gvanrossum
    Copy link
    Member

    FWIW, last week @1st1 and I discussed a new signal handling API for asyncio, where you would use register_signal_handler(sig, callback) which would return an asyncio.Handle and would allow any number of handlers to be set for the same signal. To unregister, just cancel the handle.

    However that still doesn't address the fact that asyncio just clobbers an existing handler set by the signal module. If we were to add a similar API to that module (using some other kind of handle-like object) we could switch asyncio to use that.

    FWIW you can go down this rabbit hole ever deeper. E.g. I also found #76624, suggesting to add Linux's signalfd.

    @kumaraditya303 kumaraditya303 removed 3.11 only security fixes 3.10 only security fixes 3.9 only security fixes 3.8 (EOL) end of life 3.7 (EOL) end of life labels Oct 18, 2022
    @kumaraditya303
    Copy link
    Contributor

    @gvanrossum Want to work on this? I am not sure that we need another set of APIs for this though. I can fix this without introducing new APIs and that could also be backported.

    @gvanrossum
    Copy link
    Member

    I don't have time to work on it. I agree that more APIs is not always better, but I'm not sure how you'd change this behavior and call it backwards compatible, let alone backportable.

    What semantics are you proposing for loop.add_signal_handler() and loop.remove_signal_handler()?

    @kumaraditya303
    Copy link
    Contributor

    What semantics are you proposing for loop.add_signal_handler() and loop.remove_signal_handler()?

    The only part which would be backported is that signal handlers registered with signal.signal will be respected and will be called. We can support multiple signal handlers for 3.12+ if wanted and this part will not be backported.

    @gvanrossum
    Copy link
    Member

    gvanrossum commented Dec 3, 2022 •

    I'm still not sure about the API you proposed. Suppose I have

    signal.signal(SIGUSR1, some_handler)
    loop.add_signal_handle(SIGUSR1, another_handler)
    

    Are you saying that both some_handler and another_handler will be called when SIGUSR1 arrives? That sure sounds backwards incompatible, since the current behavior only calls the latter IIRC.

    @kumaraditya303
    Copy link
    Contributor

    Are you saying that both some_handler and another_handler will be called when SIGUSR1 arrives? That sure sounds backwards incompatible, since the current behavior only calls the latter IIRC.

    Yes, both handlers would be called. asyncio should not override and remove the handler registered via signal.signal.

    That sure sounds backwards incompatible, since the current behavior only calls the latter IIRC.

    That's arguably the bug to be fixed here. We can ofcourse consider this to be a new feature and chose to not backport this.

    P.S In the second handler it should be SIGUSR1 not SIGUSR2.

    @gvanrossum
    Copy link
    Member

    Are you saying that both some_handler and another_handler will be called when SIGUSR1 arrives? That sure sounds backwards incompatible, since the current behavior only calls the latter IIRC.

    Yes, both handlers would be called. asyncio should not override and remove the handler registered via signal.signal.

    It's been like this since asyncio first got signal handling (3.4?), and surely this behavior has been accepted by user code as "that's how it works" (even if it was never documented).

    That sure sounds backwards incompatible, since the current behavior only calls the latter IIRC.

    That's arguably the bug to be fixed here. We can ofcourse consider this to be a new feature and chose to not backport this.

    I don't think we can change this even in 3.12, it would break code that sets a handler using signal.signal() and then expects to replace that using loop.add_signal_handler().

    That's why Yury and I were debating a new API.

    P.S In the second handler it should be SIGUSR1 not SIGUSR2.

    Thanks, fixed.

    @kumaraditya303
    Copy link
    Contributor

    Yeah, adding new APIs looks like the best way forward here.

    @kumaraditya303 kumaraditya303 added type-feature A feature request or enhancement and removed type-bug An unexpected behavior, bug, or error labels Dec 4, 2022
    @willingc
    Copy link
    Contributor

    Next action: Let's discuss new APIs at the core dev sprint.

    @lokmeinmatz
    Copy link

    Hi, any updates on this? :) It feels a but cumbersome to distribute this shutdown event manually to multiple places

    @gvanrossum
    Copy link
    Member

    I'm sorry, I don't think I will ever personally produce a PR for this. So if you have the skills and some courage, I recommend you try your hand at fixing it yourself! I might be able to help you a bit, just ask here.

    @picnixz picnixz added the stdlib Python modules in the Lib dir label Mar 5, 2025
    @deepwzh
    Copy link

    deepwzh commented Mar 18, 2025

    Hi @gvanrossum @kumaraditya303,

    FWIW, last week @1st1 and I discussed a new signal handling API for asyncio, where you would use register_signal_handler(sig, callback) which would return an asyncio.Handle and would allow any number of handlers to be set for the same signal. To unregister, just cancel the handle.

    However that still doesn't address the fact that asyncio just clobbers an existing handler set by the signal module. If we were to add a similar API to that module (using some other kind of handle-like object) we could switch asyncio to use that.

    FWIW you can go down this rabbit hole ever deeper. E.g. I also found #76624, suggesting to add Linux's signalfd.

    Thank you for the detailed context! I’m eager to contribute to resolving the signal handler override issue. Let me summarize my understanding to ensure alignment:

    • Key Goals for the Fix: Prevent asyncio from overriding user-registered signal handlers (via signal.signal()).
    • Current Problem: asyncio’s internal use of signal.signal() clobbers existing handlers.
    • Solution: Introduce a ​new api where both asyncio and user code register handlers without overriding each other.
    • Support Multiple Handlers per Signal: New API (e.g., register_signal_handler) should allow any number of handlers for the same signal, executed in a defined order, and it can be canceled
      

    If allowed, are there any more suggestions on design? and how should I start? For example, by directly submitting a PR or providing a further design specification.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    stdlib Python modules in the Lib dir topic-asyncio type-feature A feature request or enhancement
    Projects
    Status: Todo
    Development

    No branches or pull requests

    6 participants