|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|