Skip to content

Comments

feat: add configurable connect and read timeouts to Redis connections#70

Open
lohanidamodar wants to merge 4 commits intomainfrom
feat/redis-connection-timeouts
Open

feat: add configurable connect and read timeouts to Redis connections#70
lohanidamodar wants to merge 4 commits intomainfrom
feat/redis-connection-timeouts

Conversation

@lohanidamodar
Copy link
Contributor

@lohanidamodar lohanidamodar commented Feb 22, 2026

Queue Redis connections previously used PHP's default timeout of 0 (blocking indefinitely). When Redis becomes unresponsive due to contention (e.g. Dragonfly TxQueue saturation), worker BRPOP calls would hang until the OS TCP timeout (21-127s), causing fatal "read error on connection" crashes and cascading restarts.

Adds connectTimeout and readTimeout parameters (default 5s) to both Redis and RedisCluster connection classes so callers can configure fail-fast behaviour.

Summary by CodeRabbit

  • New Features

    • Redis and Redis Cluster connections gain configurable connect and read timeouts (defaults preserved), allowing better control over connection behavior.
    • Blocking operations now honor per-call read timeouts to avoid indefinite blocking.
  • Bug Fixes

    • Improved robustness: blocking calls restore timeouts after use and will reset connections on errors to prevent stale/locked connections.

Queue Redis connections previously used PHP's default timeout of 0
(blocking indefinitely). When Redis becomes unresponsive due to
contention (e.g. Dragonfly TxQueue saturation), worker BRPOP calls
would hang until the OS TCP timeout (21-127s), causing fatal
"read error on connection" crashes and cascading restarts.

Adds `connectTimeout` and `readTimeout` parameters (default 5s) to
both `Redis` and `RedisCluster` connection classes so callers can
configure fail-fast behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Warning

Rate limit exceeded

@lohanidamodar has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 53 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

This PR adds per-connection Redis timeouts to both Redis and RedisCluster connection classes: two new protected properties (connectTimeout and readTimeout) and updated constructors (defaults: connectTimeout = 5, readTimeout = -1). Connections are created with connectTimeout, an initial OPT_READ_TIMEOUT is set, and a new blockingReadTimeout() computes per-call read timeouts (with a safety buffer and honoring readTimeout). All blocking Redis calls temporarily set OPT_READ_TIMEOUT, restore prior values in finally blocks, and invalidate the cached Redis instance on exceptions; non-blocking behavior is preserved.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding configurable connect and read timeouts to Redis connections. It is concise, specific, and clearly describes the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/redis-connection-timeouts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

protected ?\Redis $redis = null;

