package pubsub import "github.com/tendermint/tendermint/abci/types" // An item to be published to subscribers. type item struct { Data interface{} Events []types.Event } // A subInfo value records a single subscription. type subInfo struct { clientID string // chosen by the client query Query // chosen by the client subID string // assigned at registration sub *Subscription // receives published events } // A subInfoSet is an unordered set of subscription info records. type subInfoSet map[*subInfo]struct{} func (s subInfoSet) contains(si *subInfo) bool { _, ok := s[si]; return ok } func (s subInfoSet) add(si *subInfo) { s[si] = struct{}{} } func (s subInfoSet) remove(si *subInfo) { delete(s, si) } // withQuery returns the subset of s whose query string matches qs. func (s subInfoSet) withQuery(qs string) subInfoSet { out := make(subInfoSet) for si := range s { if si.query.String() == qs { out.add(si) } } return out } // A subIndex is an indexed collection of subscription info records. // The index is not safe for concurrent use without external synchronization. type subIndex struct { all subInfoSet // all subscriptions byClient map[string]subInfoSet // per-client subscriptions byQuery map[string]subInfoSet // per-query subscriptions // TODO(creachadair): We allow indexing by query to support existing use by // the RPC service methods for event streaming. Fix up those methods not to // require this, and then remove indexing by query. } // newSubIndex constructs a new, empty subscription index. func newSubIndex() *subIndex { return &subIndex{ all: make(subInfoSet), byClient: make(map[string]subInfoSet), byQuery: make(map[string]subInfoSet), } } // findClients returns the set of subscriptions for the given client ID, or nil. func (idx *subIndex) findClientID(id string) subInfoSet { return idx.byClient[id] } // findQuery returns the set of subscriptions on the given query string, or nil. func (idx *subIndex) findQuery(qs string) subInfoSet { return idx.byQuery[qs] } // contains reports whether idx contains any subscription matching the given // client ID and query pair. func (idx *subIndex) contains(clientID, query string) bool { csubs, qsubs := idx.byClient[clientID], idx.byQuery[query] if len(csubs) == 0 || len(qsubs) == 0 { return false } for si := range csubs { if qsubs.contains(si) { return true } } return false } // add adds si to the index, replacing any previous entry with the same terms. // It is the caller's responsibility to check for duplicates before adding. // See also the contains method. func (idx *subIndex) add(si *subInfo) { idx.all.add(si) if m := idx.byClient[si.clientID]; m == nil { idx.byClient[si.clientID] = subInfoSet{si: struct{}{}} } else { m.add(si) } qs := si.query.String() if m := idx.byQuery[qs]; m == nil { idx.byQuery[qs] = subInfoSet{si: struct{}{}} } else { m.add(si) } } // removeAll removes all the elements of s from the index. func (idx *subIndex) removeAll(s subInfoSet) { for si := range s { idx.all.remove(si) idx.byClient[si.clientID].remove(si) if len(idx.byClient[si.clientID]) == 0 { delete(idx.byClient, si.clientID) } if si.query != nil { qs := si.query.String() idx.byQuery[qs].remove(si) if len(idx.byQuery[qs]) == 0 { delete(idx.byQuery, qs) } } } }