Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit ee4d6f7

Browse files
authored
Merge pull request #94 from binance-chain/v0.30.1-binance.6-beta.1
[R4R] prepare Release 0.30.1-binance.7
2 parents 332351c + 958dee6 commit ee4d6f7

6 files changed

Lines changed: 208 additions & 46 deletions

File tree

config/config.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,22 @@ type RPCConfig struct {
348348
// Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
349349
// 1024 - 40 - 10 - 50 = 924 = ~900
350350
MaxOpenConnections int `mapstructure:"max_open_connections"`
351+
352+
// Maximum number of go routine to process websocket request.
353+
// 1 - process websocket request synchronously.
354+
// 10 - default size.
355+
// Should be {WebsocketPoolSpawnSize} =< {WebsocketPoolMaxSize}
356+
WebsocketPoolMaxSize int `mapstructure:"websocket_pool_size"`
357+
358+
// The queued buffer for workers to process requests.
359+
// 10 -default
360+
WebsocketPoolQueueSize int `mapstructure:"websocket_pool_queue_size"`
361+
362+
// The initial size of goroutines in pool.
363+
// 1 - process websocket request synchronously.
364+
// 5 - default size
365+
// Should be {WebsocketPoolSpawnSize} =< {WebsocketPoolMaxSize}
366+
WebsocketPoolSpawnSize int `mapstructure:"websocket_pool_spawn_size"`
351367
}
352368

353369
// DefaultRPCConfig returns a default configuration for the RPC server
@@ -360,8 +376,11 @@ func DefaultRPCConfig() *RPCConfig {
360376
GRPCListenAddress: "",
361377
GRPCMaxOpenConnections: 900,
362378

363-
Unsafe: false,
364-
MaxOpenConnections: 900,
379+
Unsafe: false,
380+
MaxOpenConnections: 900,
381+
WebsocketPoolMaxSize: 10,
382+
WebsocketPoolQueueSize: 10,
383+
WebsocketPoolSpawnSize: 5,
365384
}
366385
}
367386

@@ -383,6 +402,18 @@ func (cfg *RPCConfig) ValidateBasic() error {
383402
if cfg.MaxOpenConnections < 0 {
384403
return errors.New("max_open_connections can't be negative")
385404
}
405+
if cfg.WebsocketPoolMaxSize < 1 {
406+
return errors.New("websocket_pool_size can't be less than 1")
407+
}
408+
if cfg.WebsocketPoolQueueSize < 1 {
409+
return errors.New("websocket_pool_queue_size can't be less than 1")
410+
}
411+
if cfg.WebsocketPoolSpawnSize < 1 {
412+
return errors.New("websocket_pool_spawn_size can't be less than 1")
413+
}
414+
if cfg.WebsocketPoolSpawnSize > cfg.WebsocketPoolMaxSize {
415+
return errors.New("websocket_pool_spawn_size can't be large than websocket_pool_size")
416+
}
386417
return nil
387418
}
388419

config/toml.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,23 @@ unsafe = {{ .RPC.Unsafe }}
177177
# 1024 - 40 - 10 - 50 = 924 = ~900
178178
max_open_connections = {{ .RPC.MaxOpenConnections }}
179179
180+
181+
# Maximum number of go routine to process websocket request.
182+
# 1 - process websocket request synchronously.
183+
# 10 - default size.
184+
# Should be {WebsocketPoolSpawnSize} =< {WebsocketPoolMaxSize}
185+
websocket_pool_size = {{ .RPC.WebsocketPoolMaxSize }}
186+
187+
# The queued buffer for workers to process requests.
188+
# 10 -default
189+
websocket_pool_queue_size = {{ .RPC.WebsocketPoolQueueSize }}
190+
191+
# The initial size of goroutines in pool.
192+
# 1 - process websocket request synchronously.
193+
# 5 - default size
194+
# Should be {WebsocketPoolSpawnSize} =< {WebsocketPoolMaxSize}
195+
websocket_pool_spawn_size = {{ .RPC.WebsocketPoolSpawnSize }}
196+
180197
##### peer to peer configuration options #####
181198
[p2p]
182199

libs/gopool/pool.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Package gopool contains tools for goroutine reuse.
2+
package gopool
3+
4+
import (
5+
"fmt"
6+
"runtime/debug"
7+
"time"
8+
9+
"github.com/tendermint/tendermint/libs/common"
10+
)
11+
12+
// ErrScheduleTimeout returned by Pool to indicate that there no free
13+
// goroutines during some period of time.
14+
var ErrScheduleTimeout = fmt.Errorf("schedule error: timed out")
15+
16+
// Pool contains logic of goroutine reuse.
17+
type Pool struct {
18+
common.BaseService
19+
sem chan struct{}
20+
work chan func()
21+
}
22+
23+
// NewPool creates new goroutine pool with given size. It also creates a work
24+
// queue of given size. Finally, it spawns given amount of goroutines
25+
// immediately.
26+
func NewPool(size, queue, spawn int) *Pool {
27+
if spawn <= 0 && queue > 0 {
28+
panic("dead queue configuration detected")
29+
}
30+
if spawn > size {
31+
panic("spawn > workers")
32+
}
33+
p := &Pool{
34+
sem: make(chan struct{}, size),
35+
work: make(chan func(), queue),
36+
}
37+
p.BaseService = *common.NewBaseService(nil, "routine-pool", p)
38+
39+
for i := 0; i < spawn; i++ {
40+
p.sem <- struct{}{}
41+
go p.worker(func() {})
42+
}
43+
44+
return p
45+
}
46+
47+
// Schedule schedules task to be executed over pool's workers.
48+
func (p *Pool) Schedule(task func()) {
49+
p.schedule(task, nil)
50+
}
51+
52+
// ScheduleTimeout schedules task to be executed over pool's workers.
53+
// It returns ErrScheduleTimeout when no free workers met during given timeout.
54+
func (p *Pool) ScheduleTimeout(timeout time.Duration, task func()) error {
55+
return p.schedule(task, time.After(timeout))
56+
}
57+
58+
func (p *Pool) schedule(task func(), timeout <-chan time.Time) error {
59+
select {
60+
case <-timeout:
61+
return ErrScheduleTimeout
62+
case p.work <- task:
63+
return nil
64+
case p.sem <- struct{}{}:
65+
go p.worker(task)
66+
return nil
67+
}
68+
}
69+
70+
func (p *Pool) worker(task func()) {
71+
defer func() {
72+
if r := recover(); r != nil {
73+
p.Logger.Error("failed to process task", "err", r, "stack", string(debug.Stack()))
74+
}
75+
<-p.sem
76+
}()
77+
78+
task()
79+
80+
for task := range p.work {
81+
task()
82+
}
83+
}

node/node.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/tendermint/tendermint/evidence"
2626
cmn "github.com/tendermint/tendermint/libs/common"
2727
dbm "github.com/tendermint/tendermint/libs/db"
28+
"github.com/tendermint/tendermint/libs/gopool"
2829
"github.com/tendermint/tendermint/libs/log"
2930
mempl "github.com/tendermint/tendermint/mempool"
3031
"github.com/tendermint/tendermint/p2p"
@@ -722,10 +723,17 @@ func (n *Node) startRPC() ([]net.Listener, error) {
722723

723724
// we may expose the rpc over both a unix and tcp socket
724725
listeners := make([]net.Listener, len(listenAddrs))
726+
var wsWorkerPool *gopool.Pool
727+
if n.config.RPC.WebsocketPoolMaxSize > 1{
728+
wsWorkerPool = gopool.NewPool(n.config.RPC.WebsocketPoolMaxSize, n.config.RPC.WebsocketPoolQueueSize, n.config.RPC.WebsocketPoolSpawnSize)
729+
wsWorkerPool.SetLogger(n.Logger.With("module", "routine-pool"))
730+
}
731+
725732
for i, listenAddr := range listenAddrs {
726733
mux := http.NewServeMux()
727734
rpcLogger := n.Logger.With("module", "rpc-server")
728-
wm := rpcserver.NewWebsocketManager(rpccore.Routes, coreCodec, rpcserver.EventSubscriber(n.eventBus))
735+
736+
wm := rpcserver.NewWebsocketManager(rpccore.Routes, coreCodec, rpcserver.EventSubscriber(n.eventBus), rpcserver.SetWorkerPool(wsWorkerPool))
729737
wm.SetLogger(rpcLogger.With("protocol", "websocket"))
730738
mux.HandleFunc("/websocket", wm.WebsocketHandler)
731739
rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger)

rpc/lib/server/handlers.go

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
amino "github.com/tendermint/go-amino"
2121
cmn "github.com/tendermint/tendermint/libs/common"
22+
"github.com/tendermint/tendermint/libs/gopool"
2223
"github.com/tendermint/tendermint/libs/log"
2324
types "github.com/tendermint/tendermint/rpc/lib/types"
2425
)
@@ -436,6 +437,8 @@ type wsConnection struct {
436437

437438
// object that is used to subscribe / unsubscribe from events
438439
eventSub types.EventSubscriber
440+
441+
workerPool *gopool.Pool
439442
}
440443

