[#1628] fixed realtime panic on concurrent clients iteration
This commit is contained in:
@@ -18,12 +18,19 @@ func NewBroker() *Broker {
|
||||
}
|
||||
}
|
||||
|
||||
// Clients returns all registered clients.
|
||||
// Clients returns a shallow copy of all registered clients indexed
|
||||
// with their connection id.
|
||||
func (b *Broker) Clients() map[string]Client {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
|
||||
return b.clients
|
||||
copy := make(map[string]Client, len(b.clients))
|
||||
|
||||
for id, c := range b.clients {
|
||||
copy[id] = c
|
||||
}
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
// ClientById finds a registered client by its id.
|
||||
@@ -56,9 +63,8 @@ func (b *Broker) Unregister(clientId string) {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
|
||||
// Note:
|
||||
// There is no need to explicitly close the client's channel since it will be GC-ed anyway.
|
||||
// Addinitionally, closing the channel explicitly could panic when there are several
|
||||
// subscriptions attached to the client that needs to receive the same event.
|
||||
delete(b.clients, clientId)
|
||||
if client, ok := b.clients[clientId]; ok {
|
||||
client.Discard()
|
||||
delete(b.clients, clientId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ func TestClients(t *testing.T) {
|
||||
b.Register(subscriptions.NewDefaultClient())
|
||||
b.Register(subscriptions.NewDefaultClient())
|
||||
|
||||
// check if it is a shallow copy
|
||||
clients := b.Clients()
|
||||
for k := range clients {
|
||||
delete(clients, k)
|
||||
}
|
||||
|
||||
// should return a new copy
|
||||
if total := len(b.Clients()); total != 2 {
|
||||
t.Fatalf("Expected 2 clients, got %v", total)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,16 @@ type Client interface {
|
||||
|
||||
// Get retrieves the key value from the client's context.
|
||||
Get(key string) any
|
||||
|
||||
// Discard marks the client as "discarded", meaning that it
|
||||
// shouldn't be used anymore for sending new messages.
|
||||
//
|
||||
// It is safe to call Discard() multiple times.
|
||||
Discard()
|
||||
|
||||
// IsDiscarded indicates whether the client has been "discarded"
|
||||
// and should no longer be used.
|
||||
IsDiscarded() bool
|
||||
}
|
||||
|
||||
// ensures that DefaultClient satisfies the Client interface
|
||||
@@ -45,6 +55,7 @@ var _ Client = (*DefaultClient)(nil)
|
||||
// DefaultClient defines a generic subscription client.
|
||||
type DefaultClient struct {
|
||||
mux sync.RWMutex
|
||||
isDiscarded bool
|
||||
id string
|
||||
store map[string]any
|
||||
channel chan Message
|
||||
@@ -63,11 +74,17 @@ func NewDefaultClient() *DefaultClient {
|
||||
|
||||
// Id implements the [Client.Id] interface method.
|
||||
func (c *DefaultClient) Id() string {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
|
||||
return c.id
|
||||
}
|
||||
|
||||
// Channel implements the [Client.Channel] interface method.
|
||||
func (c *DefaultClient) Channel() chan Message {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
|
||||
return c.channel
|
||||
}
|
||||
|
||||
@@ -139,3 +156,19 @@ func (c *DefaultClient) Set(key string, value any) {
|
||||
|
||||
c.store[key] = value
|
||||
}
|
||||
|
||||
// Discard implements the [Client.Discard] interface method.
|
||||
func (c *DefaultClient) Discard() {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
c.isDiscarded = true
|
||||
}
|
||||
|
||||
// IsDiscarded implements the [Client.IsDiscarded] interface method.
|
||||
func (c *DefaultClient) IsDiscarded() bool {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
|
||||
return c.isDiscarded
|
||||
}
|
||||
|
||||
@@ -129,3 +129,17 @@ func TestSetAndGet(t *testing.T) {
|
||||
t.Errorf("Expected 1, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscard(t *testing.T) {
|
||||
c := subscriptions.NewDefaultClient()
|
||||
|
||||
if v := c.IsDiscarded(); v {
|
||||
t.Fatal("Expected false, got true")
|
||||
}
|
||||
|
||||
c.Discard()
|
||||
|
||||
if v := c.IsDiscarded(); !v {
|
||||
t.Fatal("Expected true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user