Description
Hi there,
thank you very much for your time_bucketed.py code. And special thanks to the idea of using some kind of "icons" in the print messages. Didn't know that was possible, but I like it very much.
However, there are some issues that led to two deadlocks within an hour while I tested it:
bucket_val = r.get(key)
if bucket_val and int(bucket_val) > 0:
r.decrby(key, 1)
return False
If the key expires between GET and DECRBY, you'll generate a new key with value -1 but no associated expire (hence, the key will never expire). And because you use SETNX to create a key, you'll never create a new key with an associated expire, if there already is one without an associated expire :
if r.setnx(key, limit):
r.expire(key, int(period.total_seconds()))
Furthermore if other processes already decreased the value of that key to 0 between the GET and DECRBY, you might exceed the rate limit in this timeframe.
So I came up with this solution, that should address both issues:
#TTL returns -2 if the key does not exist and -1 if the key exists but has no associated expire.
#Reference: https://redis.io/commands/ttl
if int(R.ttl(key)) < 0:
#Creates a key with associated expire and reduces the limit by 1 atomically since we use one token/call the API right after.
R.setex(key, int(period.total_seconds()), limit-1)
return False
if R.exists(key):
#DECR creates a key with the value -1 if the key does not yet exist.
#Checks if this happend or some other process already used a token because there is a race condition.
#Reference: https://redis.io/commands/decr
if int(R.decr(key)) >= 0:
return False
return True
Regards
Mat