fix : go-routine leak in commitInterrupt channel (#851)

* fix : go-routine leak in commitInterrupt channel

* fix : fix lint

* rm : remove t.Parallel() from TestGenerateBlockAndImport tests

* fix : minor optimisations in test

* fix : BenchmarkBorMining
This commit is contained in:
SHIVAM SHARMA 2023-05-08 12:36:30 +05:30 committed by GitHub
parent 51cbcea2d8
commit 1995521521
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 59 deletions

View file

@ -212,7 +212,7 @@ var (
// Test accounts // Test accounts
testBankKey, _ = crypto.GenerateKey() testBankKey, _ = crypto.GenerateKey()
TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
testBankFunds = big.NewInt(1000000000000000000) testBankFunds = big.NewInt(9000000000000000000)
testUserKey, _ = crypto.GenerateKey() testUserKey, _ = crypto.GenerateKey()
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)

View file

@ -358,16 +358,14 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee)) txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
tcount := w.current.tcount tcount := w.current.tcount
interruptCh, stopFn := getInterruptTimer(ctx, w.current, w.chain.CurrentBlock()) //nolint:contextcheck
w.commitTransactionsWithDelay(w.current, txset, nil, interruptCh, delay) w.commitTransactions(w.current, txset, nil, context.Background())
// Only update the snapshot if any new transactions were added // Only update the snapshot if any new transactions were added
// to the pending block // to the pending block
if tcount != w.current.tcount { if tcount != w.current.tcount {
w.updateSnapshot(w.current) w.updateSnapshot(w.current)
} }
stopFn()
} else { } else {
// Special case, if the consensus engine is 0 period clique(dev mode), // Special case, if the consensus engine is 0 period clique(dev mode),
// submit sealing work here since all empty submission will be rejected // submit sealing work here since all empty submission will be rejected
@ -393,7 +391,7 @@ func (w *worker) mainLoopWithDelay(ctx context.Context, delay uint) {
} }
// nolint:gocognit // nolint:gocognit
func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCh chan struct{}, delay uint) bool { func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCtx context.Context, delay uint) bool {
gasLimit := env.header.GasLimit gasLimit := env.header.GasLimit
if env.gasPool == nil { if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit) env.gasPool = new(core.GasPool).AddGas(gasLimit)
@ -419,13 +417,17 @@ func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.Transa
mainloop: mainloop:
for { for {
// case of interrupting by timeout
if interruptCtx != nil {
// case of interrupting by timeout // case of interrupting by timeout
select { select {
case <-interruptCh: case <-interruptCtx.Done():
commitInterruptCounter.Inc(1) commitInterruptCounter.Inc(1)
log.Warn("Tx Level Interrupt")
break mainloop break mainloop
default: default:
} }
}
// In the following three cases, we will interrupt the execution of the transaction. // In the following three cases, we will interrupt the execution of the transaction.
// (1) new head block event arrival, the interrupt signal is 1 // (1) new head block event arrival, the interrupt signal is 1
// (2) worker start or restart, the interrupt signal is 1 // (2) worker start or restart, the interrupt signal is 1
@ -581,7 +583,8 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
return return
} }
var interruptCh chan struct{} //nolint:contextcheck
var interruptCtx = context.Background()
stopFn := func() {} stopFn := func() {}
defer func() { defer func() {
@ -589,7 +592,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
}() }()
if !noempty { if !noempty {
interruptCh, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock()) interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
} }
ctx, span := tracing.StartSpan(ctx, "commitWork") ctx, span := tracing.StartSpan(ctx, "commitWork")
@ -610,7 +613,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
} }
// Fill pending transactions from the txpool // Fill pending transactions from the txpool
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCh, delay) w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCtx, delay)
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start) err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
if err != nil { if err != nil {
@ -627,7 +630,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
} }
// nolint:gocognit // nolint:gocognit
func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32, env *environment, interruptCh chan struct{}, delay uint) { func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32, env *environment, interruptCtx context.Context, delay uint) {
ctx, span := tracing.StartSpan(ctx, "fillTransactions") ctx, span := tracing.StartSpan(ctx, "fillTransactions")
defer tracing.EndSpan(span) defer tracing.EndSpan(span)
@ -751,7 +754,7 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32
}) })
tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) { tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) {
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCh, delay) committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
}) })
if committed { if committed {
@ -774,7 +777,7 @@ func (w *worker) fillTransactionsWithDelay(ctx context.Context, interrupt *int32
}) })
tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) { tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) {
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCh, delay) committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
}) })
if committed { if committed {

View file

@ -654,9 +654,8 @@ func (w *worker) mainLoop(ctx context.Context) {
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee)) txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
tcount := w.current.tcount tcount := w.current.tcount
var interruptCh chan struct{} //nolint:contextcheck
w.commitTransactions(w.current, txset, nil, context.Background())
w.commitTransactions(w.current, txset, nil, interruptCh)
// Only update the snapshot if any new transactions were added // Only update the snapshot if any new transactions were added
// to the pending block // to the pending block
@ -945,7 +944,7 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*
} }
//nolint:gocognit //nolint:gocognit
func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCh chan struct{}) bool { func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *int32, interruptCtx context.Context) bool {
gasLimit := env.header.GasLimit gasLimit := env.header.GasLimit
if env.gasPool == nil { if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit) env.gasPool = new(core.GasPool).AddGas(gasLimit)
@ -970,13 +969,17 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
mainloop: mainloop:
for { for {
// case of interrupting by timeout
if interruptCtx != nil {
// case of interrupting by timeout // case of interrupting by timeout
select { select {
case <-interruptCh: case <-interruptCtx.Done():
commitInterruptCounter.Inc(1) commitInterruptCounter.Inc(1)
log.Warn("Tx Level Interrupt")
break mainloop break mainloop
default: default:
} }
}
// In the following three cases, we will interrupt the execution of the transaction. // In the following three cases, we will interrupt the execution of the transaction.
// (1) new head block event arrival, the interrupt signal is 1 // (1) new head block event arrival, the interrupt signal is 1
@ -1266,7 +1269,7 @@ func startProfiler(profile string, filepath string, number uint64) (func() error
// be customized with the plugin in the future. // be customized with the plugin in the future.
// //
//nolint:gocognit //nolint:gocognit
func (w *worker) fillTransactions(ctx context.Context, interrupt *int32, env *environment, interruptCh chan struct{}) { func (w *worker) fillTransactions(ctx context.Context, interrupt *int32, env *environment, interruptCtx context.Context) {
ctx, span := tracing.StartSpan(ctx, "fillTransactions") ctx, span := tracing.StartSpan(ctx, "fillTransactions")
defer tracing.EndSpan(span) defer tracing.EndSpan(span)
@ -1390,7 +1393,7 @@ func (w *worker) fillTransactions(ctx context.Context, interrupt *int32, env *en
}) })
tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) { tracing.Exec(ctx, "", "worker.LocalCommitTransactions", func(ctx context.Context, span trace.Span) {
committed = w.commitTransactions(env, txs, interrupt, interruptCh) committed = w.commitTransactions(env, txs, interrupt, interruptCtx)
}) })
if committed { if committed {
@ -1413,7 +1416,7 @@ func (w *worker) fillTransactions(ctx context.Context, interrupt *int32, env *en
}) })
tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) { tracing.Exec(ctx, "", "worker.RemoteCommitTransactions", func(ctx context.Context, span trace.Span) {
committed = w.commitTransactions(env, txs, interrupt, interruptCh) committed = w.commitTransactions(env, txs, interrupt, interruptCtx)
}) })
if committed { if committed {
@ -1438,10 +1441,10 @@ func (w *worker) generateWork(ctx context.Context, params *generateParams) (*typ
} }
defer work.discard() defer work.discard()
interruptCh, stopFn := getInterruptTimer(ctx, work, w.chain.CurrentBlock()) interruptCtx, stopFn := getInterruptTimer(ctx, work, w.chain.CurrentBlock())
defer stopFn() defer stopFn()
w.fillTransactions(ctx, nil, work, interruptCh) w.fillTransactions(ctx, nil, work, interruptCtx)
return w.engine.FinalizeAndAssemble(ctx, w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) return w.engine.FinalizeAndAssemble(ctx, w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts)
} }
@ -1479,7 +1482,8 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
return return
} }
var interruptCh chan struct{} //nolint:contextcheck
var interruptCtx = context.Background()
stopFn := func() {} stopFn := func() {}
defer func() { defer func() {
@ -1487,7 +1491,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
}() }()
if !noempty { if !noempty {
interruptCh, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock()) interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
} }
ctx, span := tracing.StartSpan(ctx, "commitWork") ctx, span := tracing.StartSpan(ctx, "commitWork")
@ -1508,7 +1512,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
} }
// Fill pending transactions from the txpool // Fill pending transactions from the txpool
w.fillTransactions(ctx, interrupt, work, interruptCh) w.fillTransactions(ctx, interrupt, work, interruptCtx)
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start) err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
if err != nil { if err != nil {
@ -1524,28 +1528,25 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
w.current = work w.current = work
} }
func getInterruptTimer(ctx context.Context, work *environment, current *types.Block) (chan struct{}, func()) { func getInterruptTimer(ctx context.Context, work *environment, current *types.Block) (context.Context, func()) {
delay := time.Until(time.Unix(int64(work.header.Time), 0)) delay := time.Until(time.Unix(int64(work.header.Time), 0))
timeoutTimer := time.NewTimer(delay) interruptCtx, cancel := context.WithTimeout(context.Background(), delay)
stopFn := func() {
timeoutTimer.Stop()
}
blockNumber := current.NumberU64() + 1 blockNumber := current.NumberU64() + 1
interruptCh := make(chan struct{})
go func() { go func() {
select { select {
case <-timeoutTimer.C: case <-interruptCtx.Done():
if interruptCtx.Err() != context.Canceled {
log.Info("Commit Interrupt. Pre-committing the current block", "block", blockNumber) log.Info("Commit Interrupt. Pre-committing the current block", "block", blockNumber)
cancel()
close(interruptCh) }
case <-ctx.Done(): // nothing to do case <-ctx.Done(): // nothing to do
} }
}() }()
return interruptCh, stopFn return interruptCtx, cancel
} }
// commit runs any post-transaction state modifications, assembles the final block // commit runs any post-transaction state modifications, assembles the final block

View file

@ -43,21 +43,18 @@ import (
"github.com/ethereum/go-ethereum/tests/bor/mocks" "github.com/ethereum/go-ethereum/tests/bor/mocks"
) )
// nolint : paralleltest
func TestGenerateBlockAndImportEthash(t *testing.T) { func TestGenerateBlockAndImportEthash(t *testing.T) {
t.Parallel()
testGenerateBlockAndImport(t, false, false) testGenerateBlockAndImport(t, false, false)
} }
// nolint : paralleltest
func TestGenerateBlockAndImportClique(t *testing.T) { func TestGenerateBlockAndImportClique(t *testing.T) {
t.Parallel()
testGenerateBlockAndImport(t, true, false) testGenerateBlockAndImport(t, true, false)
} }
// nolint : paralleltest
func TestGenerateBlockAndImportBor(t *testing.T) { func TestGenerateBlockAndImportBor(t *testing.T) {
t.Parallel()
testGenerateBlockAndImport(t, false, true) testGenerateBlockAndImport(t, false, true)
} }
@ -627,18 +624,18 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
} }
} }
// nolint:paralleltest
func TestCommitInterruptExperimentBor(t *testing.T) { func TestCommitInterruptExperimentBor(t *testing.T) {
t.Parallel()
// with 1 sec block time and 200 millisec tx delay we should get 5 txs per block // with 1 sec block time and 200 millisec tx delay we should get 5 txs per block
testCommitInterruptExperimentBor(t, 200, 5) testCommitInterruptExperimentBor(t, 200, 5)
time.Sleep(3 * time.Second)
// with 1 sec block time and 100 millisec tx delay we should get 10 txs per block // with 1 sec block time and 100 millisec tx delay we should get 10 txs per block
testCommitInterruptExperimentBor(t, 100, 10) testCommitInterruptExperimentBor(t, 100, 10)
} }
// nolint:thelper
func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int) { func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int) {
t.Helper()
var ( var (
engine consensus.Engine engine consensus.Engine
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
@ -726,11 +723,6 @@ func BenchmarkBorMining(b *testing.B) {
chain, _ := core.NewBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{}, nil, nil, nil) chain, _ := core.NewBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{}, nil, nil, nil)
defer chain.Stop() defer chain.Stop()
// Ignore empty commit here for less noise.
w.skipSealHook = func(task *task) bool {
return len(task.receipts) == 0
}
// fulfill tx pool // fulfill tx pool
const ( const (
totalGas = testGas + params.TxGas totalGas = testGas + params.TxGas