Skip to content

RedisCacheAdapter put, del and clear reject on a Redis outage, producing unhandled rejections #10634

Description

@AdrianCurtin

New Issue Checklist

Issue Description

RedisCacheAdapter#get catches adapter errors, logs them and resolves. put, del and clear do not, so they reject when Redis is unavailable. Parse Server calls all three without awaiting them in six places, which turns a transient Redis failure into an unhandled promise rejection.

// src/Adapters/Cache/RedisCacheAdapter.js
async get(key) {
  try {
    
  } catch (err) {
    logger.error('RedisCacheAdapter error on get', { error: err });   // handled
  }
}

async del(key) {
  await this.queue.enqueue(key);
  return this.client.del(key);                                        // rejects
}

The call sites that do not await, all of which sit on hot paths:

Location Call Runs on
src/Auth.js:140 cacheController.user.del(sessionToken) every expired-session auth
src/Auth.js:203 cacheController.user.put(sessionToken, …) every session-token auth
src/Auth.js:342 cacheController.role.put(user.id, …) every role-closure computation
src/Auth.js:350 cacheController.role.del(user.id) clearRoleCache
src/Auth.js:351 cacheController.user.del(sessionToken) clearRoleCache
src/RestWrite.js:1566 cacheController.role.clear() every _Role write

src/RestWrite.js:775 is not affected, its call is returned inside a promise chain.

Not awaiting is the correct design in each case, since a cache write must not delay or fail the request that triggered it. The defect is that the adapter rejects at all, given that its own get establishes the opposite contract.

Depending on the Node version and process configuration, an unhandled rejection either logs a warning or terminates the process, so a brief Redis outage during normal traffic can take down an otherwise healthy server. The failure also arrives with no context about which cache operation produced it, because there is no handler to name it.

This was raised in review on #10620 and fixed there for the one new call site that PR adds, with a .catch() at the call site. Fixing it in the adapter covers the six pre-existing sites at once, and any future caller.

Steps to reproduce

  1. Configure Parse Server with RedisCacheAdapter.
  2. Make Redis unavailable, for example stop the server or point the adapter at a closed port.
  3. Authenticate with a session token, or save a _Role.

A standalone reproduction, no Redis required, since a client that always rejects is what an outage looks like to the adapter:

const RedisCacheAdapter = require('parse-server/lib/Adapters/Cache/RedisCacheAdapter').default;

const cache = new RedisCacheAdapter(null, 100);
cache.client = {
  set: () => Promise.reject(new Error('Redis is unavailable')),
  del: () => Promise.reject(new Error('Redis is unavailable')),
  sendCommand: () => Promise.reject(new Error('Redis is unavailable')),
};

process.on('unhandledRejection', reason => console.log('unhandled:', reason.message));

cache.put('k', 'v');   // not awaited, as in Auth.js
cache.del('k');
cache.clear();

Actual Outcome

unhandled: Redis is unavailable
unhandled: Redis is unavailable
unhandled: Redis is unavailable

Expected Outcome

put, del and clear behave like get: the error is logged and the promise resolves. No unhandled rejection, and the log names the operation that failed.

Environment

Server

  • Parse Server version: 9.10.1-alpha.6
  • Operating system: macOS 15.5
  • Local or remote host: local

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 8.0
  • Local or remote host: local

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): not applicable, server-internal cache
  • SDK version: not applicable

Logs

Nothing is logged for the failing operation. The only output is the runtime's unhandled rejection warning.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions