2

I have a large class of data acquisition methods and settings, which we usually work with from an ipython terminal. The class also defines a signal handler method, cleanup, which is supposed to gather any pending incoming data streams and close files cleanly after a user interrupts the data acquisition with a KeyboardInterrupt (or some other unforeseen interruption occurs). I assigned this signal handler method to the corresponding signal.SIG* signals via signal.signal(signal.SIG*, self.cleanup) as part of the class' __init__ method. This has worked flawlessly for the last ca. 5 years, but seems to be broken since a fresh install of the conda environments a few months ago. I have meanwhile reproduced this on a number or conda envs where things have been working well, and after an update, this stopped working.

Here is a simplified code example (defined in interrupt_signal_handler_test.py):

import signal
import time


class InterruptTest(object):

    def __init__(self):
        print("initializing...")

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

        # assign the cleanup method to the SIGINT signal handler
        signal.signal(signal.SIGINT, self.cleanup)

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

    def cleanup(self, sigNum, frame):
        print("cleanup was called")
        print(sigNum)
        print(frame)
        raise InterruptedError("Code was interrupted by the user")

    def run(self):
        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))
        print("Press Ctrl+C to interrupt the counting.")
        i = 1
        while(True):
            time.sleep(1.0)
            print(i)
            i += 1



Firstly, when running the script directly through python, it works as intended in both environments:

$ python interrupt_signal_handler_test.py 
<built-in function default_int_handler>
initializing...
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x1083aac10, file 'interrupt_signal_handler_test.py', line 25, code run>
Traceback (most recent call last):
  File "interrupt_signal_handler_test.py", line 32, in <module>
    t.run()
  File "interrupt_signal_handler_test.py", line 25, in run
    time.sleep(1.0)
  File "interrupt_signal_handler_test.py", line 18, in cleanup
    raise InterruptedError("Code was interrupted by the user")
InterruptedError: Code was interrupted by the user

Now, when running (run -i interrupt_signal_handler_test.py) this same code manually in ipython on the "old" environment, I get the following (desired) behavior:

$ print(signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x10e9c0640, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
4
^Ccleanup was called
2
<frame at 0x7f8c6d83a000, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

The cleanup method is called every time I run() my experiment. However, on the updated python installation, I now get the following:

$ print (signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1029a04c0>>
# So far so good...

$ t.run()
<built-in function default_int_handler>
Press Ctrl+C to interrupt the counting.
1
2
^C---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 t.run()

Input In [1], in InterruptTest.run(self)
     29 i = 1
     30 while(True):
---> 31     time.sleep(1.0)
     32     print(i)
     33     i += 1

KeyboardInterrupt: 

# Not good: The signal handler was somehow reset to the built-in one

It is evident that the behavior changed - whether by design or not, I was not able to figure out. Is this a known issue? Was this changed deliberately? And if yes, how would one write custom signal handlers as class methods now to be compatible with using ipython interactively?

Note also that this is not OS-dependent, it happened both on RHELS7 and OSX-hosted conda environments.

Here are the python and ipython versions:

"old":
  - ipython=7.23.1
  - ipykernel=5.5.5
  - python=3.8.10

"new":
  - ipython=8.1.1
  - ipykernel=6.9.1
  - python=3.8.12
schlepuetz
  • 21
  • 3

0 Answers0