Skip to content

fix: flaky tests and unhandled bus subscription rejection - #129

Merged
Julien-R44 merged 2 commits into
mainfrom
fix/flaky-tests
Nov 11, 2026
Merged

Julien-R44 merged 2 commits into
mainfrom
fix/flaky-tests

Conversation

@Julien-R44

Copy link
Copy Markdown
Owner

Summary

This PR fixes the intermittent failures of the tests job. I found two causes in the CI history (older logs have expired) and by running the suite in a loop.

1. Unhandled rejection Connection is closed. (bug in Bus)

The Bus constructor called this.#bus.subscribe(...) without awaiting or catching the returned promise. When a cache is disconnected before its Redis subscriber connection is ready, ioredis rejects the queued SUBSCRIBE with Connection is closed.. Nothing catches that rejection. Japa then fails the run after every test has passed:

Tests  517 passed, 2 skipped (519)
Unhandled Rejections─
ℹ Error: Connection is closed.

The same thing can crash an application that disconnects while Redis is unreachable. The rejection is now caught and logged. The new regression test disconnecting before the bus subscription is ready does not leave an unhandled rejection fails without the fix (expected [ Error: Connection is closed. ] to deeply equal []). A bentocache patch changeset is included.

2. Tests that wait exactly the TTL

isLogicallyExpired() checks Date.now() >= logicalExpiration. A setTimeout can fire up to one millisecond before Date.now() reaches that value. These four tests waited sleep(100) for a ttl: 100 and then expected the entry to be expired:

  • Two tier cache / should returns old value if factory throws and grace enabled. CI failed with Planned for 3 assertions, but ran 2.
  • Factory Context / can access graced entry. It failed locally.
  • Two tier cache / rethrows error when suppressL2Errors is false
  • One tier cache / soft timeout should returns old value if factory take too long

They now wait 150ms. The soft timeout test also waits 100ms instead of 50ms after the 550ms factory.

The two deleteByTag races in tagging.spec.ts were already fixed in #127.

Verification

I ran all non-driver test files against local Redis and Valkey, with CI=1 as in CI:

  • 10 runs without load: no failures.
  • 8 runs with all 8 CPUs saturated: no failures, no unhandled rejections.

Lint, formatting and typecheck pass.

Four tests waited exactly the ttl before expecting the entry to be expired.
Timers can fire up to a millisecond before Date.now() reaches the expiration,
so the entry was sometimes still fresh and the factory was not called. Wait
150ms for a 100ms ttl instead, and leave a margin after the slow factory in the
soft timeout test.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1cc89-88ff-735a-b0e6-546b99965804
The bus subscribed to its channel without awaiting or catching the promise.
When the cache was disconnected before the subscriber connection was ready,
ioredis rejected the pending SUBSCRIBE with "Connection is closed." and the
rejection was unhandled. This failed the test suite intermittently and can
crash an application. Log the error instead.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1cc89-88ff-735a-b0e6-546b99965804
@changeset-bot

changeset-bot Bot commented Nov 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: eee2a25

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
bentocache Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying bentocache with  Cloudflare Pages  Cloudflare Pages

Latest commit: eee2a25
Status:🚫  Build failed.

View logs

@Julien-R44
Julien-R44 merged commit 4fcb763 into main Nov 11, 2026
10 of 11 checks passed
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.

1 participant