Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use it in a nested manner #19

Open
zachliu opened this issue Aug 17, 2021 · 2 comments
Open

Cannot use it in a nested manner #19

zachliu opened this issue Aug 17, 2021 · 2 comments

Comments

@zachliu
Copy link

zachliu commented Aug 17, 2021

I'm using it under Python 3.8.4

from time import sleep

from func_timeout import func_set_timeout


@func_set_timeout(1)
def func_1(sec):
    print(f"in func 1, sleeping {sec} sec but will timeout in 1 sec")
    sleep(sec)


@func_set_timeout(1)
def func_2(sec):
    print(f"in func 2, sleeping {sec} sec but will timeout in 1 sec")
    sleep(sec)


@func_set_timeout(5)
def func():
    func_1(2)
    print("in main func")
    func_2(0.3)


if __name__ == "__main__":
    func()

This program would stop at func_1(2) silently
it seems the top level @func_set_timeout(5) swallows all the FunctionTimedOut and doesn't re-raise

@zachliu
Copy link
Author

zachliu commented Aug 17, 2021

i think this is why but i don't understand the reason behind it

except FunctionTimedOut:
# Don't print traceback to stderr if we time out
pass

in order to use it in a nested manner, we need to capture the FunctionTimedOut and re-raise as something else such as TimeoutError for it to be captured by the top-level @func_set_timeout(5)

from functools import wraps
from time import sleep
from typing import Any, Callable, Type

from func_timeout import FunctionTimedOut, func_set_timeout


def retry(
    *exceptions: Type[Exception], count: int = 20, wait_time: int = 20,
) -> Callable:
    """Enable retry option for a set of exceptions
    """

    def retry_decorator(func: Callable) -> Callable:
        """Decorator"""

        @wraps(func)
        def func_wrapper(
            *args: Any, **kwargs: Any
        ) -> Any:
            """Function wrapper"""
            for i in range(count):
                try:
                    return func(*args, **kwargs)
                except exceptions as exception:  # pragma: no cover
                    print(
                        f"Retrying {i+1}: exception='{exception}', "
                        f"total {count} retries, wait {wait_time} seconds"
                    )
                    sleep(wait_time)

                    if i == count - 1:
                        if isinstance(exception, FunctionTimedOut):
                            raise TimeoutError("Re-raise as TimeoutError")
                        raise exception

        return func_wrapper

    return retry_decorator


@retry(
    FunctionTimedOut, count=2, wait_time=1,
)
@func_set_timeout(1)
def func_1(sec):
    print(f"in func 1, sleeping {sec} sec but will timeout in 1 sec")
    sleep(sec)


@retry(
    FunctionTimedOut, count=2, wait_time=1,
)
@func_set_timeout(1)
def func_2(sec):
    print(f"in func 2, sleeping {sec} sec but will timeout in 1 sec")
    sleep(sec)


@retry(
    FunctionTimedOut, count=2, wait_time=1,
)
@func_set_timeout(5)
def func():
    func_1(2)
    print("in main func")
    func_2(0.3)


if __name__ == "__main__":
    func()

@kata198
Copy link
Owner

kata198 commented Apr 23, 2023

Sorry for the delay, I've been away.

I've fixed this issue. The change is committed under the currently unreleased 4.4branch (I need to write tests and such)

If you want technical details on why that "pass" statement is there, it's to avoid the default exception handler which we cannot control, and it spams stderr.
You used to be able to override it, but not on anything modern. You can see the spam if you change that "pass" to "raise" and run the test suite.

I've updated the code so that we now explicitly check if we are going to raise the exception into another StoppableThread, in which case 4.4branch now does. Otherwise, (returning to the MainThread or a different type of thread) we continue to eat the error.

The test suite passes with this change, and your testcase also passes. I'll write some test cases to make sure this covers the issue completely, and it stays covered.

Thanks for the report!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants