-
Notifications
You must be signed in to change notification settings - Fork 2.1k
limit number of /subscribe clients and queries per client #3269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
529ec9b
2aba9e3
f4552ef
f997475
6fdd3b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
"time" | ||
|
||
"github.com/pkg/errors" | ||
rpcserver "github.com/tendermint/tendermint/rpc/lib/server" | ||
) | ||
|
||
const ( | ||
|
@@ -323,6 +324,19 @@ type RPCConfig struct { | |
// Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} | ||
// 1024 - 40 - 10 - 50 = 924 = ~900 | ||
MaxOpenConnections int `mapstructure:"max_open_connections"` | ||
|
||
// Maximum number of unique clientIDs that can /subscribe | ||
// If you're using /broadcast_tx_commit, set to the estimated maximum number | ||
// of broadcast_tx_commit calls per block. | ||
MaxSubscriptionClients int `mapstructure:"max_subscription_clients"` | ||
|
||
// Maximum number of unique queries a given client can /subscribe to | ||
// If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set | ||
// to the estimated maximum number of broadcast_tx_commit calls per block. | ||
MaxSubscriptionsPerClient int `mapstructure:"max_subscriptions_per_client"` | ||
|
||
// How long to wait for a tx to be committed during /broadcast_tx_commit | ||
TimeoutBroadcastTxCommit time.Duration `mapstructure:"timeout_broadcast_tx_commit"` | ||
} | ||
|
||
// DefaultRPCConfig returns a default configuration for the RPC server | ||
|
@@ -337,6 +351,10 @@ func DefaultRPCConfig() *RPCConfig { | |
|
||
Unsafe: false, | ||
MaxOpenConnections: 900, | ||
|
||
MaxSubscriptionClients: 100, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this too low? |
||
MaxSubscriptionsPerClient: 5, | ||
TimeoutBroadcastTxCommit: 10 * time.Second, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Is there an explanation why these are good defaults? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, because these were picked quasi randomly.
|
||
} | ||
} | ||
|
||
|
@@ -358,6 +376,18 @@ func (cfg *RPCConfig) ValidateBasic() error { | |
if cfg.MaxOpenConnections < 0 { | ||
return errors.New("max_open_connections can't be negative") | ||
} | ||
if cfg.MaxSubscriptionClients < 0 { | ||
return errors.New("max_subscription_clients can't be negative") | ||
} | ||
if cfg.MaxSubscriptionsPerClient < 0 { | ||
return errors.New("max_subscriptions_per_client can't be negative") | ||
} | ||
if cfg.TimeoutBroadcastTxCommit < 0 { | ||
return errors.New("timeout_broadcast_tx_commit can't be negative") | ||
} | ||
if cfg.TimeoutBroadcastTxCommit > rpcserver.WriteTimeout { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need to handle this better but separate issue. |
||
return fmt.Errorf("timeout_broadcast_tx_commit can't be greater than rpc server's write timeout: %v", rpcserver.WriteTimeout) | ||
} | ||
return nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package proxy | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
amino "github.com/tendermint/go-amino" | ||
|
@@ -34,7 +35,12 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger, maxOpe | |
mux := http.NewServeMux() | ||
rpcserver.RegisterRPCFuncs(mux, r, cdc, logger) | ||
|
||
wm := rpcserver.NewWebsocketManager(r, cdc, rpcserver.EventSubscriber(c)) | ||
unsubscribeFromAllEvents := func(remoteAddr string) { | ||
if err := c.UnsubscribeAll(context.Background(), remoteAddr); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably should use some timeout here to avoid zombie connections. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if we blocked here forever, something is horribly wrong There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this is the sign pubsub does not need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this have to do with zombie connections? What could cause this to block? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If indexer (which's using |
||
logger.Error("Failed to unsubscribe from events", "err", err) | ||
} | ||
} | ||
wm := rpcserver.NewWebsocketManager(r, cdc, rpcserver.OnDisconnect(unsubscribeFromAllEvents)) | ||
wm.SetLogger(logger) | ||
core.SetLogger(logger) | ||
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) | ||
|
@@ -51,13 +57,11 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger, maxOpe | |
// | ||
// if we want security, the client must implement it as a secure client | ||
func RPCRoutes(c rpcclient.Client) map[string]*rpcserver.RPCFunc { | ||
|
||
return map[string]*rpcserver.RPCFunc{ | ||
// Subscribe/unsubscribe are reserved for websocket events. | ||
// We can just use the core tendermint impl, which uses the | ||
// EventSwitch we registered in NewWebsocketManager above | ||
"subscribe": rpcserver.NewWSRPCFunc(core.Subscribe, "query"), | ||
"unsubscribe": rpcserver.NewWSRPCFunc(core.Unsubscribe, "query"), | ||
"subscribe": rpcserver.NewWSRPCFunc(c.(Wrapper).SubscribeWS, "query"), | ||
"unsubscribe": rpcserver.NewWSRPCFunc(c.(Wrapper).UnsubscribeWS, "query"), | ||
"unsubscribe_all": rpcserver.NewWSRPCFunc(c.(Wrapper).UnsubscribeAllWS, ""), | ||
melekes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// info API | ||
"status": rpcserver.NewRPCFunc(c.Status, ""), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,16 @@ | ||
package proxy | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
cmn "github.com/tendermint/tendermint/libs/common" | ||
|
||
"github.com/tendermint/tendermint/crypto/merkle" | ||
"github.com/tendermint/tendermint/lite" | ||
rpcclient "github.com/tendermint/tendermint/rpc/client" | ||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | ||
rpctypes "github.com/tendermint/tendermint/rpc/lib/types" | ||
) | ||
|
||
var _ rpcclient.Client = Wrapper{} | ||
|
@@ -149,6 +153,55 @@ func (w Wrapper) RegisterOpDecoder(typ string, dec merkle.OpDecoder) { | |
w.prt.RegisterOpDecoder(typ, dec) | ||
} | ||
|
||
// SubscribeWS subscribes for events using the given query and remote address as | ||
// a subscriber, but does not verify responses (UNSAFE)! | ||
func (w Wrapper) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { | ||
out, err := w.Client.Subscribe(context.Background(), ctx.RemoteAddr(), query) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
go func() { | ||
for { | ||
select { | ||
case resultEvent := <-out: | ||
// XXX(melekes) We should have a switch here that performs a validation | ||
// depending on the event's type. | ||
ctx.WSConn.TryWriteRPCResponse( | ||
rpctypes.NewRPCSuccessResponse( | ||
ctx.WSConn.Codec(), | ||
rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", ctx.JSONReq.ID)), | ||
resultEvent, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this where above mentioned verification should happen? Before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. We should have a switch here that performs a validation depending on the event's type. |
||
)) | ||
case <-w.Client.Quit(): | ||
return | ||
} | ||
} | ||
}() | ||
|
||
return &ctypes.ResultSubscribe{}, nil | ||
} | ||
|
||
// UnsubscribeWS calls original client's Unsubscribe using remote address as a | ||
// subscriber. | ||
func (w Wrapper) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { | ||
err := w.Client.Unsubscribe(context.Background(), ctx.RemoteAddr(), query) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &ctypes.ResultUnsubscribe{}, nil | ||
} | ||
|
||
// UnsubscribeAllWS calls original client's UnsubscribeAll using remote address | ||
// as a subscriber. | ||
func (w Wrapper) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { | ||
err := w.Client.UnsubscribeAll(context.Background(), ctx.RemoteAddr()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &ctypes.ResultUnsubscribe{}, nil | ||
} | ||
|
||
// // WrappedSwitch creates a websocket connection that auto-verifies any info | ||
// // coming through before passing it along. | ||
// // | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ import ( | |
cmn "github.com/tendermint/tendermint/libs/common" | ||
dbm "github.com/tendermint/tendermint/libs/db" | ||
"github.com/tendermint/tendermint/libs/log" | ||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub" | ||
mempl "github.com/tendermint/tendermint/mempool" | ||
"github.com/tendermint/tendermint/p2p" | ||
"github.com/tendermint/tendermint/p2p/pex" | ||
|
@@ -658,6 +659,7 @@ func (n *Node) ConfigureRPC() { | |
rpccore.SetConsensusReactor(n.consensusReactor) | ||
rpccore.SetEventBus(n.eventBus) | ||
rpccore.SetLogger(n.Logger.With("module", "rpc")) | ||
rpccore.SetConfig(*n.config.RPC) | ||
} | ||
|
||
func (n *Node) startRPC() ([]net.Listener, error) { | ||
|
@@ -675,8 +677,15 @@ func (n *Node) startRPC() ([]net.Listener, error) { | |
for i, listenAddr := range listenAddrs { | ||
mux := http.NewServeMux() | ||
rpcLogger := n.Logger.With("module", "rpc-server") | ||
wm := rpcserver.NewWebsocketManager(rpccore.Routes, coreCodec, rpcserver.EventSubscriber(n.eventBus)) | ||
wm.SetLogger(rpcLogger.With("protocol", "websocket")) | ||
wmLogger := rpcLogger.With("protocol", "websocket") | ||
wm := rpcserver.NewWebsocketManager(rpccore.Routes, coreCodec, | ||
rpcserver.OnDisconnect(func(remoteAddr string) { | ||
err := n.eventBus.UnsubscribeAll(context.Background(), remoteAddr) | ||
melekes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil && err != tmpubsub.ErrSubscriptionNotFound { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes |
||
wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err) | ||
} | ||
})) | ||
wm.SetLogger(wmLogger) | ||
mux.HandleFunc("/websocket", wm.WebsocketHandler) | ||
rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,3 +129,9 @@ func testTxEventsSent(t *testing.T, broadcastMethod string) { | |
}) | ||
} | ||
} | ||
|
||
// Test HTTPClient resubscribes upon disconnect && subscription error. | ||
// Test Local client resubscribes upon subscription error. | ||
func TestClientsResubscribe(t *testing.T) { | ||
// TODO(melekes) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Open an issue for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} |
Uh oh!
There was an error while loading. Please reload this page.