mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Document websocket lifecycle synchronization
This commit is contained in:
@@ -268,9 +268,13 @@ initial-client delivery, and explicit client data requests; cancel delayed or
|
||||
queued work when its client leaves; and join every state producer before
|
||||
closing client channels. Reconnect, request, or invalidation churn must not
|
||||
multiply concurrent clones of that canonical payload or race shutdown sends
|
||||
against channel closure. Ordinary unregister and slow-client eviction must use
|
||||
the same synchronized send/close ownership; recovering a send-on-closed panic
|
||||
is not a substitute for a race-free channel lifecycle.
|
||||
against channel closure. A client's lifecycle cancellation signal must be
|
||||
initialized and read through one synchronized owner before registration,
|
||||
requested-state work, delayed initial-state delivery, or disconnect may race
|
||||
over it; every path must observe the same stable signal. Ordinary unregister
|
||||
and slow-client eviction must use the same synchronized send/close ownership;
|
||||
recovering a send-on-closed panic is not a substitute for a race-free channel
|
||||
lifecycle.
|
||||
|
||||
Docker and Podman app-container CPU payloads expose two API facts with
|
||||
different meanings: canonical resource metrics and `/api/metrics-store/history`
|
||||
|
||||
@@ -35,6 +35,47 @@ func TestClientSafeSendAndCloseAreSynchronized(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLifecycleSignalConcurrentInitializationAndClose(t *testing.T) {
|
||||
client := &Client{}
|
||||
start := make(chan struct{})
|
||||
signals := make([]chan struct{}, 32)
|
||||
var workers sync.WaitGroup
|
||||
|
||||
for i := range signals {
|
||||
workers.Add(1)
|
||||
go func(index int) {
|
||||
defer workers.Done()
|
||||
<-start
|
||||
signals[index] = client.lifecycleSignal()
|
||||
}(i)
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
workers.Add(1)
|
||||
go func() {
|
||||
defer workers.Done()
|
||||
<-start
|
||||
client.closeLifecycle()
|
||||
}()
|
||||
}
|
||||
|
||||
close(start)
|
||||
workers.Wait()
|
||||
|
||||
for i, signal := range signals {
|
||||
if signal == nil {
|
||||
t.Fatalf("lifecycle signal %d was nil", i)
|
||||
}
|
||||
if signal != signals[0] {
|
||||
t.Fatalf("lifecycle signal %d did not share the client lifecycle", i)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-signals[0]:
|
||||
default:
|
||||
t.Fatal("lifecycle signal remained open after closeLifecycle")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHubDisconnectedClientsDoNotBuildInitialState(t *testing.T) {
|
||||
var stateBuilds atomic.Int64
|
||||
hub := NewHub(func(string) interface{} {
|
||||
|
||||
Reference in New Issue
Block a user