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

Interruption breaks after matplotlib plt.pause() #39601

Open
user202729 opened this issue Feb 27, 2025 · 0 comments · May be fixed by sagemath/cysignals#228
Open

Interruption breaks after matplotlib plt.pause() #39601

user202729 opened this issue Feb 27, 2025 · 0 comments · May be fixed by sagemath/cysignals#228

Comments

@user202729
Copy link
Contributor

user202729 commented Feb 27, 2025

After plt.pause() is called, ctrl-C stops working (outside plt.pause() itself).

To reproduce:

%matplotlib
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"]=(10, 7)
plt.rcParams["figure.dpi"]=144
plt.rcParams["figure.facecolor"]="white"
plt.ion()

plt.plot([1, 2, 3],[5, 4, 6])

while ensuring the plot is displayed, do the following

plt.pause(3)

wait for it to finish, then

sleep(10)

try to ctrl-C it, it won't work.


The reason is the following. Cysignals contains

    # Set the Python-level interrupt handler. When a SIGINT occurs,
    # this will not be called directly. Instead, a SIGINT is caught by
    # our interrupt handler, set up in implementation.c. If it happens
    # during pure Python code (not within sig_on()/sig_off()), the
    # handler will set Python's interrupt flag. Python regularly checks
    # this and will call its interrupt handler (which is the one we set
    # now). This handler issues a sig_check() which finally raises the
    # KeyboardInterrupt exception.
    import signal
    old = signal.signal(signal.SIGINT, python_check_interrupt)

    setup_alt_stack()
    setup_cysignals_handlers()

In other words, python_check_interrupt is set to be the Python-level signal handler, however then the actual signal handler is something set in C

    /* Install signal handlers */
    /* Handlers for interrupt-like signals */
    sa.sa_handler = cysigs_interrupt_handler;
    sa.sa_flags = 0;
#ifdef SIGHUP
    if (sigaction(SIGHUP, &sa, NULL)) {perror("cysignals sigaction"); exit(1);}
#endif
    if (sigaction(SIGINT, &sa, NULL)) {perror("cysignals sigaction"); exit(1);}
#ifdef SIGALRM
    if (sigaction(SIGALRM, &sa, NULL)) {perror("cysignals sigaction"); exit(1);}
#endif

that causes a problem, because matplotlib temporarily sets the signal handler to its own then revert it later. Equivalently the following also breaks ctrl-C even though it should be a no-op:

import signal
from signal import SIGINT

signal.signal(SIGINT, signal.getsignal(SIGINT))

the function returned is Python function python_check_interrupt.

After signal.signal(SIGINT, signal.getsignal(SIGINT)) (which is supposed to be a no-op), now on ctrl-C, only python_check_interrupt is called without actually setting the value of interrupt_received:

/* Handler for SIGHUP, SIGINT, SIGALRM, SIGTERM
 *
 * Inside sig_on() (i.e. when cysigs.sig_on_count is positive), this
 * raises an exception and jumps back to sig_on().
 * Outside of sig_on(), we set Python's interrupt flag using
 * PyErr_SetInterrupt() */
static void cysigs_interrupt_handler(int sig)
{

[…]

        cysigs.interrupt_received = sig;
        custom_set_pending_signal(sig);

Relevant explanation:

    # Set the Python-level interrupt handler. When a SIGINT occurs,
    # this will not be called directly. Instead, a SIGINT is caught by
    # our interrupt handler, set up in implementation.c. If it happens
    # during pure Python code (not within sig_on()/sig_off()), the
    # handler will set Python's interrupt flag. Python regularly checks
    # this and will call its interrupt handler (which is the one we set
    # now). This handler issues a sig_check() which finally raises the
    # KeyboardInterrupt exception.
    import signal
    old = signal.signal(signal.SIGINT, python_check_interrupt)

tl;dr the Python-level interrupt handler actually need to set the signal, as the fallback. Probably.


Some investigation: there is this thing

def init_cysignals():
    """
    Initialize ``cysignals``.

    This is normally done exactly once, namely when importing
    ``cysignals``. However, it is legal to call this multiple times,
    for example when switching between the ``cysignals`` interrupt
    handler and a different interrupt handler.

    OUTPUT: the old Python-level interrupt handler

I think a workaround for now is just to call that function again.

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 a pull request may close this issue.

1 participant