parse-community / parse-community/parse-server
RedisCacheAdapter put, del and clear reject on a Redis outage, producing unhandled rejections
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 21.4k
- Forks
- 4.8k
- Avg merge
- 7h 45m
- Merged PRs (30d)
- 11
Description
New Issue Checklist
- Report security issues confidentially.
- Any contribution is under this license.
- Before posting search existing issues.
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
- Configure Parse Server with
RedisCacheAdapter. - Make Redis unavailable, for example stop the server or point the adapter at a closed port.
- 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/Adapters/Cache/RedisCacheAdapter.js by reading get alongside put, del, and clear, then run the standalone rejecting-client reproduction from the issue. Check the six non-awaited call sites in src/Auth.js and src/RestWrite.js. Done means Redis failures are logged with the operation name and these methods resolve without unhandled rejections.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, redis
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100