Skip to content

Instantly share code, notes, and snippets.

@TySkby
Last active October 27, 2016 23:29
Show Gist options
  • Save TySkby/149af7713805fe76f701d73b7e4199e2 to your computer and use it in GitHub Desktop.
Save TySkby/149af7713805fe76f701d73b7e4199e2 to your computer and use it in GitHub Desktop.
Decorator that ensures a function takes at least X seconds to execute
"""
Simple decorator that prevents a function from finishing faster than a given number of seconds.
Useful if you're fairly confident that your function should finish in a certain amount of time,
but you want to make the return time constant (eg. to prevent constant-time attacks).
For example, if I know that my password reset function always takes about 1 second to execute
if the given email address is valid but if the email address is invalid, it usually finishes much faster,
I can ensure that it always takes 2 seconds to execute whenever it's called.
The usefulness here goes out the window if your function's normal execution time is subject
to a lot of jitter.
"""
def minimum_time(seconds):
def decorator(fn):
@wraps(fn)
def decorated_function(*args, **kwargs):
start = time.time()
rv = fn(*args, **kwargs)
end = time.time()
runtime = end - start
if runtime < seconds:
time.sleep(seconds - runtime)
return rv
return decorated_function
return decorator
# Usage:
@minimum_time(2)
def request_password_reset(email_address):
user = get_user_by_email(email_address)
if email_address:
# Assume this reliably takes around 1 second to finish
send_reset_token_to_user(user)
return 'Finished! User has been notified if they are indeed a real user.'
# This usually takes around 1 second to finish
request_password_reset('real.user@example.com')
# This usually finishes much faster because `send_reset_token_to_user` never gets called
request_password_reset('fake.user@example.com')
# But now they'll always take 2 seconds! (or more, in which case this is less useful...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment