code.vegaprotocol.io/vega@v0.79.0/libs/subscribers/stream_subscriber.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package subscribers 17 18 import ( 19 "context" 20 "sync" 21 22 "code.vegaprotocol.io/vega/core/events" 23 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 24 ) 25 26 type EventFilter func(events.Event) bool 27 28 type StreamEvent interface { 29 events.Event 30 StreamMessage() *eventspb.BusEvent 31 } 32 33 type MarketStreamEvent interface { 34 StreamEvent 35 StreamMarketMessage() *eventspb.BusEvent 36 } 37 38 type StreamSub struct { 39 *Base 40 mu *sync.Mutex // pointer because types is a value receiver, linter complains 41 types []events.Type 42 data []StreamEvent 43 filters []EventFilter 44 bufSize int 45 changeCount int 46 updated chan struct{} 47 marketEvtsOnly bool 48 } 49 50 // pass in requested batch size + expanded event types. 51 func getBufSize(batch int, types []events.Type) int { 52 if batch < 0 { 53 batch = 0 54 } 55 // subscribed to all 56 if len(types) == 0 { 57 // at least 2k buffer 58 if batch < 2000 { 59 return 2000 60 } 61 return batch 62 } 63 multipliers := 1 64 for _, t := range types { 65 // each one of these events are high volume, and ought to double the buffer size 66 switch t { 67 case events.TradeEvent, events.LedgerMovementsEvent, events.AccountEvent, events.OrderEvent: 68 multipliers++ 69 } 70 } 71 base := batch 72 if base == 0 { 73 base = 100 74 } 75 base *= len(types) * multipliers 76 // base less then 1k, but we have several multipliers (== high volume events), or more than 5 event types? 77 if base < 1000 && (multipliers > 1 || len(types) > 5) { 78 if multipliers > 1 { 79 return 500 * multipliers // 1k or more 80 } 81 return 1000 // 1k buffer 82 } 83 return base 84 } 85 86 func NewStreamSub(ctx context.Context, types []events.Type, batchSize int, filters ...EventFilter) *StreamSub { 87 // we can ignore this value throughout the call-chain, but internally we have to account for it 88 // this is equivalent to 0, but used for GQL mapping 89 if batchSize == -1 { 90 batchSize = 0 91 } 92 meo := len(types) == 1 && types[0] == events.MarketEvent 93 expandedTypes := make([]events.Type, 0, len(types)) 94 for _, t := range types { 95 if t == events.All { 96 expandedTypes = nil 97 break 98 } 99 if t == events.MarketEvent { 100 expandedTypes = append(expandedTypes, events.MarketEvents()...) 101 } else { 102 expandedTypes = append(expandedTypes, t) 103 } 104 } 105 bufLen := getBufSize(batchSize, expandedTypes) 106 cbuf := bufLen 107 if len(filters) > 0 { 108 // basically buffer length squared 109 cbuf += cbuf * len(filters) // double or tripple the buffer (len(filters) currently can be 0, 1, or 2) 110 } 111 s := &StreamSub{ 112 Base: NewBase(ctx, cbuf, false), 113 mu: &sync.Mutex{}, 114 types: expandedTypes, 115 data: make([]StreamEvent, 0, bufLen), // cap to batch size 116 filters: filters, 117 bufSize: batchSize, 118 updated: make(chan struct{}), // create a blocking channel for these 119 marketEvtsOnly: meo, 120 } 121 // running or not, we're using the channel 122 go s.loop(s.ctx) 123 return s 124 } 125 126 func (s *StreamSub) Halt() { 127 s.mu.Lock() 128 if s.changeCount == 0 || s.changeCount < s.bufSize { 129 select { 130 case <-s.updated: 131 default: 132 close(s.updated) 133 } 134 } 135 s.mu.Unlock() 136 s.Base.Halt() // close channel outside of the lock. to avoid race 137 } 138 139 func (s *StreamSub) loop(ctx context.Context) { 140 s.running = true // allow for Pause to work (ensures the pause channel can, and will be closed) 141 for { 142 select { 143 case <-ctx.Done(): 144 s.Halt() 145 return 146 case e, ok := <-s.ch: 147 // just return if closed, don't call Halt, because that would try to close s.ch a second time 148 if !ok { 149 return 150 } 151 s.Push(e...) 152 } 153 } 154 } 155 156 func (s *StreamSub) Push(evts ...events.Event) { 157 if len(evts) == 0 { 158 return 159 } 160 s.mu.Lock() 161 // update channel is eligible for closing if no events are in buffer, or the nr of changes are less than the buffer size 162 // closeUpdate := (s.changeCount == 0 || s.changeCount >= s.bufSize) 163 save := make([]StreamEvent, 0, len(evts)) 164 for _, e := range evts { 165 var se StreamEvent 166 if s.marketEvtsOnly { 167 // ensure we can get a market stream event from this 168 me, ok := e.(MarketStreamEvent) 169 if !ok { 170 continue 171 } 172 se = me 173 } else if ste, ok := e.(StreamEvent); ok { 174 se = ste 175 } else { 176 continue 177 } 178 keep := true 179 for _, f := range s.filters { 180 if !f(e) { 181 keep = false 182 break 183 } 184 } 185 if keep { 186 save = append(save, se) 187 } 188 } 189 s.changeCount += len(save) 190 s.data = append(s.data, save...) 191 if (s.bufSize > 0 && s.changeCount >= s.bufSize) || (s.bufSize == 0 && s.changeCount > 0) { 192 select { 193 case <-s.updated: 194 default: 195 close(s.updated) 196 } 197 // s.updated = make(chan struct{}) 198 } 199 s.mu.Unlock() 200 } 201 202 // UpdateBatchSize changes the batch size, and returns whatever the current buffer contains 203 // it's effectively a poll of current events ignoring requested batch size. 204 func (s *StreamSub) UpdateBatchSize(ctx context.Context, size int) []*eventspb.BusEvent { 205 s.mu.Lock() 206 if size == s.bufSize { 207 s.mu.Unlock() 208 // this is equivalent to polling for data again, wait for the buffer to be full and return 209 return s.GetData(ctx) 210 } 211 if len(s.data) == 0 { 212 s.changeCount = 0 213 if size != 0 { 214 s.bufSize = size 215 } 216 s.mu.Unlock() 217 return nil 218 } 219 s.changeCount = 0 220 data := make([]StreamEvent, len(s.data)) 221 copy(data, s.data) 222 dc := size 223 if dc == 0 { // size == 0 224 dc = cap(s.data) 225 } else if size != s.bufSize { // size was not 0, reassign bufSize 226 // buffer size changes 227 s.bufSize = size 228 } 229 s.data = make([]StreamEvent, 0, dc) 230 s.mu.Unlock() 231 messages := make([]*eventspb.BusEvent, 0, len(data)) 232 for _, d := range data { 233 if s.marketEvtsOnly { 234 e, ok := d.(MarketStreamEvent) 235 if ok { 236 messages = append(messages, e.StreamMarketMessage()) 237 } 238 } else { 239 messages = append(messages, d.StreamMessage()) 240 } 241 } 242 return messages 243 } 244 245 // GetData returns events from buffer, all if bufSize == 0, or max buffer size (rest are kept in data slice). 246 func (s *StreamSub) GetData(ctx context.Context) []*eventspb.BusEvent { 247 select { 248 case <-ctx.Done(): 249 // stream was closed 250 return nil 251 case <-s.updated: 252 s.mu.Lock() 253 // create new channel 254 s.updated = make(chan struct{}) 255 } 256 dl := len(s.data) 257 // this seems to happen with a buffer of 1 sometimes 258 // or could be an issue if s.updated was closed, but the UpdateBatchSize call acquired a lock first 259 if dl < s.bufSize || dl == 0 { 260 // data was drained (possibly UpdateBatchSize), so create new updated channel and carry on as if nothing happened 261 s.mu.Unlock() 262 return nil 263 } 264 s.changeCount = 0 265 c := s.bufSize 266 if c == 0 { 267 c = dl 268 } 269 // copy the data for return, clear the internal slice 270 data := make([]StreamEvent, c) 271 copy(data, s.data) 272 if s.bufSize == 0 { 273 // if we use s.data = s.data[:0] here, we get a data race somehow 274 s.data = s.data[:0] 275 } else if len(s.data) == s.bufSize { 276 s.data = s.data[:0] 277 } else { 278 s.data = s.data[s.bufSize:] // leave rest in the buffer 279 s.changeCount = len(s.data) // keep change count in sync with data slice 280 } 281 s.mu.Unlock() 282 messages := make([]*eventspb.BusEvent, 0, len(data)) 283 for _, d := range data { 284 if s.marketEvtsOnly { 285 e := d.(MarketStreamEvent) // we know this works already 286 messages = append(messages, e.StreamMessage()) 287 } else { 288 messages = append(messages, d.StreamMessage()) 289 } 290 } 291 return messages 292 } 293 294 func (s StreamSub) Types() []events.Type { 295 return s.types 296 }