Implement comprehensive WebSocket subscription stability fixes
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled

- Resolved critical issues causing subscriptions to drop after 30-60 seconds due to unconsumed receiver channels.
- Introduced per-subscription consumer goroutines to ensure continuous event delivery and prevent channel overflow.
- Enhanced REQ parsing to handle both wrapped and unwrapped filter arrays, eliminating EOF errors.
- Updated publisher logic to correctly send events to receiver channels, ensuring proper event delivery to subscribers.
- Added extensive documentation and testing tools to verify subscription stability and performance.
- Bumped version to v0.26.2 to reflect these significant improvements.
This commit is contained in:
2025-11-06 18:21:00 +00:00
parent d604341a27
commit 581e0ec588
23 changed files with 3054 additions and 81 deletions

View File

@@ -72,19 +72,20 @@ whitelist:
// Set read limit immediately after connection is established
conn.SetReadLimit(DefaultMaxMessageSize)
log.D.F("set read limit to %d bytes (%d MB) for %s", DefaultMaxMessageSize, DefaultMaxMessageSize/units.Mb, remote)
// Set initial read deadline - pong handler will extend it when pongs are received
conn.SetReadDeadline(time.Now().Add(DefaultPongWait))
// Add pong handler to extend read deadline when client responds to pings
conn.SetPongHandler(func(string) error {
log.T.F("received PONG from %s, extending read deadline", remote)
return conn.SetReadDeadline(time.Now().Add(DefaultPongWait))
})
defer conn.Close()
listener := &Listener{
ctx: ctx,
cancel: cancel,
Server: s,
conn: conn,
remote: remote,
@@ -94,6 +95,7 @@ whitelist:
writeDone: make(chan struct{}),
messageQueue: make(chan messageRequest, 100), // Buffered channel for message processing
processingDone: make(chan struct{}),
subscriptions: make(map[string]context.CancelFunc),
}
// Start write worker goroutine
@@ -131,12 +133,21 @@ whitelist:
defer func() {
log.D.F("closing websocket connection from %s", remote)
// Cancel all active subscriptions first
listener.subscriptionsMu.Lock()
for subID, cancelFunc := range listener.subscriptions {
log.D.F("cancelling subscription %s for %s", subID, remote)
cancelFunc()
}
listener.subscriptions = nil
listener.subscriptionsMu.Unlock()
// Cancel context and stop pinger
cancel()
ticker.Stop()
// Cancel all subscriptions for this connection
log.D.F("cancelling subscriptions for %s", remote)
// Cancel all subscriptions for this connection at publisher level
log.D.F("removing subscriptions from publisher for %s", remote)
listener.publishers.Receive(&W{
Cancel: true,
Conn: listener.conn,