A function that executes the given function after a delay.

  Executes func in a separate thread after a delay, so that this function
  returns immediately.  Note that any exceptions raised by func will be
  ignored (but logged).  Also, if time_s is a PolledTimeout with no expiration,
  then this method simply returns immediately and does nothing.

  Args:
    time_s: Delay in seconds to wait before executing func, may be a
      PolledTimeout object.
    func: Function to call.
    *args: Arguments to call the function with.
    **kwargs: Keyword arguments to call the function with.

def execute_after_delay(time_s, func, *args, **kwargs):
  """A function that executes the given function after a delay.

  Executes func in a separate thread after a delay, so that this function
  returns immediately.  Note that any exceptions raised by func will be
  ignored (but logged).  Also, if time_s is a PolledTimeout with no expiration,
  then this method simply returns immediately and does nothing.

  Args:
    time_s: Delay in seconds to wait before executing func, may be a
      PolledTimeout object.
    func: Function to call.
    *args: Arguments to call the function with.
    **kwargs: Keyword arguments to call the function with.
  """
  timeout = PolledTimeout.from_seconds(time_s)

  def target():
    time.sleep(timeout.remaining)
    try:
      func(*args, **kwargs)
    except Exception:  # pylint: disable=broad-except
      _LOG.exception('Error executing %s after %s expires.', func, timeout)
  if timeout.remaining is not None:
    thread = threading.Thread(target=target)
    thread.start()