fix: absorb multiplex close bursts
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user