code.vegaprotocol.io/vega@v0.79.0/core/datasource/spec/engine_subscribers.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 spec
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/datasource"
    25  	"code.vegaprotocol.io/vega/core/datasource/common"
    26  )
    27  
    28  // OnMatchedData describes the callback function used when an data dource data matches the spec.
    29  type OnMatchedData func(ctx context.Context, data common.Data) error
    30  
    31  // SpecPredicate describes the predicate used to filter the subscribers.
    32  // When returning true, all the subscribers associated to the matching
    33  // Spec are collected.
    34  // The order between specs and subscribers is preserved.
    35  type SpecPredicate func(spec Spec) (bool, error)
    36  
    37  // SubscriptionPredicate describes the predicate used to check if any
    38  // of the currently existing subscriptions expects the public keys inside
    39  // the incoming Spec object.
    40  type SubscriptionPredicate func(spec Spec) bool
    41  
    42  // SubscriptionID is a unique identifier referencing the subscription of an
    43  // OnMatchedData to a Spec.
    44  type SubscriptionID uint64
    45  
    46  // Unsubscriber is a closure that is created at subscription step in order to
    47  // provide the ability to unsubscribe at any conveninent moment.
    48  type Unsubscriber func(context.Context, SubscriptionID)
    49  
    50  // updatedSubscription wraps all useful information about an updated
    51  // subscription.
    52  type updatedSubscription struct {
    53  	subscriptionID  SubscriptionID
    54  	spec            datasource.Spec
    55  	specActivatedAt time.Time
    56  }
    57  
    58  // filterResult describes the result of the filter operation.
    59  type filterResult struct {
    60  	// specIDs lists all the Spec ID that matched the filter
    61  	// predicate.
    62  	specIDs []SpecID
    63  	// subscribers list all the subscribers associated to the matched Spec.
    64  	subscribers []OnMatchedData
    65  }
    66  
    67  // hasMatched returns true if filter has matched the predicated.
    68  func (r filterResult) hasMatched() bool {
    69  	return len(r.specIDs) > 0
    70  }
    71  
    72  // specSubscriptions wraps the subscribers (in form of OnMatchedData) to
    73  // the Spec.
    74  type specSubscriptions struct {
    75  	mu sync.RWMutex
    76  
    77  	lastSubscriptionID SubscriptionID
    78  	subscriptions      []*specSubscription
    79  	// subscriptionsMatrix maps a SubscriptionID to a SpecID to speed up
    80  	// the retrieval of the OnMatchedData into the subscriptions.
    81  	subscriptionsMatrix map[SubscriptionID]SpecID
    82  }
    83  
    84  // newSpecSubscriptions initialises the subscription handler.
    85  func newSpecSubscriptions() *specSubscriptions {
    86  	return &specSubscriptions{
    87  		subscriptions:       []*specSubscription{},
    88  		subscriptionsMatrix: map[SubscriptionID]SpecID{},
    89  	}
    90  }
    91  
    92  // hasAnySubscribers checks if any of the subscriptions contains public keys that
    93  // match the given ones by the predicate.
    94  // Returns fast on the first match.
    95  func (s *specSubscriptions) hasAnySubscribers(predicate SubscriptionPredicate) bool {
    96  	s.mu.RLock()
    97  	defer s.mu.RUnlock()
    98  
    99  	for _, subscription := range s.subscriptions {
   100  		if predicate(subscription.spec) {
   101  			return true
   102  		}
   103  	}
   104  
   105  	return false
   106  }
   107  
   108  // filterSubscribers collects the subscribers that match the predicate on the Spec.
   109  // The order between specs and subscribers is preserved.
   110  func (s *specSubscriptions) filterSubscribers(predicate SpecPredicate) (*filterResult, error) {
   111  	s.mu.RLock()
   112  	defer s.mu.RUnlock()
   113  
   114  	result := &filterResult{
   115  		specIDs:     []SpecID{},
   116  		subscribers: []OnMatchedData{},
   117  	}
   118  
   119  	for _, subscription := range s.subscriptions {
   120  		matched, err := predicate(subscription.spec)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		if !matched {
   125  			continue
   126  		}
   127  		result.specIDs = append(result.specIDs, subscription.spec.id)
   128  		for _, subscriber := range subscription.subscribers {
   129  			result.subscribers = append(result.subscribers, subscriber.cb)
   130  		}
   131  	}
   132  	return result, nil
   133  }
   134  
   135  // getSubscription returns the subscription associated to the given SpecID.  Returns the updates subscription and
   136  // true if this is the first subscription to the spec.
   137  func (s *specSubscriptions) addSubscriber(spec Spec, cb OnMatchedData, tm time.Time) (updatedSubscription, bool) {
   138  	s.mu.Lock()
   139  	defer s.mu.Unlock()
   140  
   141  	firstSubscription := false
   142  	_, subscription := s.getSubscription(spec.id)
   143  	if subscription == nil {
   144  		firstSubscription = true
   145  		subscription = s.createSubscription(spec, tm)
   146  	}
   147  
   148  	subscriptionID := s.nextSubscriptionID()
   149  	subscription.addSubscriber(subscriptionID, cb)
   150  
   151  	s.subscriptionsMatrix[subscriptionID] = spec.id
   152  
   153  	return updatedSubscription{
   154  		subscriptionID:  subscriptionID,
   155  		specActivatedAt: subscription.specActivatedAt,
   156  		spec:            *spec.OriginalSpec,
   157  	}, firstSubscription
   158  }
   159  
   160  func (s *specSubscriptions) removeSubscriber(subscriptionID SubscriptionID) (updatedSubscription, bool) {
   161  	s.mu.Lock()
   162  	defer s.mu.Unlock()
   163  
   164  	specID, ok := s.subscriptionsMatrix[subscriptionID]
   165  	if !ok {
   166  		panic(fmt.Sprintf("unknown subscriber ID %d", subscriptionID))
   167  	}
   168  
   169  	index, subscription := s.getSubscription(specID)
   170  	subscription.removeSubscriber(subscriptionID)
   171  
   172  	delete(s.subscriptionsMatrix, subscriptionID)
   173  
   174  	hasNoMoreSubscriber := subscription.hasNoMoreSubscriber()
   175  	if hasNoMoreSubscriber {
   176  		s.removeSubscriptionFromIndex(index)
   177  	}
   178  
   179  	return updatedSubscription{
   180  		subscriptionID:  subscriptionID,
   181  		specActivatedAt: subscription.specActivatedAt,
   182  		spec:            *subscription.spec.OriginalSpec,
   183  	}, hasNoMoreSubscriber
   184  }
   185  
   186  // Internal usage.
   187  func (s *specSubscriptions) removeSubscriptionFromIndex(index int) {
   188  	copy(s.subscriptions[index:], s.subscriptions[index+1:])
   189  	lastIndex := len(s.subscriptions) - 1
   190  	s.subscriptions[lastIndex] = nil
   191  	s.subscriptions = s.subscriptions[:lastIndex]
   192  }
   193  
   194  // Internal usage.
   195  func (s *specSubscriptions) createSubscription(spec Spec, tm time.Time) *specSubscription {
   196  	subscription := newSpecSubscription(spec, tm)
   197  	s.subscriptions = append(s.subscriptions, subscription)
   198  	return subscription
   199  }
   200  
   201  // Internal usage.
   202  func (s *specSubscriptions) getSubscription(id SpecID) (int, *specSubscription) {
   203  	for i, subscription := range s.subscriptions {
   204  		if subscription.spec.id == id {
   205  			return i, subscription
   206  		}
   207  	}
   208  	return -1, nil
   209  }
   210  
   211  // nextSubscriptionID computes the next SubscriptionID
   212  // Internal usage.
   213  func (s *specSubscriptions) nextSubscriptionID() SubscriptionID {
   214  	s.lastSubscriptionID++
   215  	return s.lastSubscriptionID
   216  }
   217  
   218  // specSubscription groups all OnMatchedData callbacks by Spec.
   219  type specSubscription struct {
   220  	spec            Spec
   221  	specActivatedAt time.Time
   222  	subscribers     []*specSubscriber
   223  }
   224  
   225  type specSubscriber struct {
   226  	id SubscriptionID
   227  	cb OnMatchedData
   228  }
   229  
   230  // Internal usage.
   231  func newSpecSubscription(spec Spec, activationTime time.Time) *specSubscription {
   232  	return &specSubscription{
   233  		spec:            spec,
   234  		specActivatedAt: activationTime,
   235  		subscribers:     []*specSubscriber{},
   236  	}
   237  }
   238  
   239  // Internal usage.
   240  func (s *specSubscription) addSubscriber(id SubscriptionID, cb OnMatchedData) {
   241  	s.subscribers = append(s.subscribers, &specSubscriber{
   242  		id: id,
   243  		cb: cb,
   244  	})
   245  }
   246  
   247  // Internal usage.
   248  func (s *specSubscription) removeSubscriber(id SubscriptionID) {
   249  	index, _ := s.getSubscriber(id)
   250  	s.removeSubscriberFromIndex(index)
   251  }
   252  
   253  // hasNoMoreSubscriber returns true if there is no subscriber for the associated
   254  // Spec, false otherwise.
   255  // Internal usage.
   256  func (s *specSubscription) hasNoMoreSubscriber() bool {
   257  	return len(s.subscribers) == 0
   258  }
   259  
   260  // Internal usage.
   261  func (s *specSubscription) getSubscriber(id SubscriptionID) (int, *specSubscriber) {
   262  	for i, subscriber := range s.subscribers {
   263  		if subscriber.id == id {
   264  			return i, subscriber
   265  		}
   266  	}
   267  	return -1, nil
   268  }
   269  
   270  // Internal usage.
   271  func (s *specSubscription) removeSubscriberFromIndex(index int) {
   272  	copy(s.subscribers[index:], s.subscribers[index+1:])
   273  	lastIndex := len(s.subscribers) - 1
   274  	s.subscribers[lastIndex] = nil
   275  	s.subscribers = s.subscribers[:lastIndex]
   276  }