441444
// NewWSConnection wraps websocket.Conn.
@@ -477,6 +480,12 @@ func EventSubscriber(eventSub types.EventSubscriber) func(*wsConnection) {
477480
}
478481
}
479482

483+
func SetWorkerPool(workerPool *gopool.Pool) func(*wsConnection) {
484+
return func(wsc *wsConnection) {
485+
wsc.workerPool = workerPool
486+
}
487+
}
488+
480489
// WriteWait sets the amount of time to wait before a websocket write times out.
481490
// It should only be used in the constructor - not Goroutine-safe.
482491
func WriteWait(writeWait time.Duration) func(*wsConnection) {
@@ -611,57 +620,68 @@ func (wsc *wsConnection) readRoutine() {
611620
wsc.Stop()
612621
return
613622
}
614-
615-
var request types.RPCRequest
616-
err = json.Unmarshal(in, &request)
617-
if err != nil {
618-
wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshaling request")))
619-
continue
620-
}
621-
622-
// A Notification is a Request object without an "id" member.
623-
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
624-
if request.ID == types.JSONRPCStringID("") {
625-
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
626-
continue
623+
// Fetch the RPCFunc and execute it.
624+
if wsc.workerPool != nil {
625+
wsc.workerPool.Schedule(func() {
626+
wsc.processRequest(in)
627+
})
628+
} else {
629+
wsc.processRequest(in)
627630
}
628631

629-
// Now, fetch the RPCFunc and execute it.
632+
}
633+
}
634+
}
630635

631-
rpcFunc := wsc.funcMap[request.Method]
632-
if rpcFunc == nil {
633-
wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID))
634-
continue
635-
}
636-
var args []reflect.Value
637-
if rpcFunc.ws {
638-
wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
639-
if len(request.Params) > 0 {
640-
args, err = jsonParamsToArgsWS(rpcFunc, wsc.cdc, request.Params, wsCtx)
641-
}
642-
} else {
643-
if len(request.Params) > 0 {
644-
args, err = jsonParamsToArgsRPC(rpcFunc, wsc.cdc, request.Params)
645-
}
646-
}
647-
if err != nil {
648-
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
649-
continue
650-
}
651-
returns := rpcFunc.f.Call(args)
636+
func (wsc *wsConnection) processRequest(message []byte) {
637+
var request types.RPCRequest
638+
err := json.Unmarshal(message, &request)
639+
if err != nil {
640+
wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshaling request")))
641+
return
642+
}
652643

653-
// TODO: Need to encode args/returns to string if we want to log them
654-
wsc.Logger.Info("WSJSONRPC", "method", request.Method)
644+
// A Notification is a Request object without an "id" member.
645+
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
646+
if request.ID == types.JSONRPCStringID("") {
647+
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
648+
return
649+
}
655650

656-
result, err := unreflectResult(returns)
657-
if err != nil {
658-
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
659-
continue
660-
}
651+
// Now, fetch the RPCFunc and execute it.
661652

662-
wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result))
653+
rpcFunc := wsc.funcMap[request.Method]
654+
if rpcFunc == nil {
655+
wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID))
656+
return
657+
}
658+
var args []reflect.Value
659+
if rpcFunc.ws {
660+
wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
661+
if len(request.Params) > 0 {
662+
args, err = jsonParamsToArgsWS(rpcFunc, wsc.cdc, request.Params, wsCtx)
663663
}
664+
} else {
665+
if len(request.Params) > 0 {
666+
args, err = jsonParamsToArgsRPC(rpcFunc, wsc.cdc, request.Params)
667+
}
668+
}
669+
if err != nil {
670+
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
671+
return
672+
}
673+
returns := rpcFunc.f.Call(args)
674+
675+
// TODO: Need to encode args/returns to string if we want to log them
676+
wsc.Logger.Info("WSJSONRPC", "method", request.Method)
677+
678+
result, err := unreflectResult(returns)
679+
if err != nil {
680+
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
681+
return
664682
}
683+
684+
wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result))
665685
}
666686

667687
// receives on a write channel and writes out on the socket

rpc/test/helpers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ func GetConfig() *cfg.Config {
8989
globalConfig.RPC.ListenAddress = rpc
9090
globalConfig.RPC.CORSAllowedOrigins = []string{"https://tendermint.com/"}
9191
globalConfig.RPC.GRPCListenAddress = grpc
92+
globalConfig.RPC.WebsocketPoolMaxSize = 1
93+
globalConfig.RPC.WebsocketPoolQueueSize = 1
94+
globalConfig.RPC.WebsocketPoolSpawnSize = 1
9295
globalConfig.TxIndex.IndexTags = "app.creator,tx.height" // see kvstore application
9396
}
9497
return globalConfig

0 commit comments

Comments
 (0)