mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-03 10:32:05 +00:00
Rework the internal plumbing of the server. This change does not modify the
exported interfaces or semantics of the package, and all the existing tests
still pass.
The main changes here are to:
- Simplify the interface for subscription indexing with a typed index rather
than a single nested map.
- Ensure orderly shutdown of channels, so that there is no longer a dynamic
race with concurrent publishers & subscribers at shutdown.
- Remove a layer of indirection between publishers and subscribers. This mainly
helps legibility.
- Remove order dependencies between registration and delivery.
- Add documentation comments where they seemed helpful, and clarified the
existing comments where it was practical.
Although performance was not a primary goal of this change, the simplifications
did very slightly reduce memory use and increase throughput on the existing
benchmarks, though the delta is not statistically significant.
BENCHMARK BEFORE AFTER SPEEDUP (%) B/op (B) B/op (A)
Benchmark10Clients-12 5947 5566 6.4 2017 1942
Benchmark100Clients-12 6111 5762 5.7 1992 1910
Benchmark1000Clients-12 6983 6344 9.2 2046 1959
114 lines
3.3 KiB
Go
114 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|