public function __construct(string $host, int $port = 6379, ?string $user = null, ?string $password = null)
public function __construct(string $host, int $port = 6379, ?string $user = null, ?string $password = null, float $connectTimeout = 5, float $readTimeout = 5)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this seconds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I meant by that we should also note it in the source

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Queue/Connection/Redis.php`:
- Around line 193-194: The Redis client is currently using a 5s OPT_READ_TIMEOUT
which can cause blocking calls to throw and leaves the broken connection reused;
change the default readTimeout to -1 (disable client-side socket timeout) and
update the blocking methods rightPop, leftPop, and rightPopLeftPush to: before
issuing a blocking command temporarily set \Redis::OPT_READ_TIMEOUT to at least
the requested $timeout (or disable if $timeout is -1), call the blocking method
inside a try/catch that catches RedisException, and in the catch set
$this->redis = null (so getRedis() will reconnect) and rethrow or translate the
error as appropriate; also ensure you restore the previous OPT_READ_TIMEOUT
value in a finally block so the option is always reset after the call.

In `@src/Queue/Connection/RedisCluster.php`:
- Line 188: The RedisCluster is constructed with a fixed readTimeout which can
abort blocking commands and corrupt the connection; modify the code so that
blocking methods (rightPop, leftPop, rightPopLeftPush) temporarily set Redis
option OPT_READ_TIMEOUT to max($timeout + 1, $this->readTimeout) before issuing
the blocking call and restore the original OPT_READ_TIMEOUT afterwards, and wrap
the call in a try/catch that nulls $this->redis on RedisException to force
reconnection (getRedis should tolerate a null and recreate the client);
additionally change the default constructor behavior for new \RedisCluster(...)
to use a readTimeout of -1 (infinite) instead of a small fixed value so callers
rely on per-call timeouts unless explicitly overridden.

lohanidamodar and others added 2 commits February 22, 2026 11:51
A fixed readTimeout on the connection caused brPop/blPop/bRPopLPush to
throw RedisException whenever the command's block duration exceeded the
socket read timeout, regardless of whether Redis responded correctly.

Changes:
- Default readTimeout to -1 (infinite) to preserve existing behavior;
  callers can opt in to a stricter timeout when needed (e.g. publishers)
- Before each blocking call, temporarily widen OPT_READ_TIMEOUT to
  max($timeout + 1, $this->readTimeout) so the socket never fires before
  Redis returns
- Restore the previous OPT_READ_TIMEOUT in a finally block
- On RedisException in a blocking call, null $this->redis so getRedis()
  reconnects on the next call instead of reusing a broken connection
- Deduplicate leftPopArray/rightPopArray to delegate to leftPop/rightPop

Applies to both Redis and RedisCluster connection classes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Queue/Connection/Redis.php`:
- Around line 244-245: getRedis() currently connects but never authenticates
using the stored constructor credentials ($user and $password), so either call
$this->redis->auth($this->password) or $this->redis->auth($this->user,
$this->password) immediately after connect() (and only when credentials are
present), and handle failures by throwing or logging an exception; update
getRedis() to perform this auth step (or remove the unused $user/$password
fields from the constructor if you prefer) so authentication is not silently
ignored.

Comment on lines +244 to +245
$this->redis->connect($this->host, $this->port, $this->connectTimeout);
$this->redis->setOption(\Redis::OPT_READ_TIMEOUT, $this->readTimeout);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if auth() is called anywhere else or if there's a base class handling it
rg -n 'auth\(' src/Queue/Connection/Redis.php
echo "---"
# Check if Connection interface or any parent class handles auth
rg -n 'auth' src/Queue/Connection/
echo "---"
# Check git history for auth in getRedis
git log --oneline -10 -- src/Queue/Connection/Redis.php

Repository: utopia-php/queue

Length of output: 150


🏁 Script executed:

# Let's examine the Redis.php file to see constructor and getRedis method
cat -n src/Queue/Connection/Redis.php | head -100

Repository: utopia-php/queue

Length of output: 4220


🏁 Script executed:

# Continue reading Redis.php to see the rest of the constructor and getRedis method
cat -n src/Queue/Connection/Redis.php | sed -n '100,280p'

Repository: utopia-php/queue

Length of output: 5685


🏁 Script executed:

# Check if there's a parent class or interface
rg -n 'class Redis|interface Connection' src/Queue/Connection/

Repository: utopia-php/queue

Length of output: 207


🏁 Script executed:

# Check the Connection interface definition
cat -n src/Queue/Connection.php

Repository: utopia-php/queue

Length of output: 1786


🏁 Script executed:

# Check if RedisCluster handles auth differently
rg -n 'auth' src/Queue/Connection/RedisCluster.php

Repository: utopia-php/queue

Length of output: 42


Call auth() or auth(password) in getRedis() to authenticate with stored credentials.

The constructor accepts and stores $user and $password (line 30), but getRedis() never calls the PHP Redis auth() method. This means credentials are accepted but silently ignored, bypassing authentication entirely. Either implement authentication by calling auth() in getRedis() after connecting, or remove the unused parameters from the constructor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Queue/Connection/Redis.php` around lines 244 - 245, getRedis() currently
connects but never authenticates using the stored constructor credentials ($user
and $password), so either call $this->redis->auth($this->password) or
$this->redis->auth($this->user, $this->password) immediately after connect()
(and only when credentials are present), and handle failures by throwing or
logging an exception; update getRedis() to perform this auth step (or remove the
unused $user/$password fields from the constructor if you prefer) so
authentication is not silently ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants