github.com/searKing/golang/go@v1.2.117/sync/subject.go (about) 1 // Copyright 2022 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sync 6 7 import ( 8 "context" 9 "fmt" 10 "sync" 11 12 "github.com/searKing/golang/go/errors" 13 "github.com/searKing/golang/go/pragma" 14 "github.com/searKing/golang/go/sync/atomic" 15 ) 16 17 // Subject implements a condition variable like with channel, a rendezvous point 18 // for goroutines waiting for or announcing the occurrence 19 // of an event. 20 // 21 // The caller typically cannot assume that the condition is true when 22 // Subscribe chan returns. Instead, the caller should Wait in a loop: 23 // 24 // time.After(timeout, c.PublishBroadcast()) // for timeout or periodic event 25 // c.PublishBroadcast() // for async notify event directly 26 // eventC, cancel := c.Subscribe() 27 // for !condition() { 28 // select{ 29 // case event, closed := <- eventC: 30 // ... make use of event ... 31 // } 32 // } 33 // ... make use of condition ... 34 type Subject struct { 35 noCopy pragma.DoNotCopy 36 37 mu sync.Mutex 38 subscribers map[*subscriber]struct{} 39 40 inShutdown atomic.Bool // true when when server is in shutdown 41 } 42 43 type subscriber struct { 44 mu sync.Mutex // guard close of channel msgC 45 msgC chan any 46 47 once sync.Once 48 doneC chan struct{} // closed when subscriber is in shutdown, like removed. 49 } 50 51 func (s *subscriber) Shutdown() { 52 if s == nil { 53 return 54 } 55 s.once.Do(func() { 56 close(s.doneC) 57 s.mu.Lock() 58 defer s.mu.Unlock() 59 close(s.msgC) 60 }) 61 } 62 63 // publish wakes a listener waiting on c to consume the event. 64 // event will be dropped if ctx is Done before event is received. 65 func (s *subscriber) publish(ctx context.Context, event any) error { 66 // guard of msgC's close 67 s.mu.Lock() 68 defer s.mu.Unlock() 69 select { 70 case <-ctx.Done(): 71 // event dropped because of publisher 72 return ctx.Err() 73 case <-s.doneC: 74 // event dropped because of subscriber 75 return fmt.Errorf("event dropped because of subscriber unsubscribed") 76 default: 77 } 78 79 select { 80 case <-ctx.Done(): 81 // event dropped because of publisher 82 return ctx.Err() 83 case <-s.doneC: 84 // event dropped because of subscriber 85 return fmt.Errorf("event dropped because of subscriber unsubscribed") 86 case s.msgC <- event: 87 // event consumed 88 return nil 89 } 90 } 91 92 // Subscribe returns a channel that's closed when awoken by PublishSignal or PublishBroadcast. 93 // never be canceled. Successive calls to Subscribe return different values. 94 // The close of the Subscribe channel may happen asynchronously, 95 // after the cancel function returns. 96 func (s *Subject) Subscribe() (<-chan any, context.CancelFunc) { 97 listener := &subscriber{ 98 msgC: make(chan any), 99 doneC: make(chan struct{}), 100 } 101 s.trackChannel(listener, true) 102 return listener.msgC, func() { 103 s.trackChannel(listener, false) 104 } 105 } 106 107 // PublishSignal wakes one listener waiting on c, if there is any. 108 // PublishSignal blocks until event is received or dropped. 109 func (s *Subject) PublishSignal(ctx context.Context, event any) error { 110 s.mu.Lock() 111 defer s.mu.Unlock() 112 var wg sync.WaitGroup 113 var errs []error 114 for listener := range s.subscribers { 115 wg.Add(1) 116 go func(listener *subscriber) { 117 defer wg.Done() 118 err := listener.publish(ctx, event) 119 if err != nil { 120 errs = append(errs, err) 121 } 122 }(listener) 123 break 124 } 125 wg.Wait() 126 return errors.Multi(errs...) 127 } 128 129 // PublishBroadcast wakes all listeners waiting on c. 130 // PublishBroadcast blocks until event is received or dropped. 131 // event will be dropped if ctx is Done before event is received. 132 func (s *Subject) PublishBroadcast(ctx context.Context, event any) error { 133 var wg sync.WaitGroup 134 var errs []error 135 func() { 136 s.mu.Lock() 137 defer s.mu.Unlock() 138 for listener := range s.subscribers { 139 wg.Add(1) 140 go func(listener *subscriber) { 141 defer wg.Done() 142 err := listener.publish(ctx, event) 143 if err != nil { 144 errs = append(errs, err) 145 } 146 }(listener) 147 } 148 }() 149 wg.Wait() 150 return errors.Multi(errs...) 151 } 152 153 func (s *Subject) trackChannel(c *subscriber, add bool) { 154 func() { 155 s.mu.Lock() 156 defer s.mu.Unlock() 157 if s.subscribers == nil { 158 s.subscribers = make(map[*subscriber]struct{}) 159 } 160 _, has := s.subscribers[c] 161 if has { 162 if add { 163 return 164 } 165 delete(s.subscribers, c) 166 return 167 } 168 if add { 169 s.subscribers[c] = struct{}{} 170 } 171 }() 172 if !add { 173 c.Shutdown() 174 } 175 }