Skip to content

redis - test: Stop the keep-alive test from racing the connect timeout - #2193

Merged
jaredwray merged 2 commits into
mainfrom
redis-test-keepalive-timeout
Oct 4, 2026
Merged

jaredwray merged 2 commits into
mainfrom
redis-test-keepalive-timeout

Conversation

@jaredwray

@jaredwray jaredwray commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Please check if the PR fulfills these requirements

  • Followed the Contributing and Code of Conduct guidelines.
  • Tests for the changes have been added (for bug fixes/features) with 100% code coverage. — test-only change

What kind of change does this PR introduce?

Test fix (flaky test). No source changes.

Problem

getClient > should keep a successful connection alive after connectionTimeout elapses connected to a real Redis with connectionTimeout: 50. The TCP connect and the handshake therefore had to beat a 50 ms timer. When every package's tests run in parallel in CI, they sometimes don't:

Error: Redis client is not connected or has failed to connect. This is thrown because throwOnConnectError is set to true.
 ❯ KeyvRedis.connectClient src/index.ts:440:11
 ❯ test/get-client.test.ts:705:18
Caused by: Error: Redis timed out after 50ms

It has failed this way twice today: in the codecov job on main at 6d4b270, and on the release PR #2192. The release workflow's test job runs the same suite, so this test can also block a release.

Fix

A bigger budget only makes a failure less likely. The first commit used 500 ms, and through a proxy that adds 250 ms to every chunk it still fails with Redis timed out after 500ms. Following #2182, the test now controls the clock it was racing instead of racing it:

  • Keyv times the connect with a JS setTimeout (raceWithTimeout). The test fakes setTimeout and clearTimeout while it connects, so the race can't time out however long the handshake takes.
  • The test then moves the clock past the timeout instead of sleeping. It runs in about 50 ms, down from 120 ms before and 600 ms with the first commit.
  • node-redis's TCP connectTimeout runs on Node's internal timers, which stay real. connectionTimeout is therefore 1 s, the window mono - test: Stop expiry tests from racing a 100 ms TTL #2182 gave the tests that use the server's clock. It only bounds the TCP connect, not the handshake.
  • node-redis only uses the global setTimeout for its ping timer, which is off unless pingInterval is set. Real timers come back before set/get, and a finally restores them and disconnects even if an assertion fails.

The test also checks that the same client is still open after the timeout. Before, a client torn down at the timeout went unnoticed, because the adapter reconnects on the next call. No retries were added (see AGENTS.md).

Verification

Scenario Original (50 ms) First commit (500 ms) This PR
Direct Redis pass pass pass, 10/10 runs
Proxy adding 40 ms per chunk fails (timed out after 50ms, the CI error) pass pass
Proxy adding 250 ms per chunk (handshake over 1 s) fails fails (timed out after 500ms) pass
Simulated bug: a timer destroys the client once the timeout elapses passes (misses it) passes (misses it) fails (client.isOpen is false)
  • The whole get-client.test.ts file passes (35/35).
  • biome check --error-on-warnings is clean.
  • tsc on the test file shows no new errors (0 before, 0 after).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2

"should keep a successful connection alive after connectionTimeout
elapses" connected to a real Redis with `connectionTimeout: 50`, so the
TCP connect and the handshake had 50 ms to finish. Under the parallel
test load in CI they sometimes take longer, the connect times out, and
the test fails with "Redis timed out after 50ms". That happened in the
codecov job on main at 6d4b270 and on #2192.

The test now uses 500 ms, the budget the other tests that connect to a
real server use, and waits 600 ms instead of 120 ms, so it still idles
past the timeout before it uses the connection.

Reproduced locally with a TCP proxy that adds 40 ms to every chunk: the
old test fails with the same error, and the new one passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-04T20:22:26.526815Z ea48518 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@jaredwray jaredwray mentioned this pull request Oct 4, 2026
8 of 10 tasks
@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (d0bc310) to head (59a9b5d).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2193   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           56        56           
  Lines         5802      5802           
  Branches       996       988    -8     
=========================================
  Hits          5802      5802           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

A 500 ms budget only made the keep-alive test less likely to fail: a
handshake that takes longer still loses the race. Through a proxy that
adds 250 ms to every chunk, the 500 ms version fails with "Redis timed
out after 500ms".

Keyv times the connect with a JS setTimeout, so the test now fakes
setTimeout and clearTimeout while it connects. The race can't time
out, however long the handshake takes, and the test moves the clock
past the timeout instead of sleeping, so it runs in about 50 ms instead
of 600 ms. node-redis's TCP connectTimeout runs on Node's internal
timers, which stay real, so connectionTimeout is now 1 s, the window
the other tests that use the server's clock got in #2182.

The test also checks that the same client is still open after the
timeout. Before, a client torn down when the timeout elapsed went
unnoticed, because the adapter reconnects on the next call. A timer
that destroys the client after the timeout now fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
@jaredwray jaredwray changed the title redis - test: Give the keep-alive test time to connect on a busy runner redis - test: Stop the keep-alive test from racing the connect timeout Oct 4, 2026
@jaredwray
jaredwray merged commit c86df8b into main Oct 4, 2026
16 checks passed
@jaredwray
jaredwray deleted the redis-test-keepalive-timeout branch October 4, 2026 20:37
jaredwray pushed a commit that referenced this pull request Oct 4, 2026
Brings in #2193, which this branch already carried as d16fffe and
538c8d0, so the release diff is the version bump and docs again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwt8ciN4uKrQkEM14TCff2
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