Skip to content

Revert "fix: redis pub/sub and streaming response issue"#6084

Merged
yau-wd merged 1 commit intomainfrom
revert-6008-fix/streaming
Mar 30, 2026
Merged

Revert "fix: redis pub/sub and streaming response issue"#6084
yau-wd merged 1 commit intomainfrom
revert-6008-fix/streaming

Conversation

@yau-wd
Copy link
Copy Markdown
Contributor

@yau-wd yau-wd commented Mar 30, 2026

Reverts #6008, accidentally merge into main.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Server-Sent Events (SSE) and Redis pub/sub implementation by removing several safety wrappers, cleanup routines, and the heartbeat mechanism. Feedback highlights critical stability and resource management concerns, specifically the removal of unsubscription logic in finally blocks which leads to Redis resource leaks. The reviewer also noted that the deletion of error handling around JSON parsing and response writing introduces risks of server crashes upon encountering malformed data or client disconnections. Furthermore, the removal of the heartbeat mechanism may result in dropped connections through proxies, and the elimination of centralized publishing helpers has caused significant code duplication and inconsistent logging practices.

Comment on lines 56 to 58
} finally {
if (isQueueMode && chatId) {
await getRunningExpressApp().redisSubscriber.unsubscribe(chatId)
}
sseStreamer.removeClient(chatId)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The logic to unsubscribe from the Redis channel in the finally block has been removed. This will lead to stale subscriptions on the Redis server, as the application subscribes to a channel for each streaming request but never unsubscribes. This is a resource leak that can impact server performance and stability over time. Please reintroduce the unsubscribe call.

Comment on lines 86 to 88
} finally {
if (isQueueMode && chatId) {
await getRunningExpressApp().redisSubscriber.unsubscribe(chatId)
}
sseStreamer.removeClient(chatId)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The logic to unsubscribe from the Redis channel in the finally block has been removed. This will lead to stale subscriptions on the Redis server, as the application subscribes to a channel for each streaming request but never unsubscribes. This is a resource leak that can impact server performance and stability over time. Please reintroduce the unsubscribe call.

this.sseStreamer.streamErrorEvent(chatId, err instanceof Error ? err.message : 'Failed to process stream event')
}
// Parse the message from Redis
const event = JSON.parse(message)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Parsing the message with JSON.parse without a try-catch block is risky. A malformed message received from Redis will throw an unhandled exception and crash the server process. Please wrap this in a try-catch block and log any parsing errors.

@@ -11,87 +11,66 @@ type Client = {
}

export class SSEStreamer implements IServerSideEventStreamer {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The heartbeat mechanism (startHeartbeat, stopHeartbeat) has been removed. This is crucial for keeping Server-Sent Events (SSE) connections alive, especially when they pass through proxies or load balancers with idle timeouts. Without heartbeats, client connections are likely to be dropped unexpectedly, leading to a poor user experience. This functionality should be restored.

Comment on lines 37 to 45
streamCustomEvent(chatId: string, eventType: string, data: any) {
const clientResponse = {
event: eventType,
data: data
const client = this.clients[chatId]
if (client) {
const clientResponse = {
event: eventType,
data: data
}
client.response.write('message:\ndata:' + JSON.stringify(clientResponse) + '\n\n')
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Calling client.response.write() directly without error handling is unsafe. If the client disconnects, this call will throw an unhandled exception and crash the server. The previous safeWrite implementation prevented this by wrapping the write operation in a try-catch block. Please reintroduce a safe writing mechanism to prevent server crashes. This issue applies to all stream... methods in this class.

Comment on lines +83 to 95
streamCustomEvent(chatId: string, eventType: string, data: any) {
try {
await this.redisPublisher.publish(channel, message)
this.redisPublisher.publish(
chatId,
JSON.stringify({
chatId,
eventType,
data
})
)
} catch (error) {
logger.error(`[RedisEventPublisher] Error publishing to channel ${channel}:`, { error })
console.error('Error streaming custom event:', error)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The removal of the safePublish helper method has led to duplicated try-catch blocks in every stream... method. This increases code duplication and makes maintenance harder. Furthermore, the check for this.redisPublisher.isReady before publishing is now missing, and errors are logged via console.error instead of the application's logger. It's recommended to re-introduce a private helper method to centralize publishing logic, including readiness checks, error handling, and consistent logging to improve readability and reduce future errors.

References
  1. Prioritize code readability and understandability over conciseness. A series of simple, chained operations or helper methods can be preferable to duplicated complex logic to reduce the potential for future errors.
  2. Use a default (fallback) implementation (like the application's standard logger) unless the specific implementation has meaningfully different behavior.

@yau-wd yau-wd merged commit 3ecb978 into main Mar 30, 2026
9 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.

2 participants