func_timeout icon indicating copy to clipboard operation
func_timeout copied to clipboard

FunctionTimedOut can't be catch by expcet Exception as e: .

Open yougrianes opened this issue 2 years ago • 4 comments

I find that FunctionTimedOut is the same level exceptions as Exception, and both create from BaseException. this thing makes me a little bit confused at the first time.

yougrianes avatar Feb 15 '23 07:02 yougrianes

Agree. I have to use except BaseException as e to catch the error. Really confused. What's the design purpose?

secsilm avatar May 12 '23 06:05 secsilm

Raising an exception from BaseException rather than Exception makes multiprocessing.map failed to handle the exceptions in subprocess, causing the process pool failed to join.


import os
import multiprocessing
from multiprocessing import Pool
import func_timeout
from func_timeout import func_set_timeout

@func_set_timeout(1)
def dummy(x):
    os.system("sleep 1")
    return x

def wrapper(x):
    try:
        return dummy(x)
    except Exception as e:
        # func_timeout.exceptions.FunctionTimedOut
        # raise
        print(e)
        return None

logger = multiprocessing.log_to_stderr()
logger.setLevel(multiprocessing.SUBDEBUG)

with Pool(8) as pool:
    r = pool.map(wrapper, range(100), chunksize=8)
print(r)

caic99 avatar Sep 15 '23 03:09 caic99

I think it should definitely inherits from Exception rather than BaseException. It causes issues on our side because we want to just catch Exception error and not catch BaseException errors like a systemexit or keyboardinterrupt. So we need to wrap calls to this libs just to rethrow a normal error that inherits from Exception instead of BaseException. A bit convoluted imo.

brianquirion avatar May 06 '24 18:05 brianquirion

same issue today, an exception handler i configured in FastApi wouldn't handle such timeout errors

# Exception handler for general exceptions
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc: Exception):    
    return await _error_formatter(error_message=str(exc),
                                  error_type="server_error",
                                  status_code=500,
                                  )

@app.get("/some_long_job")
async def some_long_job(arg1, arg2):
    result = func_timeout.func_timeout(timeout=0.02, func=some_specific_long_job, args=(arg1, arg2))
    return result

went to source code of "FunctionTimedOut" and changed baseException to Exception, obviously a bad idea.

this is my current workaroud:

class TimeoutException(Exception):
    pass

def func_timeout_handleable(timeout: float,
                            func: any,
                            args: tuple = (),
                            kwargs: dict = None,
                            specific_error_message: str = None
                            ):
    try:
        result = func_timeout.func_timeout(timeout=timeout, func=func, args=args, kwargs=kwargs)
        return result
    except func_timeout.exceptions.FunctionTimedOut:
        if specific_error_message is None:
            raise TimeoutException(f"timed out after {timeout} seconds")
        else:
            raise TimeoutException(specific_error_message)

Moeinoddin-Mansourfar avatar Jul 03 '24 14:07 Moeinoddin-Mansourfar