Timeout a Generic Function

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Timeout a Generic Function

Post by cversek » Sat Feb 18, 2017 5:42 am

I am interested in sandboxing a user-supplied function. This function must run asynchronously with an HTTP server. The first requirement would be to force exit the trial function if it takes too long so the server can get back to handling requests. At this point I'm not worried about malicious code, just catching programmer errors and reporting them nicely. I've implemented a "timeout" using a timer interrupt callback that raises an Exception and finally calls `machine.reset()` here is an example:

Code: Select all

import machine, time
from machine import Timer

TIMEOUT_MS = 5000 #soft-reset will happen around 5 sec

def timeout_callback(t):
    try:
        raise Exception("Timeout!")
    finally:
        machine.reset()  #FIXME, gentler way to break out?


def trial_function():
    cnt = 0
    while True:
        print("%d..." % cnt)
        time.sleep(1.0)
        cnt += 1
        
try:
    timer = Timer(0)
    timer.init(period=TIMEOUT_MS, mode=Timer.ONE_SHOT, callback=timeout_callback)
    trial_function()
    timer.deinit()
except Exception as exc:
    print("Caught exc: %s" % exc)
My question is to ask if any - less heavy-handed ways - of aborting this trial function are possible? Preferably, a timeout exception might be generated in the scope where the function runs, in order to be handled along with other exceptions. However, I've read in the Writing interrupt handlers guide that exceptions generated in ISRs are not forwarded to the main program; indeed the Exception's message is printed when the callback fires, but without `machine.reset()` the trial function keeps on counting. Clearly, there exist serval implementations that can timeout arbitrary functions in CPython environments, such as this ActiveState recipe using the multiprocessing library; but I doubt this approach could work directly in micropython (no processes to kill?). The EPS8266 and possibly the ESP32 are the ports that I am considering using for my project. I would even be willing to consider using a C-coded extension which could interact directly with the interpreter. Thanks in advance for your help,

- Craig
Last edited by cversek on Sat Feb 18, 2017 5:52 am, edited 1 time in total.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Timeout a Generic Function

Post by pythoncoder » Sat Feb 18, 2017 5:50 am

I'd suggest using uasyncio. Have two coroutines, one acting as a watchdog timer. The main coroutine periodically feeds the dog. If the dog times out, it can take any action you require. I've written a guide to the MicroPython asyncio subset here https://github.com/peterhinch/micropython-async.git which includes some code for a software watchdog (in aswitch.py).

This approach avoids interrupt issues.
Peter Hinch
Index to my micropython libraries.

cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Re: Timeout a Generic Function

Post by cversek » Sat Feb 18, 2017 7:09 am

Hi Peter,
Thanks for the quick response. I'm new to coroutines and a little confused, but maybe you can help clear things up for me? Without having direct control over the "trial function" would it actually be possible to build a control flow with coroutines that can run this function and force it to timeout even if it never `yields` but would otherwise run for an arbitrary amount of time? My (poor) understanding is that cooperative multitasking actually requires the tasks to explicitly yield control. I will definitely look into uasyncio in any case, because it seems quite useful and interesting.

For a little more background into the application, the intent is to allow even novice users of the system to upload and run sandboxed python scripts that interact directly with the hardware. (I know it sounds dangerous, right?) It is for an educational measurement and control platform using the LAN, but usually not exposed to the web. The user will write (or upload) a script on a webpage served by a Feather HUZZAH, which is then imported by the server; using socket timeouts the server can periodically schedule a function in this module. The problem comes when the user writes a long running function (very likely by accident) and it blocks the server from handling requests so the interface no longer works.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Timeout a Generic Function

Post by dhylands » Sat Feb 18, 2017 7:56 am

For functions which you don't control, you'd probably need to use the new soft-irq capability and schedule a soft-irq which throws an exception. This assume that the code doesn't catch all exceptions.

The soft-irq functionality hasn't landed yet but probably will soon.
See: https://github.com/micropython/micropython/pull/2878

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Timeout a Generic Function

Post by pythoncoder » Sat Feb 18, 2017 5:20 pm

cversek wrote:... would it actually be possible to build a control flow with coroutines that can run this function and force it to timeout even if it never `yields` but would otherwise run for an arbitrary amount of time? ...
You're right, asyncio can't cope with blocking code. I think @dhylands' solution is the way to go.
Peter Hinch
Index to my micropython libraries.

cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Re: Timeout a Generic Function

Post by cversek » Sun Feb 19, 2017 8:16 am

@dhylands
Thanks for the suggestion, I pulled the dpgeorge:scheduler branch and tried out the `micropython.schedule` function. Here is my updated timer callback:

Code: Select all

def timeout_callback(t):
    def _soft_ISR(arg):
        raise Exception("Timeout!")
    micropython.schedule(_soft_ISR,t)
Unfortunately, it had the same effect as directly raising the exception in the parent ISR (prints exception message and `trial_function` continues counting). Is this what you meant for me to try out?

BTW, I also tried this (because maybe it's weird to define a function in a hard ISR?), but got the same null result:

Code: Select all

def _soft_ISR(arg):
    raise Exception("Timeout!")

def timeout_callback(t):
    micropython.schedule(_soft_ISR,t)

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Timeout a Generic Function

Post by dhylands » Sun Feb 19, 2017 7:47 pm

I created a complete example (which doesn't seem to work) and added a comment to this thread:
https://github.com/micropython/micropython/pull/2878

Post Reply