Handling infinite loops in ctypes function with Python

Question:

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.

Asked By: try_hard

||

Answers:

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.
Answered By: Mark Tolonen

I’ve encountered this same problem, and whilst the multiprocessing solution is fine, it can be a bit cumbersome to implement. I’ve had success using signal.alarm:

timeout = 3
signal.alarm(timeout) # sets the timeout to 3 seconds
ctype_call_that_may_block_forever()
signal.alarm(0) # disables the alarm