github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/common/signal/pubsub/pubsub.go (about) 1 package pubsub 2 3 import ( 4 "errors" 5 "sync" 6 "time" 7 8 "github.com/xmplusdev/xmcore/common" 9 "github.com/xmplusdev/xmcore/common/signal/done" 10 "github.com/xmplusdev/xmcore/common/task" 11 ) 12 13 type Subscriber struct { 14 buffer chan interface{} 15 done *done.Instance 16 } 17 18 func (s *Subscriber) push(msg interface{}) { 19 select { 20 case s.buffer <- msg: 21 default: 22 } 23 } 24 25 func (s *Subscriber) Wait() <-chan interface{} { 26 return s.buffer 27 } 28 29 func (s *Subscriber) Close() error { 30 return s.done.Close() 31 } 32 33 func (s *Subscriber) IsClosed() bool { 34 return s.done.Done() 35 } 36 37 type Service struct { 38 sync.RWMutex 39 subs map[string][]*Subscriber 40 ctask *task.Periodic 41 } 42 43 func NewService() *Service { 44 s := &Service{ 45 subs: make(map[string][]*Subscriber), 46 } 47 s.ctask = &task.Periodic{ 48 Execute: s.Cleanup, 49 Interval: time.Second * 30, 50 } 51 return s 52 } 53 54 // Cleanup cleans up internal caches of subscribers. 55 // Visible for testing only. 56 func (s *Service) Cleanup() error { 57 s.Lock() 58 defer s.Unlock() 59 60 if len(s.subs) == 0 { 61 return errors.New("nothing to do") 62 } 63 64 for name, subs := range s.subs { 65 newSub := make([]*Subscriber, 0, len(s.subs)) 66 for _, sub := range subs { 67 if !sub.IsClosed() { 68 newSub = append(newSub, sub) 69 } 70 } 71 if len(newSub) == 0 { 72 delete(s.subs, name) 73 } else { 74 s.subs[name] = newSub 75 } 76 } 77 78 if len(s.subs) == 0 { 79 s.subs = make(map[string][]*Subscriber) 80 } 81 return nil 82 } 83 84 func (s *Service) Subscribe(name string) *Subscriber { 85 sub := &Subscriber{ 86 buffer: make(chan interface{}, 16), 87 done: done.New(), 88 } 89 s.Lock() 90 s.subs[name] = append(s.subs[name], sub) 91 s.Unlock() 92 common.Must(s.ctask.Start()) 93 return sub 94 } 95 96 func (s *Service) Publish(name string, message interface{}) { 97 s.RLock() 98 defer s.RUnlock() 99 100 for _, sub := range s.subs[name] { 101 if !sub.IsClosed() { 102 sub.push(message) 103 } 104 } 105 }