diff --git a/README.md b/README.md index d9af2ba..346b3eb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ### Option 1: Manual Setup ```sh -docker run --net=host --rm -v ./redis:/usr/local/etc/redis --name test-redis redis redis-server /usr/local/etc/redis/redis.conf +docker run --rm -p 6379:6379 -v ./redis:/usr/local/etc/redis --name test-redis redis redis-server /usr/local/etc/redis/redis.conf pixi run serve ``` @@ -48,7 +48,7 @@ for i in {1..20}; do curl -s -D - http://localhost:8000/stream/live 2>/dev/null Before running tests, start Redis: ```sh -docker run --net=host --rm -v ./redis:/usr/local/etc/redis --name test-redis redis redis-server /usr/local/etc/redis/redis.conf +docker run --rm -p 6379:6379 -v ./redis:/usr/local/etc/redis --name test-redis redis redis-server /usr/local/etc/redis/redis.conf ``` ### Run the Test Suite diff --git a/server.py b/server.py index 6e592d8..bd93553 100644 --- a/server.py +++ b/server.py @@ -15,10 +15,17 @@ class Settings(BaseSettings): redis_url: str = "redis://localhost:6379/0" ttl: int = 60 * 60 # 1 hour + socket_timeout: float = 10.0 # 5 seconds + socket_connect_timeout: float = 10.0 # 10 seconds def build_app(settings: Settings): - redis_client = redis.from_url(settings.redis_url) + # This doesn't connect to the redis, so it will work if redis is down. + redis_client = redis.from_url( + settings.redis_url, + socket_timeout=settings.socket_timeout, + socket_connect_timeout=settings.socket_connect_timeout, + ) @asynccontextmanager async def lifespan(app: FastAPI): diff --git a/tests/test_redis_timeout.py b/tests/test_redis_timeout.py new file mode 100644 index 0000000..fb5cd52 --- /dev/null +++ b/tests/test_redis_timeout.py @@ -0,0 +1,51 @@ +import time +import pytest +import redis.asyncio as redis + + +def test_redis_command_with_slow_operation(): + """Test that Redis operations timeout when they take too long.""" + import asyncio + from server import Settings + + # Create Redis client with timeout configuration + redis_client = redis.from_url( + "redis://localhost:6379/0", + # Operations will hang indefinitely without this timeout. + socket_timeout=1.0, # 1 second timeout + retry_on_timeout=False + ) + + async def run_slow_operation(): + # Use a Lua script that busy-waits for 3 seconds (longer than timeout) + slow_script = """ + local start_time = tonumber(redis.call('TIME')[1]) + local end_time = start_time + 3 + + while tonumber(redis.call('TIME')[1]) < end_time do + -- busy wait + end + + return "done" + """ + + # This should timeout because the script takes 3 seconds but timeout is 1s + result = await redis_client.eval(slow_script, 0) + return result + + start_time = time.time() + + # This should timeout, not complete successfully + with pytest.raises((redis.TimeoutError)): + asyncio.run(run_slow_operation()) + + elapsed_time = time.time() - start_time + + # Should timeout within a reasonable time (much less than 3 seconds) + assert elapsed_time < 2.0, f"Redis operation took too long: {elapsed_time} seconds" + + # Clean up + try: + asyncio.run(redis_client.aclose()) + except Exception: + pass