# Buggy code
def timeout(seconds=None, error_message="Timer expired"):
    def decorator(func):
        def _handle_timeout(signum, frame):
            msg = "Timer expired after %s seconds" % globals().get("GATHER_TIMEOUT")
            raise TimeoutError(msg)

        def wrapper(*args, **kwargs):
            local_seconds = seconds
            if local_seconds is None:
                local_seconds = (
                    globals().get("GATHER_TIMEOUT") or DEFAULT_GATHER_TIMEOUT
                )
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(local_seconds)

            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return wrapper

    # If we were called as @timeout, then the first parameter will be the
    # function we are to wrap instead of the number of seconds.  Detect this
    # and correct it by setting seconds to our default value and return the
    # inner decorator function manually wrapped around the function
    if callable(seconds):
        func = seconds
        seconds = None
        return decorator(func)

    # If we were called as @timeout([...]) then python itself will take
    # care of wrapping the inner decorator around the function

    return decorator


# Traceback (TimeoutError)
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_d9POp0/ansible_modlib.zip/ansible/module_utils/basic.py", line 2853, in run_command
cmd = subprocess.Popen(args, **kwargs)
File "/usr/lib64/python2.7/subprocess.py", line 711, in __init__
errread, errwrite)
File "/usr/lib64/python2.7/subprocess.py", line 1308, in _execute_child
data = _eintr_retry_call(os.read, errpipe_read, 1048576)
File "/usr/lib64/python2.7/subprocess.py", line 478, in _eintr_retry_call
return func(*args)
File "/tmp/ansible_d9POp0/ansible_modlib.zip/ansible/module_utils/facts/timeout.py", line 37, in _handle_timeout
raise TimeoutError(msg)
TimeoutError: Timer expired after 10 seconds

fatal: [kso-nlape1vp-rano-009]: FAILED! => {
"changed": false,
"cmd": "/usr/bin/udevadm info --query property --name /dev/sda1",
"invocation": {
"module_args": {
"fact_path": "/etc/ansible/facts.d",
"filter": "*",
"gather_subset": [
"all"
],
"gather_timeout": 10
}
},
"msg": "Timer expired after 10 seconds",
"rc": 257
}

# Fixed code
def timeout(seconds=None, error_message="Timer expired"):
    """
    Timeout decorator to expire after a set number of seconds.  This raises an
    ansible.module_utils.facts.TimeoutError if the timeout is hit before the
    function completes.
    """

    def decorator(func):
        def wrapper(*args, **kwargs):
            timeout_value = seconds
            if timeout_value is None:
                timeout_value = (
                    globals().get("GATHER_TIMEOUT") or DEFAULT_GATHER_TIMEOUT
                )

            pool = mp.ThreadPool(processes=1)
            res = pool.apply_async(func, args, kwargs)
            pool.close()
            try:
                return res.get(timeout_value)
            except multiprocessing.TimeoutError:
                # This is an ansible.module_utils.common.facts.timeout.TimeoutError
                raise TimeoutError("Timer expired after %s seconds" % timeout_value)

        return wrapper

    # If we were called as @timeout, then the first parameter will be the
    # function we are to wrap instead of the number of seconds.  Detect this
    # and correct it by setting seconds to our default value and return the
    # inner decorator function manually wrapped around the function
    if callable(seconds):
        func = seconds
        seconds = None
        return decorator(func)

    # If we were called as @timeout([...]) then python itself will take
    # care of wrapping the inner decorator around the function

    return decorator