5

I have a C-extension which implements an LRU cache https://github.com/pbrady/fastcache . I've recently noticed that in an application (SymPy) which makes fairly heavy use of caching, a timeout signal gets lost and the application continues to run. This only happens when using my C-extension and not with a pure Python LRU cache (i.e. functools.lru_cache) https://github.com/pbrady/fastcache/issues/26.

I've peppered my routines with calls to PyErr_CheckSignals() and the signal gets lost less frequently but it's still happening. Note that during a call, the cache will call PyObject_Hash, PyDict_Get/Set/DelItem and PyObject_Call (in the case of a miss).

Here's the relevant snippet of the SymPy code (timeout is an integer):

def _timeout(self, function, timeout):
    def callback(x, y):
        signal.alarm(0)
        raise Skipped("Timeout")
    signal.signal(signal.SIGALRM, callback)
    signal.alarm(timeout)  # Set an alarm with a given timeout
    function()
    signal.alarm(0)  # Disable the alarm    enter code here

Can something be overwriting the signal? If so, how do I work around this?

ptb
  • 2,138
  • 1
  • 11
  • 16

1 Answers1

1

Turns out there is no real mystery here but it is a little tricky.

In addition to calls to PyErr_CheckSignals() the signal can be caught by the interpreter whenever control passes to it via calls to PyObject_Hash, PyDict_Get/Set/DelItem. If the signal is caught by the interpreter in one of these functions it will trigger an exception due to the callback function (and the signal will go away since it was handled). However, I was not checking the return value of all my functions (i.e. I knew my argument was hashable so I wasn't checking the return value of PyDict_SetItem) Thus the exception was ignored and the program continued to execute as if the signal had not happened.

Special thanks to Ondrej for talking through this.

ptb
  • 2,138
  • 1
  • 11
  • 16