rpc: add subscription id to events (#6386)

Addresses  #3931
This commit is contained in:
Sam Kleinman
2021-06-15 17:33:47 +00:00
committed by GitHub
parent 8d0c38257e
commit 886519e3ca
12 changed files with 221 additions and 70 deletions
+29 -9
View File
@@ -52,7 +52,13 @@ type wsEvents struct {
ws *jsonrpcclient.WSClient
mtx tmsync.RWMutex
subscriptions map[string]chan ctypes.ResultEvent // query -> chan
subscriptions map[string]*wsSubscription
}
type wsSubscription struct {
res chan ctypes.ResultEvent
id string
query string
}
var _ rpcclient.EventsClient = (*wsEvents)(nil)
@@ -70,7 +76,7 @@ func newWsEvents(remote string, wso WSOptions) (*wsEvents, error) {
}
w := &wsEvents{
subscriptions: make(map[string]chan ctypes.ResultEvent),
subscriptions: make(map[string]*wsSubscription),
}
w.BaseService = *service.NewBaseService(nil, "wsEvents", w)
@@ -136,10 +142,10 @@ func (w *wsEvents) Subscribe(ctx context.Context, subscriber, query string,
outc := make(chan ctypes.ResultEvent, outCap)
w.mtx.Lock()
defer w.mtx.Unlock()
// subscriber param is ignored because Tendermint will override it with
// remote IP anyway.
w.subscriptions[query] = outc
w.mtx.Unlock()
w.subscriptions[query] = &wsSubscription{res: outc, query: query}
return outc, nil
}
@@ -158,9 +164,12 @@ func (w *wsEvents) Unsubscribe(ctx context.Context, subscriber, query string) er
}
w.mtx.Lock()
_, ok := w.subscriptions[query]
info, ok := w.subscriptions[query]
if ok {
delete(w.subscriptions, query)
if info.id != "" {
delete(w.subscriptions, info.id)
}
delete(w.subscriptions, info.query)
}
w.mtx.Unlock()
@@ -181,7 +190,7 @@ func (w *wsEvents) UnsubscribeAll(ctx context.Context, subscriber string) error
}
w.mtx.Lock()
w.subscriptions = make(map[string]chan ctypes.ResultEvent)
w.subscriptions = make(map[string]*wsSubscription)
w.mtx.Unlock()
return nil
@@ -196,7 +205,11 @@ func (w *wsEvents) redoSubscriptionsAfter(d time.Duration) {
w.mtx.Lock()
defer w.mtx.Unlock()
for q := range w.subscriptions {
for q, info := range w.subscriptions {
if q != "" && q == info.id {
continue
}
err := w.ws.Subscribe(ctx, q)
if err != nil {
w.Logger.Error("failed to resubscribe", "query", q, "err", err)
@@ -240,10 +253,17 @@ func (w *wsEvents) eventListener() {
w.mtx.RLock()
out, ok := w.subscriptions[result.Query]
if ok {
if _, idOk := w.subscriptions[result.SubscriptionID]; !idOk {
out.id = result.SubscriptionID
w.subscriptions[result.SubscriptionID] = out
}
}
w.mtx.RUnlock()
if ok {
select {
case out <- *result:
case out.res <- *result:
case <-w.Quit():
return
}
+17 -4
View File
@@ -248,7 +248,13 @@ func (c *Local) eventsRoutine(
for {
select {
case msg := <-sub.Out():
result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
result := ctypes.ResultEvent{
SubscriptionID: msg.SubscriptionID(),
Query: q.String(),
Data: msg.Data(),
Events: msg.Events(),
}
if cap(outc) == 0 {
outc <- result
} else {
@@ -293,11 +299,18 @@ func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscript
}
func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
q, err := tmquery.New(query)
args := tmpubsub.UnsubscribeArgs{Subscriber: subscriber}
var err error
args.Query, err = tmquery.New(query)
if err != nil {
return fmt.Errorf("failed to parse query: %w", err)
// if this isn't a valid query it might be an ID, so
// we'll try that. It'll turn into an error when we
// try to unsubscribe. Eventually, perhaps, we'll want
// to change the interface to only allow
// unsubscription by ID, but that's a larger change.
args.ID = query
}
return c.EventBus.Unsubscribe(ctx, subscriber, q)
return c.EventBus.Unsubscribe(ctx, args)
}
func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
+10 -6
View File
@@ -86,13 +86,17 @@ func (env *Environment) Subscribe(ctx *rpctypes.Context, query string) (*ctypes.
// Unsubscribe from events via WebSocket.
// More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe
func (env *Environment) Unsubscribe(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
addr := ctx.RemoteAddr()
env.Logger.Info("Unsubscribe from query", "remote", addr, "query", query)
q, err := tmquery.New(query)
args := tmpubsub.UnsubscribeArgs{Subscriber: ctx.RemoteAddr()}
env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", query)
var err error
args.Query, err = tmquery.New(query)
if err != nil {
return nil, fmt.Errorf("failed to parse query: %w", err)
args.ID = query
}
err = env.EventBus.Unsubscribe(context.Background(), addr, q)
err = env.EventBus.Unsubscribe(ctx.Context(), args)
if err != nil {
return nil, err
}
@@ -104,7 +108,7 @@ func (env *Environment) Unsubscribe(ctx *rpctypes.Context, query string) (*ctype
func (env *Environment) UnsubscribeAll(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
addr := ctx.RemoteAddr()
env.Logger.Info("Unsubscribe from all", "remote", addr)
err := env.EventBus.UnsubscribeAll(context.Background(), addr)
err := env.EventBus.UnsubscribeAll(ctx.Context(), addr)
if err != nil {
return nil, err
}
+3 -1
View File
@@ -8,6 +8,7 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
mempl "github.com/tendermint/tendermint/internal/mempool"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
"github.com/tendermint/tendermint/types"
@@ -77,7 +78,8 @@ func (env *Environment) BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*
return nil, err
}
defer func() {
if err := env.EventBus.Unsubscribe(context.Background(), subscriber, q); err != nil {
args := tmpubsub.UnsubscribeArgs{Subscriber: subscriber, Query: q}
if err := env.EventBus.Unsubscribe(context.Background(), args); err != nil {
env.Logger.Error("Error unsubscribing from eventBus", "err", err)
}
}()
+4 -3
View File
@@ -258,7 +258,8 @@ type (
// Event data from a subscription
type ResultEvent struct {
Query string `json:"query"`
Data types.TMEventData `json:"data"`
Events map[string][]string `json:"events"`
SubscriptionID string `json:"subscription_id"`
Query string `json:"query"`
Data types.TMEventData `json:"data"`
Events map[string][]string `json:"events"`
}