vutran1710/PyrateLimiter

Ratelimiting in async context raising BucketFullException, and Redis not working in any concurrency mode

Closed this issue · 3 comments

  1. I am trying to rate limit a simple function in async context and i keep getting BucketFullException no matter what bucket im using (memory, redis, file lock sqlite)
  2. When i test the concurrency using redis i find that there is no way to use it concurrently..

Ultimately i want to be able to rate limit functions across multiple instances, thats why i wanted to use redis. is it not possible?

import logging
import os
import tempfile
import asyncio
import datetime

from concurrent import futures
from dataclasses import dataclass
from enum import Enum

from pyrate_limiter import RequestRate, Limiter, FileLockSQLiteBucket, RedisBucket
from redis.client import Redis


@dataclass
class Rate:
    requests: int
    seconds: int

class BucketClass(str, Enum):
    REDIS = 'redis'
    FILE_LOCK_SQLITE = 'file_lock_sqlite'
    MEMORY = 'memory'

def create_limiter(*rates: Rate, bucket_class: BucketClass = BucketClass.FILE_LOCK_SQLITE):
    rates = [RequestRate(rate.requests, rate.seconds) for rate in rates]

    if bucket_class == BucketClass.FILE_LOCK_SQLITE:
        bucket_path = os.path.join(tempfile.gettempdir(), 'ratelimit_sqlite')
        return Limiter(*rates, bucket_class=FileLockSQLiteBucket, bucket_kwargs={'path': bucket_path})
    if bucket_class == BucketClass.MEMORY:
        return Limiter(*rates)
    if bucket_class == BucketClass.REDIS:
        redis_cluster_url = "CENSORED"
        redis_client = Redis(host=redis_cluster_url, port=6379, db=0, ssl=True, ssl_cert_reqs="none")
        return Limiter(*rates, bucket_class=RedisBucket,
                       bucket_kwargs={'redis_pool': redis_client.connection_pool,
                                      'bucket_name': 'ratelimit_test'}
                       )

limiter = create_limiter(Rate(1, 1), bucket_class=BucketClass.REDIS)

@limiter.ratelimit('tomertest123', delay=True)
async def async_func():
    print(datetime.datetime.utcnow())

@limiter.ratelimit('tomertest123', delay=True)
def sync_func():
    print(datetime.datetime.utcnow())

TEST_MODE = 'threading'

# Always raising BucketFullException, regardless of what bucket im using
if TEST_MODE == 'async':
    async def main():
        tasks = [asyncio.create_task(async_func()) for _ in range(5)]
        asyncio.gather(*tasks)


    asyncio.run(main())

# When using Redis bucket, it just runs them all at the same time
elif TEST_MODE == 'threading':
    with futures.ThreadPoolExecutor(5) as executor:
        future_items = [executor.submit(sync_func) for _ in range(5)]
        executor.shutdown(wait=False)
        for future in futures.as_completed(future_items):
            future.result()

# Works but i dont want to run it synchronously
elif TEST_MODE == 'sync':
    for _ in range(5):
        sync_func()

very sorry for the inconvenience. actually pyrate-limiter v2 does not have async-redis backend.

yet V2 is now in mantainance mode and im releasing pyrate limiter V3 in a few days, which has had everything fixed and bundled with more useful features + better performance

So if you are not in a hurry, you can hang around for a while and come back later. otherwise you can manually implement async-redis bucket by copying the existing redis-bucket and change the method to async one.

@vutran1710 ohh i dont mind if it will take few days, thanks!

V3 is out! :)