redis/redis-py · error · TimeoutError

Timeout reading from

Error message

Timeout reading from {host_error}

What it means

Raised as TimeoutError from read_response() when the read_timeout (self.socket_timeout, or an explicit per-call timeout that is None) elapses before the parser produces a response. Only raised when the user did NOT pass an explicit timeout; passing an explicit timeout that elapses returns None instead (the operation is retried). The connection is disconnected nowait before raising.

Solutions

  1. Raise socket_timeout to exceed the longest legitimate blocking command.
  2. For blocking commands, pass an explicit timeout= to read_response()/parse_response() so you get None instead of an exception.
  3. Investigate server slowlog, AOF/fsync latency, and fork latency (INFO stats, LATENCY events).
  4. Use command_timeout or per-command timeouts instead of a single global socket_timeout when workloads vary.

Example fix

// before
r = redis.asyncio.Redis(host=h, socket_timeout=1.0)
v = await r.blpop('q', timeout=10)  # always times out the socket first
// after
r = redis.asyncio.Redis(host=h)  # no socket_timeout
v = await r.blpop('q', timeout=10)
Defensive patterns

Strategy: try-catch

Validate before calling

def socket_timeout_for_block(block_seconds: float) -> float | None:
    # don't set a socket_timeout smaller than the longest blocking wait
    return None if block_seconds > 0 else 5.0

Type guard

def is_read_timeout(exc: BaseException) -> bool:
    return isinstance(exc, TimeoutError) and 'reading from' in str(exc).lower()

Try / catch

from redis.retry import Retry
from redis.backoff import ExponentialBackoff

r = redis.asyncio.Redis(
    host=h,
    socket_timeout=5.0,
    retry=Retry(ExponentialBackoff(), 2),
    retry_on_timeout=True,
)

Prevention

When it happens

Trigger: Any await of a response (command read, pubsub poll, BLPOP-style blocking command) when socket_timeout is set and the server has not replied in time. Distinct from a blocking-command wait: this is the socket-level budget, and it kills the connection.

Common situations: socket_timeout smaller than a legitimate BLPOP/BRPOP/XREAD block duration; server stalled (slowlog, fork during save, AOF fsync); large response stuck behind head-of-line blocking; cross-region latency spikes.

Understand the failure class

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/67d1fda577b373ec. Report an issue: GitHub.

Appendix: source

Thrown at redis/asyncio/connection.py:1310

                else:
                    async with timeout_context:
                        response = await self._read_response_from_parser(
                            disable_decoding=disable_decoding,
                            push_request=push_request,
                        )
            else:
                response = await self._read_response_from_parser(
                    disable_decoding=disable_decoding,
                    push_request=push_request,
                )
        except asyncio.TimeoutError:
            if timeout is not None:
                # user requested timeout, return None. Operation can be retried
                return None
            # it was a self.socket_timeout error.
            if disconnect_on_error:
                await self.disconnect(nowait=True)
            raise TimeoutError(f"Timeout reading from {host_error}")
        except OSError as e:
            if disconnect_on_error:
                await self.disconnect(nowait=True)
            raise ConnectionError(f"Error while reading from {host_error} : {e.args}")
        except BaseException:
            # Also by default close in case of BaseException.  A lot of code
            # relies on this behaviour when doing Command/Response pairs.
            # See #1128.
            if disconnect_on_error:
                await self.disconnect(nowait=True)
            raise

        if self.health_check_interval:
            next_time = asyncio.get_running_loop().time() + self.health_check_interval
            self.next_health_check = next_time

        if isinstance(response, ResponseError):
            raise response from None

View on GitHub (pinned to 6a6b581b48)