github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/pubsub/pubsub.go (about) 1 // Copyright (c) 2015-2024 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package pubsub 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "fmt" 24 "sync" 25 "sync/atomic" 26 ) 27 28 // GetByteBuffer returns a byte buffer from the pool. 29 var GetByteBuffer = func() []byte { 30 return make([]byte, 0, 4096) 31 } 32 33 // Sub - subscriber entity. 34 type Sub[T Maskable] struct { 35 ch chan T 36 types Mask 37 filter func(entry T) bool 38 } 39 40 // PubSub holds publishers and subscribers 41 type PubSub[T Maskable, M Maskable] struct { 42 // atomics, keep at top: 43 types uint64 44 numSubscribers int32 45 maxSubscribers int32 46 47 // not atomics: 48 subs []*Sub[T] 49 sync.RWMutex 50 } 51 52 // Publish message to the subscribers. 53 // Note that publish is always non-blocking send so that we don't block on slow receivers. 54 // Hence receivers should use buffered channel so as not to miss the published events. 55 func (ps *PubSub[T, M]) Publish(item T) { 56 ps.RLock() 57 defer ps.RUnlock() 58 for _, sub := range ps.subs { 59 if sub.types.Contains(Mask(item.Mask())) && (sub.filter == nil || sub.filter(item)) { 60 select { 61 case sub.ch <- item: 62 default: 63 } 64 } 65 } 66 } 67 68 // Subscribe - Adds a subscriber to pubsub system 69 func (ps *PubSub[T, M]) Subscribe(mask M, subCh chan T, doneCh <-chan struct{}, filter func(entry T) bool) error { 70 totalSubs := atomic.AddInt32(&ps.numSubscribers, 1) 71 if ps.maxSubscribers > 0 && totalSubs > ps.maxSubscribers { 72 atomic.AddInt32(&ps.numSubscribers, -1) 73 return fmt.Errorf("the limit of `%d` subscribers is reached", ps.maxSubscribers) 74 } 75 ps.Lock() 76 defer ps.Unlock() 77 78 sub := &Sub[T]{ch: subCh, types: Mask(mask.Mask()), filter: filter} 79 ps.subs = append(ps.subs, sub) 80 81 // We hold a lock, so we are safe to update 82 combined := Mask(atomic.LoadUint64(&ps.types)) 83 combined.Merge(Mask(mask.Mask())) 84 atomic.StoreUint64(&ps.types, uint64(combined)) 85 86 go func() { 87 <-doneCh 88 89 ps.Lock() 90 defer ps.Unlock() 91 var remainTypes Mask 92 for i, s := range ps.subs { 93 if s == sub { 94 ps.subs = append(ps.subs[:i], ps.subs[i+1:]...) 95 } else { 96 remainTypes.Merge(s.types) 97 } 98 } 99 atomic.StoreUint64(&ps.types, uint64(remainTypes)) 100 atomic.AddInt32(&ps.numSubscribers, -1) 101 }() 102 103 return nil 104 } 105 106 // SubscribeJSON - Adds a subscriber to pubsub system and returns results with JSON encoding. 107 func (ps *PubSub[T, M]) SubscribeJSON(mask M, subCh chan<- []byte, doneCh <-chan struct{}, filter func(entry T) bool, wg *sync.WaitGroup) error { 108 totalSubs := atomic.AddInt32(&ps.numSubscribers, 1) 109 if ps.maxSubscribers > 0 && totalSubs > ps.maxSubscribers { 110 atomic.AddInt32(&ps.numSubscribers, -1) 111 return fmt.Errorf("the limit of `%d` subscribers is reached", ps.maxSubscribers) 112 } 113 ps.Lock() 114 defer ps.Unlock() 115 subChT := make(chan T, 10000) 116 sub := &Sub[T]{ch: subChT, types: Mask(mask.Mask()), filter: filter} 117 ps.subs = append(ps.subs, sub) 118 119 // We hold a lock, so we are safe to update 120 combined := Mask(atomic.LoadUint64(&ps.types)) 121 combined.Merge(Mask(mask.Mask())) 122 atomic.StoreUint64(&ps.types, uint64(combined)) 123 if wg != nil { 124 wg.Add(1) 125 } 126 go func() { 127 defer func() { 128 if wg != nil { 129 wg.Done() 130 } 131 // Clean up and de-register the subscriber 132 ps.Lock() 133 defer ps.Unlock() 134 var remainTypes Mask 135 for i, s := range ps.subs { 136 if s == sub { 137 ps.subs = append(ps.subs[:i], ps.subs[i+1:]...) 138 } else { 139 remainTypes.Merge(s.types) 140 } 141 } 142 atomic.StoreUint64(&ps.types, uint64(remainTypes)) 143 atomic.AddInt32(&ps.numSubscribers, -1) 144 }() 145 146 // Read from subChT and write to subCh 147 var buf bytes.Buffer 148 enc := json.NewEncoder(&buf) 149 for { 150 select { 151 case <-doneCh: 152 return 153 case v, ok := <-subChT: 154 if !ok { 155 return 156 } 157 buf.Reset() 158 err := enc.Encode(v) 159 if err != nil { 160 return 161 } 162 163 select { 164 case subCh <- append(GetByteBuffer()[:0], buf.Bytes()...): 165 continue 166 case <-doneCh: 167 return 168 } 169 } 170 } 171 }() 172 173 return nil 174 } 175 176 // NumSubscribers returns the number of current subscribers, 177 // The mask is checked against the active subscribed types, 178 // and 0 will be returned if nobody is subscribed for the type(s). 179 func (ps *PubSub[T, M]) NumSubscribers(mask M) int32 { 180 types := Mask(atomic.LoadUint64(&ps.types)) 181 if !types.Overlaps(Mask(mask.Mask())) { 182 return 0 183 } 184 return atomic.LoadInt32(&ps.numSubscribers) 185 } 186 187 // Subscribers returns the number of current subscribers for all types. 188 func (ps *PubSub[T, M]) Subscribers() int32 { 189 return atomic.LoadInt32(&ps.numSubscribers) 190 } 191 192 // New inits a PubSub system with a limit of maximum 193 // subscribers unless zero is specified 194 func New[T Maskable, M Maskable](maxSubscribers int32) *PubSub[T, M] { 195 return &PubSub[T, M]{maxSubscribers: maxSubscribers} 196 }