You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

243 lines
6.2 KiB

package proxy
import (
"context"
"errors"
"fmt"
"os"
"syscall"
abciclient "github.com/tendermint/tendermint/abci/client"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
)
const (
connConsensus = "consensus"
connMempool = "mempool"
connQuery = "query"
connSnapshot = "snapshot"
)
// AppConns is the Tendermint's interface to the application that consists of
// multiple connections.
type AppConns interface {
service.Service
// Mempool connection
Mempool() AppConnMempool
// Consensus connection
Consensus() AppConnConsensus
// Query connection
Query() AppConnQuery
// Snapshot connection
Snapshot() AppConnSnapshot
}
// NewAppConns calls NewMultiAppConn.
func NewAppConns(clientCreator abciclient.Creator, logger log.Logger, metrics *Metrics) AppConns {
return NewMultiAppConn(clientCreator, logger, metrics)
}
// multiAppConn implements AppConns.
//
// A multiAppConn is made of a few appConns and manages their underlying abci
// clients.
// TODO: on app restart, clients must reboot together
type multiAppConn struct {
service.BaseService
logger log.Logger
metrics *Metrics
consensusConn AppConnConsensus
mempoolConn AppConnMempool
queryConn AppConnQuery
snapshotConn AppConnSnapshot
consensusConnClient stoppableClient
mempoolConnClient stoppableClient
queryConnClient stoppableClient
snapshotConnClient stoppableClient
clientCreator abciclient.Creator
}
// TODO: this is a totally internal and quasi permanent shim for
// clients. eventually we can have a single client and have some kind
// of reasonable lifecycle witout needing an explicit stop method.
type stoppableClient interface {
abciclient.Client
Stop() error
}
// NewMultiAppConn makes all necessary abci connections to the application.
func NewMultiAppConn(clientCreator abciclient.Creator, logger log.Logger, metrics *Metrics) AppConns {
multiAppConn := &multiAppConn{
logger: logger,
metrics: metrics,
clientCreator: clientCreator,
}
multiAppConn.BaseService = *service.NewBaseService(logger, "multiAppConn", multiAppConn)
return multiAppConn
}
func (app *multiAppConn) Mempool() AppConnMempool {
return app.mempoolConn
}
func (app *multiAppConn) Consensus() AppConnConsensus {
return app.consensusConn
}
func (app *multiAppConn) Query() AppConnQuery {
return app.queryConn
}
func (app *multiAppConn) Snapshot() AppConnSnapshot {
return app.snapshotConn
}
func (app *multiAppConn) OnStart(ctx context.Context) error {
c, err := app.abciClientFor(ctx, connQuery)
if err != nil {
return err
}
app.queryConnClient = c.(stoppableClient)
app.queryConn = NewAppConnQuery(c, app.metrics)
c, err = app.abciClientFor(ctx, connSnapshot)
if err != nil {
app.stopAllClients()
return err
}
app.snapshotConnClient = c.(stoppableClient)
app.snapshotConn = NewAppConnSnapshot(c, app.metrics)
c, err = app.abciClientFor(ctx, connMempool)
if err != nil {
app.stopAllClients()
return err
}
app.mempoolConnClient = c.(stoppableClient)
app.mempoolConn = NewAppConnMempool(c, app.metrics)
c, err = app.abciClientFor(ctx, connConsensus)
if err != nil {
app.stopAllClients()
return err
}
app.consensusConnClient = c.(stoppableClient)
app.consensusConn = NewAppConnConsensus(c, app.metrics)
// Kill Tendermint if the ABCI application crashes.
app.startWatchersForClientErrorToKillTendermint(ctx)
return nil
}
func (app *multiAppConn) OnStop() {
app.stopAllClients()
}
func (app *multiAppConn) startWatchersForClientErrorToKillTendermint(ctx context.Context) {
// this function starts a number of threads (per abci client)
// that will SIGTERM's our own PID if any of the ABCI clients
// exit/return early. If the context is canceled then these
// functions will not kill tendermint.
killFn := func(conn string, err error, logger log.Logger) {
logger.Error(
fmt.Sprintf("%s connection terminated. Did the application crash? Please restart tendermint", conn),
"err", err)
if killErr := kill(); killErr != nil {
logger.Error("Failed to kill this process - please do so manually", "err", killErr)
}
}
type op struct {
connClient stoppableClient
name string
}
for _, client := range []op{
{
connClient: app.consensusConnClient,
name: connConsensus,
},
{
connClient: app.mempoolConnClient,
name: connMempool,
},
{
connClient: app.queryConnClient,
name: connQuery,
},
{
connClient: app.snapshotConnClient,
name: connSnapshot,
},
} {
go func(name string, client stoppableClient) {
client.Wait()
if ctx.Err() != nil {
return
}
if err := client.Error(); err != nil {
killFn(name, err, app.logger)
}
}(client.name, client.connClient)
}
}
func (app *multiAppConn) stopAllClients() {
if app.consensusConnClient != nil {
if err := app.consensusConnClient.Stop(); err != nil {
if !errors.Is(err, service.ErrAlreadyStopped) {
app.logger.Error("error while stopping consensus client", "error", err)
}
}
}
if app.mempoolConnClient != nil {
if err := app.mempoolConnClient.Stop(); err != nil {
if !errors.Is(err, service.ErrAlreadyStopped) {
app.logger.Error("error while stopping mempool client", "error", err)
}
}
}
if app.queryConnClient != nil {
if err := app.queryConnClient.Stop(); err != nil {
if !errors.Is(err, service.ErrAlreadyStopped) {
app.logger.Error("error while stopping query client", "error", err)
}
}
}
if app.snapshotConnClient != nil {
if err := app.snapshotConnClient.Stop(); err != nil {
if !errors.Is(err, service.ErrAlreadyStopped) {
app.logger.Error("error while stopping snapshot client", "error", err)
}
}
}
}
func (app *multiAppConn) abciClientFor(ctx context.Context, conn string) (abciclient.Client, error) {
c, err := app.clientCreator(app.logger.With(
"module", "abci-client",
"connection", conn))
if err != nil {
return nil, fmt.Errorf("error creating ABCI client (%s connection): %w", conn, err)
}
if err := c.Start(ctx); err != nil {
return nil, fmt.Errorf("error starting ABCI client (%s connection): %w", conn, err)
}
return c, nil
}
func kill() error {
p, err := os.FindProcess(os.Getpid())
if err != nil {
return err
}
return p.Signal(syscall.SIGTERM)
}