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  }