A context manager that allows terminating a plot by sending a SIGINT. It is necessary because the running backend prevents the Python interpreter from running and processing signals (i.e., to raise a KeyboardInterrupt). To solve this, one needs to somehow wake up the interpreter an
(prepare_notifier, handle_sigint)
| 1648 | |
| 1649 | @contextmanager |
| 1650 | def _allow_interrupt(prepare_notifier, handle_sigint): |
| 1651 | """ |
| 1652 | A context manager that allows terminating a plot by sending a SIGINT. It |
| 1653 | is necessary because the running backend prevents the Python interpreter |
| 1654 | from running and processing signals (i.e., to raise a KeyboardInterrupt). |
| 1655 | To solve this, one needs to somehow wake up the interpreter and make it |
| 1656 | close the plot window. We do this by using the signal.set_wakeup_fd() |
| 1657 | function which organizes a write of the signal number into a socketpair. |
| 1658 | A backend-specific function, *prepare_notifier*, arranges to listen to |
| 1659 | the pair's read socket while the event loop is running. (If it returns a |
| 1660 | notifier object, that object is kept alive while the context manager runs.) |
| 1661 | |
| 1662 | If SIGINT was indeed caught, after exiting the on_signal() function the |
| 1663 | interpreter reacts to the signal according to the handler function which |
| 1664 | had been set up by a signal.signal() call; here, we arrange to call the |
| 1665 | backend-specific *handle_sigint* function, passing the notifier object |
| 1666 | as returned by prepare_notifier(). Finally, we call the old SIGINT |
| 1667 | handler with the same arguments that were given to our custom handler. |
| 1668 | |
| 1669 | We do this only if the old handler for SIGINT was not None, which means |
| 1670 | that a non-python handler was installed, i.e. in Julia, and not SIG_IGN |
| 1671 | which means we should ignore the interrupts. |
| 1672 | |
| 1673 | Parameters |
| 1674 | ---------- |
| 1675 | prepare_notifier : Callable[[socket.socket], object] |
| 1676 | handle_sigint : Callable[[object], object] |
| 1677 | """ |
| 1678 | |
| 1679 | old_sigint_handler = signal.getsignal(signal.SIGINT) |
| 1680 | if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL): |
| 1681 | yield |
| 1682 | return |
| 1683 | |
| 1684 | handler_args = None |
| 1685 | wsock, rsock = socket.socketpair() |
| 1686 | wsock.setblocking(False) |
| 1687 | rsock.setblocking(False) |
| 1688 | old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno()) |
| 1689 | notifier = prepare_notifier(rsock) |
| 1690 | |
| 1691 | def save_args_and_handle_sigint(*args): |
| 1692 | nonlocal handler_args, notifier |
| 1693 | handler_args = args |
| 1694 | handle_sigint(notifier) |
| 1695 | notifier = None |
| 1696 | |
| 1697 | signal.signal(signal.SIGINT, save_args_and_handle_sigint) |
| 1698 | try: |
| 1699 | yield |
| 1700 | finally: |
| 1701 | wsock.close() |
| 1702 | rsock.close() |
| 1703 | signal.set_wakeup_fd(old_wakeup_fd) |
| 1704 | signal.signal(signal.SIGINT, old_sigint_handler) |
| 1705 | if handler_args is not None: |
| 1706 | old_sigint_handler(*handler_args) |
| 1707 |
no test coverage detected
searching dependent graphs…