Search code examples
pythonloopstimeoutctypesinfinite

Handling infinite loops in ctypes function with Python


Let's say I have a several functions defined in C, one of which results in an infinite loop. I am using the ctypes module in Python to run each of these functions, and hence it results in an infinite loop resulting to a complete halt in my Python script.

I tried to run the C function under a timeout, but the timeout never gets triggered. How do I handle this? I cannot stop my Python script, if this infinite loop function is encountered, my requirement is to to print an error message, then continue with the next function. The timeout looks something like this:

limit = 10
def raise_timeout(signum, frame):
    raise TimeoutError

def timeout(limit):
    signal.signal(signal.SIGALARM, raise_timeout)
    signal.alarm(limit)
    try:
        yield
    except TimeoutError:
        print('Timed out')
    finally:
        signal.signal(signal.SIGALARM, signal.SIG_IGN)


## Attempting to run function

for function in function_list:

    #Perform actions to load the shared .so library, define res/argtypes etc

    with timeout(limit):
        result = c_lib() # This is the function I run from ctypes

The only way I see is to handle it using a timer, for example, 10 seconds or so. But I feel like I'm doing this wrong - is there any way for my Python script to figure out that the ctypes function hasn't responded for 10 seconds, therefore it should exit somehow?

I'm desperate here, any sort of hacky thing that works but goes against common sense is also fine. :(

Any help is appreciated, thanks.


Solution

  • If the C code hangs, Python doesn't regain control as far as I know. You could try calling the function using a multiprocessing pool.

    Demo:

    import multiprocessing as mp
    from multiprocessing.pool import Pool
    
    import time
    
    def worker(work):
        time.sleep(work)  # pretend to do some work
        return work
    
    def call(func,work_time,timeout):
        global p
        a = p.apply_async(func,(work_time,))  # call function using pool
        try:
            result = a.get(timeout)           # wait for asynchronous result
            print(f'{result=}')
        except mp.TimeoutError:
            p.terminate()                     # kill the pool
            p.join()                          # wait for the pool processes to stop
            print('Terminated.')
            p = Pool(1)                       # restart pool
    
    if __name__ == '__main__':
        p = Pool(1)       # global process pool (1 process)
        call(worker,1,3)
        call(worker,2,3)
        call(worker,3,3)
        call(worker,4,3)
        p.close()         # Tell all processes in pool to stop
        p.join()          # Wait for them to stop.
    

    Output:

    result=1
    result=2
    Terminated.
    Terminated.