Skip to content

retry() hangs indefinitely (and silently) if a retry_base is passed positionally #269

@SnoopJ

Description

@SnoopJ

If a user decorates a function with @retry(inst) for a retry_base instance inst for any of the builtin strategies as a positional argument—an easy misspelling of @retry(retry=inst))—tenacity will hang indefinitely as the resulting decoration attempts to call the strategy with the function as an argument, which will generally be an error.

Below is a program that recreates this failure for all builtin strategies.

from multiprocessing import Pool, Process
import sys

from tenacity import retry
from tenacity.retry import *


def make_class(strat):
    """
    This sample will hang indefinitely while defining the class Bar

    retry() thinks the strategy is what I want to retry,the unrolled
    equivalent is:

          def retryable(self, param):
              pass
          retryable = retry(strat)(retryable)

    so the thing being retried is strat(retryable) which is very unlikely
    to be a valid call
    """
    class Bar:
        print("Defining Bar...")

        @retry(strat)  # oops! I forgot to type retry=
        def retryable(self, param):
            pass

        print("Done defining Bar")  # we never get here

    return Bar
def make_func(strat):                                                                                                                  [1/1317]
    """
    This sample will hang indefinitely while defining the function task()
    """
    @retry(strat)  # oops! I forgot to type retry=
    def task(x):
        print("Running task")  # will never print
        pass

    return task

strats = {
    "retry_if_exception": retry_if_exception(lambda e: True),
    "retry_if_exception_type": retry_if_exception_type(TimeoutError),
    "retry_unless_exception_type": retry_unless_exception_type(RuntimeError),
    "retry_if_result": retry_if_result(lambda e: True),
    "retry_if_not_result": retry_if_not_result(lambda e: False),
    "retry_if_exception_message": retry_if_exception_message(match=".*"),
    "retry_if_not_exception_message": retry_if_not_exception_message(match=".*"),
    "retry_any": retry_any(retry_if_result(lambda x: lambda y: True),),
    "retry_all": retry_all(retry_if_result(lambda x: lambda y: True),),
}


TIMEOUT = 5

if __name__ == "__main__":
    for name,strat in strats.items():
        for f in (make_class, make_func):
            proc = Process(target=f, args=(strat,))
            print(f"Calling {f.__name__} with arg {name} with a timeout of {TIMEOUT} seconds")
            proc.start()
            proc.join(TIMEOUT)  # give the process a few seconds
            if proc.exitcode is None:
                print(f"*** Process for {f.__name__}({name}) is hung, terminating ***\n")
                proc.terminate()
            else:
                print(f"Process terminated with exit code {proc.exitcode}")

Output:

Calling make_class with arg retry_if_exception with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_exception) is hung, terminating ***

Calling make_func with arg retry_if_exception with a timeout of 5 seconds
*** Process for make_func(retry_if_exception) is hung, terminating ***

Calling make_class with arg retry_if_exception_type with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_exception_type) is hung, terminating ***

Calling make_func with arg retry_if_exception_type with a timeout of 5 seconds
*** Process for make_func(retry_if_exception_type) is hung, terminating ***

Calling make_class with arg retry_unless_exception_type with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_unless_exception_type) is hung, terminating ***

Calling make_func with arg retry_unless_exception_type with a timeout of 5 seconds
*** Process for make_func(retry_unless_exception_type) is hung, terminating ***

Calling make_class with arg retry_if_result with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_result) is hung, terminating ***

Calling make_func with arg retry_if_result with a timeout of 5 seconds
*** Process for make_func(retry_if_result) is hung, terminating ***

Calling make_class with arg retry_if_not_result with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_not_result) is hung, terminating ***

Calling make_func with arg retry_if_not_result with a timeout of 5 seconds
*** Process for make_func(retry_if_not_result) is hung, terminating ***

Calling make_class with arg retry_if_exception_message with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_exception_message) is hung, terminating ***

Calling make_func with arg retry_if_exception_message with a timeout of 5 seconds
*** Process for make_func(retry_if_exception_message) is hung, terminating ***

Calling make_class with arg retry_if_not_exception_message with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_if_not_exception_message) is hung, terminating ***

Calling make_func with arg retry_if_not_exception_message with a timeout of 5 seconds
*** Process for make_func(retry_if_not_exception_message) is hung, terminating ***

Calling make_class with arg retry_any with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_any) is hung, terminating ***

Calling make_func with arg retry_any with a timeout of 5 seconds
*** Process for make_func(retry_any) is hung, terminating ***

Calling make_class with arg retry_all with a timeout of 5 seconds
Defining Bar...
*** Process for make_class(retry_all) is hung, terminating ***

Calling make_func with arg retry_all with a timeout of 5 seconds
*** Process for make_func(retry_all) is hung, terminating ***

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions