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 }