Fix unprotected JSON.parse in Redis.getObject() crashing viewer stats processing#7537
Fix unprotected JSON.parse in Redis.getObject() crashing viewer stats processing#7537lawrence3699 wants to merge 1 commit intoChocobozzz:developfrom
Conversation
… processing Wrap JSON.parse in getObject() with try/catch and add a null guard in processViewerStats() so corrupted Redis entries are skipped and cleaned up instead of crashing the entire batch job.
There was a problem hiding this comment.
Pull request overview
Defensive hardening of viewer stats batch processing so a corrupted Redis entry doesn’t crash processViewerStats() and block persistence of subsequent viewer stats.
Changes:
- Add
try/catcharoundJSON.parseinRedis.getObject()and returnnullon parse failure. - Skip and delete invalid viewer stats Redis keys during
processViewerStats()when stats cannot be loaded. - Add warning logs for parse failures / invalid viewer stats entries.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| server/core/lib/stats/shared/video-viewer-stats.ts | Skip/delete invalid viewer stats entries during batch processing to prevent aborting the whole loop. |
| server/core/lib/redis.ts | Catch JSON.parse failures in getObject() and log/return null instead of throwing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if (!stats) { | ||
| logger.warn('Cannot read viewer stats for Redis key %s, removing invalid entry.', key, lTags()) | ||
| await this.deleteLocalVideoViewersKeys(key) |
There was a problem hiding this comment.
The new invalid-entry path deletes the Redis key outside the per-key try/catch. If deleteLocalVideoViewersKeys throws (Redis connectivity, permission, etc.), it will be caught by the outer catch and abort the entire for loop, reintroducing the “one bad entry stops the batch” behavior. Consider wrapping the delete in its own try/catch (log and continue), or reusing the inner per-key try/catch around the invalid-entry cleanup as well.
| await this.deleteLocalVideoViewersKeys(key) | |
| try { | |
| await this.deleteLocalVideoViewersKeys(key) | |
| } catch (err) { | |
| logger.error('Cannot delete invalid viewer stats for Redis key %s.', key, { err, ...lTags() }) | |
| } |
| @@ -156,6 +156,12 @@ export class VideoViewerStats { | |||
| for (const key of allKeys) { | |||
| const stats: LocalViewerStats = await this.getLocalVideoViewerByKey(key) | |||
There was a problem hiding this comment.
stats can now legitimately be null (e.g. parse failure in Redis.getObject()), but it’s still typed as LocalViewerStats here. Updating the return types of getLocalVideoViewerByKey/getLocalVideoViewer (and this local variable) to LocalViewerStats | null would make the null-handling explicit and prevent accidental property access elsewhere without a guard.
| const stats: LocalViewerStats = await this.getLocalVideoViewerByKey(key) | |
| const stats: LocalViewerStats | null = await this.getLocalVideoViewerByKey(key) |
| try { | ||
| return JSON.parse(value) | ||
| } catch (err) { | ||
| logger.warn('Cannot parse Redis key %s.', key, { err }) |
There was a problem hiding this comment.
This new warning log in redis.ts omits the file’s standard lTags() metadata (used by other logs in this module). Adding { err, ...lTags() } (or similar) will keep Redis logs consistently taggable/filterable.
| logger.warn('Cannot parse Redis key %s.', key, { err }) | |
| logger.warn('Cannot parse Redis key %s.', key, { err, ...lTags() }) |
Description
Noticed while reading through the viewer stats processing code that
Redis.getObject()callsJSON.parse()without any error handling. On a self-hosted instance, if a Redis value gets corrupted (partial write during a crash, data from a different PeerTube version, manual Redis intervention), the unhandled parse error propagates up throughgetLocalVideoViewerByKey()inprocessViewerStats().Since
getLocalVideoViewerByKey()is called outside the inner try/catch block (line 157, before the transaction at line 164), the exception is caught by the outer catch at line 181 — which exits the entireforloop. This means a single corrupted entry blocks all remaining viewer stats from being persisted to the database.Minimal reproduction:
The same codebase already handles this correctly in
server/core/models/server/plugin.ts(lines 209–213):Before: One corrupted Redis viewer stats entry crashes
processViewerStats(), preventing all remaining entries from being saved to the database.After: Corrupted entries are logged, cleaned up from Redis, and skipped. The remaining entries are processed normally.
Changes
redis.ts: WrapJSON.parseingetObject()with try/catch, log warning, return nullvideo-viewer-stats.ts: Add null check inprocessViewerStats()loop — log, delete the invalid key, and continueRelated issues
Has this been tested?
JSON.parseand the fix returns null insteadplugin.tsScreenshots
N/A