github.com/number571/tendermint@v0.34.11-gost/libs/pubsub/pubsub.go (about) 1 // Package pubsub implements a pub-sub model with a single publisher (Server) 2 // and multiple subscribers (clients). 3 // 4 // Though you can have multiple publishers by sharing a pointer to a server or 5 // by giving the same channel to each publisher and publishing messages from 6 // that channel (fan-in). 7 // 8 // Clients subscribe for messages, which could be of any type, using a query. 9 // When some message is published, we match it with all queries. If there is a 10 // match, this message will be pushed to all clients, subscribed to that query. 11 // See query subpackage for our implementation. 12 // 13 // Example: 14 // 15 // q, err := query.New("account.name='John'") 16 // if err != nil { 17 // return err 18 // } 19 // ctx, cancel := context.WithTimeout(context.Background(), 1 * time.Second) 20 // defer cancel() 21 // subscription, err := pubsub.Subscribe(ctx, "johns-transactions", q) 22 // if err != nil { 23 // return err 24 // } 25 // 26 // for { 27 // select { 28 // case msg <- subscription.Out(): 29 // // handle msg.Data() and msg.Events() 30 // case <-subscription.Canceled(): 31 // return subscription.Err() 32 // } 33 // } 34 // 35 package pubsub 36 37 import ( 38 "context" 39 "errors" 40 "fmt" 41 42 "github.com/number571/tendermint/abci/types" 43 tmsync "github.com/number571/tendermint/internal/libs/sync" 44 "github.com/number571/tendermint/libs/pubsub/query" 45 "github.com/number571/tendermint/libs/service" 46 ) 47 48 type operation int 49 50 const ( 51 sub operation = iota 52 pub 53 unsub 54 shutdown 55 ) 56 57 var ( 58 // ErrSubscriptionNotFound is returned when a client tries to unsubscribe 59 // from not existing subscription. 60 ErrSubscriptionNotFound = errors.New("subscription not found") 61 62 // ErrAlreadySubscribed is returned when a client tries to subscribe twice or 63 // more using the same query. 64 ErrAlreadySubscribed = errors.New("already subscribed") 65 ) 66 67 // Query defines an interface for a query to be used for subscribing. A query 68 // matches against a map of events. Each key in this map is a composite of the 69 // even type and an attribute key (e.g. "{eventType}.{eventAttrKey}") and the 70 // values are the event values that are contained under that relationship. This 71 // allows event types to repeat themselves with the same set of keys and 72 // different values. 73 type Query interface { 74 Matches(events []types.Event) (bool, error) 75 String() string 76 } 77 78 type UnsubscribeArgs struct { 79 ID string 80 Subscriber string 81 Query Query 82 } 83 84 func (args UnsubscribeArgs) Validate() error { 85 if args.Subscriber == "" { 86 return errors.New("must specify a subscriber") 87 } 88 89 if args.ID == "" && args.Query == nil { 90 return fmt.Errorf("subscription is not fully defined [subscriber=%q]", args.Subscriber) 91 } 92 93 return nil 94 } 95 96 type cmd struct { 97 op operation 98 99 // subscribe, unsubscribe 100 query Query 101 subscription *Subscription 102 clientID string 103 104 // publish 105 msg interface{} 106 events []types.Event 107 } 108 109 // Server allows clients to subscribe/unsubscribe for messages, publishing 110 // messages with or without events, and manages internal state. 111 type Server struct { 112 service.BaseService 113 114 cmds chan cmd 115 cmdsCap int 116 117 // check if we have subscription before 118 // subscribing or unsubscribing 119 mtx tmsync.RWMutex 120 121 // subscriber -> [query->id (string) OR id->query (string))], 122 // track connections both by ID (new) and query (legacy) to 123 // avoid breaking the interface. 124 subscriptions map[string]map[string]string 125 } 126 127 // Option sets a parameter for the server. 128 type Option func(*Server) 129 130 // NewServer returns a new server. See the commentary on the Option functions 131 // for a detailed description of how to configure buffering. If no options are 132 // provided, the resulting server's queue is unbuffered. 133 func NewServer(options ...Option) *Server { 134 s := &Server{ 135 subscriptions: make(map[string]map[string]string), 136 } 137 s.BaseService = *service.NewBaseService(nil, "PubSub", s) 138 139 for _, option := range options { 140 option(s) 141 } 142 143 // if BufferCapacity option was not set, the channel is unbuffered 144 s.cmds = make(chan cmd, s.cmdsCap) 145 146 return s 147 } 148 149 // BufferCapacity allows you to specify capacity for the internal server's 150 // queue. Since the server, given Y subscribers, could only process X messages, 151 // this option could be used to survive spikes (e.g. high amount of 152 // transactions during peak hours). 153 func BufferCapacity(cap int) Option { 154 return func(s *Server) { 155 if cap > 0 { 156 s.cmdsCap = cap 157 } 158 } 159 } 160 161 // BufferCapacity returns capacity of the internal server's queue. 162 func (s *Server) BufferCapacity() int { 163 return s.cmdsCap 164 } 165 166 // Subscribe creates a subscription for the given client. 167 // 168 // An error will be returned to the caller if the context is canceled or if 169 // subscription already exist for pair clientID and query. 170 // 171 // outCapacity can be used to set a capacity for Subscription#Out channel (1 by 172 // default). Panics if outCapacity is less than or equal to zero. If you want 173 // an unbuffered channel, use SubscribeUnbuffered. 174 func (s *Server) Subscribe( 175 ctx context.Context, 176 clientID string, 177 query Query, 178 outCapacity ...int) (*Subscription, error) { 179 outCap := 1 180 if len(outCapacity) > 0 { 181 if outCapacity[0] <= 0 { 182 panic("Negative or zero capacity. Use SubscribeUnbuffered if you want an unbuffered channel") 183 } 184 outCap = outCapacity[0] 185 } 186 187 return s.subscribe(ctx, clientID, query, outCap) 188 } 189 190 // SubscribeUnbuffered does the same as Subscribe, except it returns a 191 // subscription with unbuffered channel. Use with caution as it can freeze the 192 // server. 193 func (s *Server) SubscribeUnbuffered(ctx context.Context, clientID string, query Query) (*Subscription, error) { 194 return s.subscribe(ctx, clientID, query, 0) 195 } 196 197 func (s *Server) subscribe(ctx context.Context, clientID string, query Query, outCapacity int) (*Subscription, error) { 198 s.mtx.RLock() 199 clientSubscriptions, ok := s.subscriptions[clientID] 200 if ok { 201 _, ok = clientSubscriptions[query.String()] 202 } 203 s.mtx.RUnlock() 204 if ok { 205 return nil, ErrAlreadySubscribed 206 } 207 208 subscription := NewSubscription(outCapacity) 209 select { 210 case s.cmds <- cmd{op: sub, clientID: clientID, query: query, subscription: subscription}: 211 s.mtx.Lock() 212 if _, ok = s.subscriptions[clientID]; !ok { 213 s.subscriptions[clientID] = make(map[string]string) 214 } 215 s.subscriptions[clientID][query.String()] = subscription.id 216 s.subscriptions[clientID][subscription.id] = query.String() 217 s.mtx.Unlock() 218 return subscription, nil 219 case <-ctx.Done(): 220 return nil, ctx.Err() 221 case <-s.Quit(): 222 return nil, nil 223 } 224 } 225 226 // Unsubscribe removes the subscription on the given query. An error will be 227 // returned to the caller if the context is canceled or if subscription does 228 // not exist. 229 func (s *Server) Unsubscribe(ctx context.Context, args UnsubscribeArgs) error { 230 if err := args.Validate(); err != nil { 231 return err 232 } 233 var qs string 234 if args.Query != nil { 235 qs = args.Query.String() 236 } 237 238 s.mtx.RLock() 239 clientSubscriptions, ok := s.subscriptions[args.Subscriber] 240 if args.ID != "" { 241 qs, ok = clientSubscriptions[args.ID] 242 243 if ok && args.Query == nil { 244 var err error 245 args.Query, err = query.New(qs) 246 if err != nil { 247 return err 248 } 249 } 250 } else if qs != "" { 251 args.ID, ok = clientSubscriptions[qs] 252 } 253 254 s.mtx.RUnlock() 255 if !ok { 256 return ErrSubscriptionNotFound 257 } 258 259 select { 260 case s.cmds <- cmd{op: unsub, clientID: args.Subscriber, query: args.Query, subscription: &Subscription{id: args.ID}}: 261 s.mtx.Lock() 262 263 delete(clientSubscriptions, args.ID) 264 delete(clientSubscriptions, qs) 265 266 if len(clientSubscriptions) == 0 { 267 delete(s.subscriptions, args.Subscriber) 268 } 269 s.mtx.Unlock() 270 return nil 271 case <-ctx.Done(): 272 return ctx.Err() 273 case <-s.Quit(): 274 return nil 275 } 276 } 277 278 // UnsubscribeAll removes all client subscriptions. An error will be returned 279 // to the caller if the context is canceled or if subscription does not exist. 280 func (s *Server) UnsubscribeAll(ctx context.Context, clientID string) error { 281 s.mtx.RLock() 282 _, ok := s.subscriptions[clientID] 283 s.mtx.RUnlock() 284 if !ok { 285 return ErrSubscriptionNotFound 286 } 287 288 select { 289 case s.cmds <- cmd{op: unsub, clientID: clientID}: 290 s.mtx.Lock() 291 delete(s.subscriptions, clientID) 292 s.mtx.Unlock() 293 return nil 294 case <-ctx.Done(): 295 return ctx.Err() 296 case <-s.Quit(): 297 return nil 298 } 299 } 300 301 // NumClients returns the number of clients. 302 func (s *Server) NumClients() int { 303 s.mtx.RLock() 304 defer s.mtx.RUnlock() 305 return len(s.subscriptions) 306 } 307 308 // NumClientSubscriptions returns the number of subscriptions the client has. 309 func (s *Server) NumClientSubscriptions(clientID string) int { 310 s.mtx.RLock() 311 defer s.mtx.RUnlock() 312 return len(s.subscriptions[clientID]) / 2 313 } 314 315 // Publish publishes the given message. An error will be returned to the caller 316 // if the context is canceled. 317 func (s *Server) Publish(ctx context.Context, msg interface{}) error { 318 return s.PublishWithEvents(ctx, msg, []types.Event{}) 319 } 320 321 // PublishWithEvents publishes the given message with the set of events. The set 322 // is matched with clients queries. If there is a match, the message is sent to 323 // the client. 324 func (s *Server) PublishWithEvents(ctx context.Context, msg interface{}, events []types.Event) error { 325 select { 326 case s.cmds <- cmd{op: pub, msg: msg, events: events}: 327 return nil 328 case <-ctx.Done(): 329 return ctx.Err() 330 case <-s.Quit(): 331 return nil 332 } 333 } 334 335 // OnStop implements Service.OnStop by shutting down the server. 336 func (s *Server) OnStop() { 337 s.cmds <- cmd{op: shutdown} 338 } 339 340 // NOTE: not goroutine safe 341 type state struct { 342 // query string -> client -> subscription 343 subscriptions map[string]map[string]*Subscription 344 // query string -> queryPlusRefCount 345 queries map[string]*queryPlusRefCount 346 } 347 348 // queryPlusRefCount holds a pointer to a query and reference counter. When 349 // refCount is zero, query will be removed. 350 type queryPlusRefCount struct { 351 q Query 352 refCount int 353 } 354 355 // OnStart implements Service.OnStart by starting the server. 356 func (s *Server) OnStart() error { 357 go s.loop(state{ 358 subscriptions: make(map[string]map[string]*Subscription), 359 queries: make(map[string]*queryPlusRefCount), 360 }) 361 return nil 362 } 363 364 // OnReset implements Service.OnReset 365 func (s *Server) OnReset() error { 366 return nil 367 } 368 369 func (s *Server) loop(state state) { 370 loop: 371 for cmd := range s.cmds { 372 switch cmd.op { 373 case unsub: 374 if cmd.query != nil { 375 state.remove(cmd.clientID, cmd.query.String(), cmd.subscription.id, ErrUnsubscribed) 376 } else { 377 state.removeClient(cmd.clientID, ErrUnsubscribed) 378 } 379 case shutdown: 380 state.removeAll(nil) 381 break loop 382 case sub: 383 state.add(cmd.clientID, cmd.query, cmd.subscription) 384 case pub: 385 if err := state.send(cmd.msg, cmd.events); err != nil { 386 s.Logger.Error("Error querying for events", "err", err) 387 } 388 } 389 } 390 } 391 392 func (state *state) add(clientID string, q Query, subscription *Subscription) { 393 qStr := q.String() 394 395 // initialize subscription for this client per query if needed 396 if _, ok := state.subscriptions[qStr]; !ok { 397 state.subscriptions[qStr] = make(map[string]*Subscription) 398 } 399 400 if _, ok := state.subscriptions[subscription.id]; !ok { 401 state.subscriptions[subscription.id] = make(map[string]*Subscription) 402 } 403 404 // create subscription 405 state.subscriptions[qStr][clientID] = subscription 406 state.subscriptions[subscription.id][clientID] = subscription 407 408 // initialize query if needed 409 if _, ok := state.queries[qStr]; !ok { 410 state.queries[qStr] = &queryPlusRefCount{q: q, refCount: 0} 411 } 412 // increment reference counter 413 state.queries[qStr].refCount++ 414 } 415 416 func (state *state) remove(clientID string, qStr, id string, reason error) { 417 clientSubscriptions, ok := state.subscriptions[qStr] 418 if !ok { 419 return 420 } 421 422 subscription, ok := clientSubscriptions[clientID] 423 if !ok { 424 return 425 } 426 427 subscription.cancel(reason) 428 429 // remove client from query map. 430 // if query has no other clients subscribed, remove it. 431 delete(state.subscriptions[qStr], clientID) 432 delete(state.subscriptions[id], clientID) 433 if len(state.subscriptions[qStr]) == 0 { 434 delete(state.subscriptions, qStr) 435 } 436 437 // decrease ref counter in queries 438 if ref, ok := state.queries[qStr]; ok { 439 ref.refCount-- 440 if ref.refCount == 0 { 441 // remove the query if nobody else is using it 442 delete(state.queries, qStr) 443 } 444 } 445 } 446 447 func (state *state) removeClient(clientID string, reason error) { 448 seen := map[string]struct{}{} 449 for qStr, clientSubscriptions := range state.subscriptions { 450 if sub, ok := clientSubscriptions[clientID]; ok { 451 if _, ok = seen[sub.id]; ok { 452 // all subscriptions are double indexed by ID and query, only 453 // process them once. 454 continue 455 } 456 state.remove(clientID, qStr, sub.id, reason) 457 seen[sub.id] = struct{}{} 458 } 459 } 460 } 461 462 func (state *state) removeAll(reason error) { 463 for qStr, clientSubscriptions := range state.subscriptions { 464 sub, ok := clientSubscriptions[qStr] 465 if !ok || ok && sub.id == qStr { 466 // all subscriptions are double indexed by ID and query, only 467 // process them once. 468 continue 469 } 470 471 for clientID := range clientSubscriptions { 472 state.remove(clientID, qStr, sub.id, reason) 473 } 474 } 475 } 476 477 func (state *state) send(msg interface{}, events []types.Event) error { 478 for qStr, clientSubscriptions := range state.subscriptions { 479 if sub, ok := clientSubscriptions[qStr]; ok && sub.id == qStr { 480 continue 481 } 482 var q Query 483 if qi, ok := state.queries[qStr]; ok { 484 q = qi.q 485 } else { 486 continue 487 } 488 489 match, err := q.Matches(events) 490 if err != nil { 491 return fmt.Errorf("failed to match against query %s: %w", q.String(), err) 492 } 493 494 if match { 495 for clientID, subscription := range clientSubscriptions { 496 if cap(subscription.out) == 0 { 497 // block on unbuffered channel 498 subscription.out <- NewMessage(subscription.id, msg, events) 499 } else { 500 // don't block on buffered channels 501 select { 502 case subscription.out <- NewMessage(subscription.id, msg, events): 503 default: 504 state.remove(clientID, qStr, subscription.id, ErrOutOfCapacity) 505 } 506 } 507 } 508 } 509 } 510 511 return nil 512 }