RedisCacheAdapter put, del and clear reject on a Redis outage, producing unhandled rejections
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- javascript, node.js, redis
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.
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.
- Dominant language
- JavaScript
- Stars
- 21.4k
- Forks
- 4.8k
- Avg merge
- 7h 45m
- Merged PRs (30d)
- 11
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.
More from parse-community/parse-server
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
parse-community/parse-server#10631 · 1 comment ·
-
/installations and /audiences reject a find sent as POST + _method=GET with "Invalid key name: 0" Open
Difficulty 2/5 Half a day Newbie friendliness 65/100
parse-community/parse-server#10626 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
parse-community/parse-server#10619 · 1 comment ·
-
type:meta
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
parse-community/parse-server#10613 · 2 comments ·
-
type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
parse-community/parse-server#10472 · 1 comment ·
All issues in parse-community/parse-server
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
HarperFast/skills#96 ·
-
[Block] Latest Posts [Type] Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
sugarlabs/musicblocks#8847 ·