|
| 1 | +import os |
| 2 | +import redis.asyncio as redis |
| 3 | +from models.match import MatchData |
| 4 | +import asyncio |
| 5 | +match_channel = 'match_channel' |
| 6 | + |
| 7 | +# Initialize Redis client |
| 8 | + |
| 9 | + |
| 10 | +async def get_redis(): |
| 11 | + redis_host = os.getenv("REDIS_HOST", "localhost") |
| 12 | + redis_port = os.getenv("REDIS_PORT", "6379") |
| 13 | + return redis.Redis(host=redis_host, port=int(redis_port), db=0, decode_responses=True) |
| 14 | + |
| 15 | + |
| 16 | +async def acquire_lock(redis_client, key, lock_timeout_ms=30000, retry_interval_ms=100, max_retries=100) -> bool: |
| 17 | + lock_key = f"{key}:lock" |
| 18 | + retries = 0 |
| 19 | + |
| 20 | + while retries < max_retries: |
| 21 | + locked = await redis_client.set(lock_key, "locked", nx=True, px=lock_timeout_ms) |
| 22 | + if locked: |
| 23 | + return True |
| 24 | + else: |
| 25 | + retries += 1 |
| 26 | + # Convert ms to seconds |
| 27 | + await asyncio.sleep(retry_interval_ms / 1000) |
| 28 | + |
| 29 | + return False |
| 30 | + |
| 31 | + |
| 32 | +async def release_lock(redis_client, key) -> None: |
| 33 | + lock_key = f"{key}:lock" |
| 34 | + await redis_client.delete(lock_key) |
| 35 | + |
| 36 | + |
| 37 | +# Helper function to build a unique queue key based on topic and difficulty |
| 38 | +def build_queue_key(topic, difficulty): |
| 39 | + return f"{topic}:{difficulty}" |
| 40 | + |
| 41 | +# Asynchronous task to listen for matches |
| 42 | + |
| 43 | + |
| 44 | +async def listen_for_matches(redis_client): |
| 45 | + pubsub = redis_client.pubsub() |
| 46 | + await pubsub.subscribe(match_channel) |
| 47 | + async for message in pubsub.listen(): |
| 48 | + if message["type"] == "message": |
| 49 | + print(f"Match notification: {message['data']}") |
| 50 | + |
| 51 | +# Asynchronous matching logic |
| 52 | + |
| 53 | + |
| 54 | +async def find_match_else_enqueue(user_id, topic, difficulty): |
| 55 | + redis_client = await get_redis() |
| 56 | + queue_key = build_queue_key(topic, difficulty) |
| 57 | + |
| 58 | + result = None |
| 59 | + |
| 60 | + # ACQUIRE LOCK |
| 61 | + islocked = await acquire_lock(redis_client, queue_key) |
| 62 | + |
| 63 | + if not islocked: |
| 64 | + raise Exception("Could not acquire lock") |
| 65 | + |
| 66 | + # Check if the user is already in the queue |
| 67 | + user_in_queue = await redis_client.lrange(queue_key, 0, -1) |
| 68 | + if user_id in user_in_queue: |
| 69 | + result = {"message": f"User { |
| 70 | + user_id} is already in the queue, waiting for a match"} |
| 71 | + else: |
| 72 | + queue_length = await redis_client.llen(queue_key) |
| 73 | + if queue_length > 0: |
| 74 | + matched_user = await redis_client.rpop(queue_key) |
| 75 | + match_data = MatchData( |
| 76 | + user1=user_id, user2=matched_user, topic=topic, difficulty=difficulty) |
| 77 | + await redis_client.publish(match_channel, match_data.json()) |
| 78 | + result = {"message": "Match found", "match": match_data} |
| 79 | + else: |
| 80 | + await redis_client.lpush(queue_key, user_id) |
| 81 | + result = {"message": f"User { |
| 82 | + user_id} enqueued, waiting for a match"} |
| 83 | + |
| 84 | + # RELEASE LOCK |
| 85 | + await release_lock(redis_client, queue_key) |
| 86 | + return result |
| 87 | + |
| 88 | + |
| 89 | +async def remove_user_from_queue(user_id, topic, difficulty): |
| 90 | + redis_client = await get_redis() |
| 91 | + queue_key = build_queue_key(topic, difficulty) |
| 92 | + |
| 93 | + # ACQUIRE LOCK |
| 94 | + islocked = await acquire_lock(redis_client, queue_key) |
| 95 | + |
| 96 | + if not islocked: |
| 97 | + raise Exception("Could not acquire lock") |
| 98 | + |
| 99 | + # Check if the user is already in the queue |
| 100 | + user_in_queue = await redis_client.lrange(queue_key, 0, -1) |
| 101 | + if user_id in user_in_queue: |
| 102 | + await redis_client.lrem(queue_key, 0, user_id) |
| 103 | + result = {"message": f"User { |
| 104 | + user_id} removed from the queue"} |
| 105 | + else: |
| 106 | + result = {"message": f"User { |
| 107 | + user_id} is not in the queue"} |
| 108 | + |
| 109 | + # RELEASE LOCK |
| 110 | + await release_lock(redis_client, queue_key) |
| 111 | + return result |
0 commit comments