mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
Merge pull request #1769 from tendermint/1755-possible-memory-leak
Memory leak in Websocket
This commit is contained in:
commit
c793a72ac5
@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.20.1
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
- [rpc] fix memory leak in Websocket (when using `/subscribe` method)
|
||||||
|
|
||||||
## 0.20.0
|
## 0.20.0
|
||||||
|
|
||||||
*June 6th, 2018*
|
*June 6th, 2018*
|
||||||
|
@ -156,6 +156,8 @@ func (s *Server) Subscribe(ctx context.Context, clientID string, query Query, ou
|
|||||||
if _, ok = s.subscriptions[clientID]; !ok {
|
if _, ok = s.subscriptions[clientID]; !ok {
|
||||||
s.subscriptions[clientID] = make(map[string]Query)
|
s.subscriptions[clientID] = make(map[string]Query)
|
||||||
}
|
}
|
||||||
|
// preserve original query
|
||||||
|
// see Unsubscribe
|
||||||
s.subscriptions[clientID][query.String()] = query
|
s.subscriptions[clientID][query.String()] = query
|
||||||
s.mtx.Unlock()
|
s.mtx.Unlock()
|
||||||
return nil
|
return nil
|
||||||
@ -314,6 +316,9 @@ func (state *state) remove(clientID string, q Query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(state.queries[q], clientID)
|
delete(state.queries[q], clientID)
|
||||||
|
if len(state.queries[q]) == 0 {
|
||||||
|
delete(state.queries, q)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,8 +333,10 @@ func (state *state) removeAll(clientID string) {
|
|||||||
close(ch)
|
close(ch)
|
||||||
|
|
||||||
delete(state.queries[q], clientID)
|
delete(state.queries[q], clientID)
|
||||||
|
if len(state.queries[q]) == 0 {
|
||||||
|
delete(state.queries, q)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(state.clients, clientID)
|
delete(state.clients, clientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/tendermint/go-amino"
|
amino "github.com/tendermint/go-amino"
|
||||||
types "github.com/tendermint/tendermint/rpc/lib/types"
|
types "github.com/tendermint/tendermint/rpc/lib/types"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
@ -613,11 +613,9 @@ func (wsc *wsConnection) readRoutine() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
|
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -684,20 +682,20 @@ func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error
|
|||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
|
|
||||||
// WebsocketManager is the main manager for all websocket connections.
|
// WebsocketManager provides a WS handler for incoming connections and passes a
|
||||||
// It holds the event switch and a map of functions for routing.
|
// map of functions along with any additional params to new connections.
|
||||||
// NOTE: The websocket path is defined externally, e.g. in node/node.go
|
// NOTE: The websocket path is defined externally, e.g. in node/node.go
|
||||||
type WebsocketManager struct {
|
type WebsocketManager struct {
|
||||||
websocket.Upgrader
|
websocket.Upgrader
|
||||||
|
|
||||||
funcMap map[string]*RPCFunc
|
funcMap map[string]*RPCFunc
|
||||||
cdc *amino.Codec
|
cdc *amino.Codec
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
wsConnOptions []func(*wsConnection)
|
wsConnOptions []func(*wsConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebsocketManager returns a new WebsocketManager that routes according to
|
// NewWebsocketManager returns a new WebsocketManager that passes a map of
|
||||||
// the given funcMap and connects to the server with the given connection
|
// functions, connection options and logger to new WS connections.
|
||||||
// options.
|
|
||||||
func NewWebsocketManager(funcMap map[string]*RPCFunc, cdc *amino.Codec, wsConnOptions ...func(*wsConnection)) *WebsocketManager {
|
func NewWebsocketManager(funcMap map[string]*RPCFunc, cdc *amino.Codec, wsConnOptions ...func(*wsConnection)) *WebsocketManager {
|
||||||
return &WebsocketManager{
|
return &WebsocketManager{
|
||||||
funcMap: funcMap,
|
funcMap: funcMap,
|
||||||
@ -718,7 +716,8 @@ func (wm *WebsocketManager) SetLogger(l log.Logger) {
|
|||||||
wm.logger = l
|
wm.logger = l
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketHandler upgrades the request/response (via http.Hijack) and starts the wsConnection.
|
// WebsocketHandler upgrades the request/response (via http.Hijack) and starts
|
||||||
|
// the wsConnection.
|
||||||
func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
wsConn, err := wm.Upgrade(w, r, nil)
|
wsConn, err := wm.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,15 +9,23 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/tendermint/go-amino"
|
amino "github.com/tendermint/go-amino"
|
||||||
rs "github.com/tendermint/tendermint/rpc/lib/server"
|
rs "github.com/tendermint/tendermint/rpc/lib/server"
|
||||||
types "github.com/tendermint/tendermint/rpc/lib/types"
|
types "github.com/tendermint/tendermint/rpc/lib/types"
|
||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// HTTP REST API
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// JSON-RPC over HTTP
|
||||||
|
|
||||||
func testMux() *http.ServeMux {
|
func testMux() *http.ServeMux {
|
||||||
funcMap := map[string]*rs.RPCFunc{
|
funcMap := map[string]*rs.RPCFunc{
|
||||||
"c": rs.NewRPCFunc(func(s string, i int) (string, error) { return "foo", nil }, "s,i"),
|
"c": rs.NewRPCFunc(func(s string, i int) (string, error) { return "foo", nil }, "s,i"),
|
||||||
@ -108,3 +116,44 @@ func TestUnknownRPCPath(t *testing.T) {
|
|||||||
// Always expecting back a 404 error
|
// Always expecting back a 404 error
|
||||||
require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
|
require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// JSON-RPC over WEBSOCKETS
|
||||||
|
|
||||||
|
func TestWebsocketManagerHandler(t *testing.T) {
|
||||||
|
s := newWSServer()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
// check upgrader works
|
||||||
|
d := websocket.Dialer{}
|
||||||
|
c, dialResp, err := d.Dial("ws://"+s.Listener.Addr().String()+"/websocket", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if got, want := dialResp.StatusCode, http.StatusSwitchingProtocols; got != want {
|
||||||
|
t.Errorf("dialResp.StatusCode = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check basic functionality works
|
||||||
|
req, err := types.MapToRequest(amino.NewCodec(), "TestWebsocketManager", "c", map[string]interface{}{"s": "a", "i": 10})
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = c.WriteJSON(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var resp types.RPCResponse
|
||||||
|
err = c.ReadJSON(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWSServer() *httptest.Server {
|
||||||
|
funcMap := map[string]*rs.RPCFunc{
|
||||||
|
"c": rs.NewWSRPCFunc(func(wsCtx types.WSRPCContext, s string, i int) (string, error) { return "foo", nil }, "s,i"),
|
||||||
|
}
|
||||||
|
wm := rs.NewWebsocketManager(funcMap, amino.NewCodec())
|
||||||
|
wm.SetLogger(log.TestingLogger())
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/websocket", wm.WebsocketHandler)
|
||||||
|
|
||||||
|
return httptest.NewServer(mux)
|
||||||
|
}
|
||||||
|
@ -7,7 +7,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tendermint/go-amino"
|
|
||||||
|
amino "github.com/tendermint/go-amino"
|
||||||
|
|
||||||
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/tendermint/tmlibs/log"
|
"github.com/tendermint/tmlibs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultCapacity = 1000
|
const defaultCapacity = 0
|
||||||
|
|
||||||
type EventBusSubscriber interface {
|
type EventBusSubscriber interface {
|
||||||
Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error
|
Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error
|
||||||
|
Loading…
x
Reference in New Issue
Block a user