Skip to content

time_bucketed.py not deadlock safe #1

Open
@mbaader

Description

@mbaader

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions