fix: absorb multiplex close bursts

This commit is contained in:
richjohnrambo
2026-10-02 15:53:03 +08:00
parent a406d63154
commit 80808349fb
4 changed files with 238 additions and 0 deletions
+45
View File
@@ -35,6 +35,11 @@ import (
// Maximum number of queued messages before session is considered stale and dropped.
const sendQueueLimit = 128
// Multiplex sessions may receive a short burst of presence updates while many
// remote connections are closing. Give the cluster drain a bounded chance to
// free one slot before treating the proxy as stuck.
const multiplexEnqueueWait = 250 * time.Millisecond
// Time given to a background session to terminate to avoid tiggering presence notifications.
// If session terminates (or unsubscribes from topic) in this time frame notifications are not sent at all.
const deferredNotificationsTimeout = time.Second * 5
@@ -171,6 +176,7 @@ type Session struct {
// Ensures that one multiplexing session is drained by at most one cluster
// write task at a time, preserving per-topic response order.
clusterWriteScheduled atomic.Bool
clusterQueueMu sync.Mutex
}
// Subscription is a mapper of sessions to topics.
@@ -190,6 +196,9 @@ type Subscription struct {
}
func (s *Session) enqueueOutbound(payload any) bool {
if s.isMultiplex() {
return s.enqueueMultiplexOutbound(payload, multiplexEnqueueWait)
}
if s.proto != WEBSOCK {
select {
case s.send <- payload:
@@ -212,6 +221,42 @@ func (s *Session) enqueueOutbound(payload any) bool {
}
}
func (s *Session) enqueueMultiplexOutbound(payload any, wait time.Duration) bool {
deadline := time.NewTimer(wait)
defer deadline.Stop()
retry := time.NewTicker(time.Millisecond)
defer retry.Stop()
drainScheduled := false
for {
s.clusterQueueMu.Lock()
if atomic.LoadInt32(&s.terminating) > 0 {
s.clusterQueueMu.Unlock()
return true
}
select {
case s.send <- payload:
s.clusterQueueMu.Unlock()
return true
default:
s.clusterQueueMu.Unlock()
}
if !drainScheduled {
s.scheduleClusterWriteLoop()
drainScheduled = true
}
select {
case <-retry.C:
case <-deadline.C:
s.clusterQueueMu.Lock()
terminating := atomic.LoadInt32(&s.terminating) > 0
s.clusterQueueMu.Unlock()
return terminating
}
}
}
func (s *Session) observeOutboundDequeue(purged bool) {
if s.proto != WEBSOCK {
return