forked from TrueCloudLab/frostfs-node
[#1170] pkg/morph: Add worker pool
Add worker pool to the listener to prevent blocking. It is used only for notary notifications and new block events handling since it uses RPC calls. That may lead to the deadlock state: neo-go cannot send RPC until notification channel is read but notification channel cannot be read since neo-go client cannot send RPC. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
4ed5b1ceef
commit
01e69f2f7a
3 changed files with 56 additions and 7 deletions
|
@ -173,6 +173,14 @@ func waitNotaryDeposit(c *cfg, tx util.Uint256) error {
|
|||
}
|
||||
|
||||
func listenMorphNotifications(c *cfg) {
|
||||
// listenerPoolCap is a capacity of a
|
||||
// worker pool inside the listener. It
|
||||
// is used to prevent blocking in neo-go:
|
||||
// the client cannot make RPC requests if
|
||||
// the notification channel is not being
|
||||
// read by another goroutine.
|
||||
const listenerPoolCap = 10
|
||||
|
||||
var (
|
||||
err error
|
||||
subs subscriber.Subscriber
|
||||
|
@ -192,8 +200,9 @@ func listenMorphNotifications(c *cfg) {
|
|||
fatalOnErr(err)
|
||||
|
||||
lis, err := event.NewListener(event.ListenerParams{
|
||||
Logger: c.log,
|
||||
Subscriber: subs,
|
||||
Logger: c.log,
|
||||
Subscriber: subs,
|
||||
WorkerPoolCapacity: listenerPoolCap,
|
||||
})
|
||||
fatalOnErr(err)
|
||||
|
||||
|
|
|
@ -921,6 +921,14 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper, errChan chan<-
|
|||
}
|
||||
|
||||
func createListener(ctx context.Context, cli *client.Client, p *chainParams) (event.Listener, error) {
|
||||
// listenerPoolCap is a capacity of a
|
||||
// worker pool inside the listener. It
|
||||
// is used to prevent blocking in neo-go:
|
||||
// the client cannot make RPC requests if
|
||||
// the notification channel is not being
|
||||
// read by another goroutine.
|
||||
const listenerPoolCap = 10
|
||||
|
||||
var (
|
||||
sub subscriber.Subscriber
|
||||
err error
|
||||
|
@ -936,8 +944,9 @@ func createListener(ctx context.Context, cli *client.Client, p *chainParams) (ev
|
|||
}
|
||||
|
||||
listener, err := event.NewListener(event.ListenerParams{
|
||||
Logger: p.log,
|
||||
Subscriber: sub,
|
||||
Logger: p.log.With(zap.String("chain", p.name)),
|
||||
Subscriber: sub,
|
||||
WorkerPoolCapacity: listenerPoolCap,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/subscriber"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -85,6 +86,8 @@ type ListenerParams struct {
|
|||
Logger *zap.Logger
|
||||
|
||||
Subscriber subscriber.Subscriber
|
||||
|
||||
WorkerPoolCapacity int
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
|
@ -108,6 +111,8 @@ type listener struct {
|
|||
subscriber subscriber.Subscriber
|
||||
|
||||
blockHandlers []BlockHandler
|
||||
|
||||
pool *ants.Pool
|
||||
}
|
||||
|
||||
const newListenerFailMsg = "could not instantiate Listener"
|
||||
|
@ -258,7 +263,12 @@ loop:
|
|||
continue loop
|
||||
}
|
||||
|
||||
l.parseAndHandleNotary(notaryEvent)
|
||||
if err = l.pool.Submit(func() {
|
||||
l.parseAndHandleNotary(notaryEvent)
|
||||
}); err != nil {
|
||||
l.log.Warn("listener worker pool drained",
|
||||
zap.Int("capacity", l.pool.Cap()))
|
||||
}
|
||||
case b, ok := <-blockChan:
|
||||
if !ok {
|
||||
l.log.Warn("stop event listener by block channel")
|
||||
|
@ -272,8 +282,13 @@ loop:
|
|||
continue loop
|
||||
}
|
||||
|
||||
for i := range l.blockHandlers {
|
||||
l.blockHandlers[i](b)
|
||||
if err = l.pool.Submit(func() {
|
||||
for i := range l.blockHandlers {
|
||||
l.blockHandlers[i](b)
|
||||
}
|
||||
}); err != nil {
|
||||
l.log.Warn("listener worker pool drained",
|
||||
zap.Int("capacity", l.pool.Cap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -586,6 +601,11 @@ func (l *listener) RegisterBlockHandler(handler BlockHandler) {
|
|||
|
||||
// NewListener create the notification event listener instance and returns Listener interface.
|
||||
func NewListener(p ListenerParams) (Listener, error) {
|
||||
// defaultPoolCap is a default worker
|
||||
// pool capacity if it was not specified
|
||||
// via params
|
||||
const defaultPoolCap = 10
|
||||
|
||||
switch {
|
||||
case p.Logger == nil:
|
||||
return nil, fmt.Errorf("%s: %w", newListenerFailMsg, errNilLogger)
|
||||
|
@ -593,10 +613,21 @@ func NewListener(p ListenerParams) (Listener, error) {
|
|||
return nil, fmt.Errorf("%s: %w", newListenerFailMsg, errNilSubscriber)
|
||||
}
|
||||
|
||||
poolCap := p.WorkerPoolCapacity
|
||||
if poolCap == 0 {
|
||||
poolCap = defaultPoolCap
|
||||
}
|
||||
|
||||
pool, err := ants.NewPool(poolCap, ants.WithNonblocking(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not init worker pool: %w", err)
|
||||
}
|
||||
|
||||
return &listener{
|
||||
notificationParsers: make(map[scriptHashWithType]NotificationParser),
|
||||
notificationHandlers: make(map[scriptHashWithType][]Handler),
|
||||
log: p.Logger,
|
||||
subscriber: p.Subscriber,
|
||||
pool: pool,
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue