cheran-senthil / TLE

🤖 Discord Bot for Competitive Programming
https://discordapp.com/invite/2CJ6qvY
MIT License
318 stars 202 forks source link

Ensure Codeforces API rate limit is respected #52

Closed meooow25 closed 5 years ago

meooow25 commented 5 years ago

API may be requested at most 5 times in one second.

Source documentation

algmyr commented 5 years ago

Untested sketch of a rate limiter

import time
import itertools
def rate_limit(f, per_second, active = {}, counter = itertools.count()):
    async def wrapped(*args, **kwargs):
        req_id = next(counter)
        now = time.time()

        # Next valid slot is 1s after the `per_second`th last request
        next_valid = now
        if len(active) >= per_second:
            next_valid = max(next_valid, active[list(active.keys())[-per_second]])
        active[req_id] = next_valid

        # Delay as needed
        delay = next_valid - now
        if delay > 0:
            await asyncio.sleep(delay)

        result = await f(*args, **kwargs)
        del active[req_id]
        return result
    return wrapped

If it looks reasonable I can test it and make a PR.

aryamanarora commented 5 years ago

Unlike blues who must be disrespected

algmyr commented 5 years ago

Ok, revamped and seems to work fine.

from collections import deque
import asyncio
import time

def cf_ratelimit(f):
    per_second = 5
    last = deque([0]*per_second)
    async def wrapped(*args, **kwargs):
        now = time.time()

        # Next valid slot is 1s after the `per_second`th last request
        next_valid = max(now, 1 + last[0])
        last.append(next_valid)
        last.popleft()

        # Delay as needed
        delay = next_valid - now
        if delay > 0:
            await asyncio.sleep(delay)

        return await f(*args, **kwargs)
    return wrapped

@cf_ratelimit
async def f(s):
    print(s)

async def main():
    for i in range(50):
        await f(i)

asyncio.run(main())
meooow25 commented 5 years ago

Looks good. We should also integrate the lock here, and implement API request retries if possible.

krofna commented 5 years ago

@algmyr can you create a pull request?

algmyr commented 5 years ago

Closed by #82. Can be replaced later on if we want something more fancy.