mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
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:
parent
51cbcea2d8
commit
1995521521
4 changed files with 55 additions and 59 deletions
|
|
@ -212,7 +212,7 @@ var (
|
|||
// Test accounts
|
||||
testBankKey, _ = crypto.GenerateKey()
|
||||
TestBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||
testBankFunds = big.NewInt(1000000000000000000)
|
||||
testBankFunds = big.NewInt(9000000000000000000)
|
||||
|
||||
testUserKey, _ = crypto.GenerateKey()
|
||||
testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
tcount := w.current.tcount
|
||||
|
||||
interruptCh, stopFn := getInterruptTimer(ctx, w.current, w.chain.CurrentBlock())
|
||||
w.commitTransactionsWithDelay(w.current, txset, nil, interruptCh, delay)
|
||||
//nolint:contextcheck
|
||||
w.commitTransactions(w.current, txset, nil, context.Background())
|
||||
|
||||
// Only update the snapshot if any new transactions were added
|
||||
// to the pending block
|
||||
if tcount != w.current.tcount {
|
||||
w.updateSnapshot(w.current)
|
||||
}
|
||||
|
||||
stopFn()
|
||||
} else {
|
||||
// Special case, if the consensus engine is 0 period clique(dev mode),
|
||||
// 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
|
||||
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
|
||||
if env.gasPool == nil {
|
||||
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||
|
|
@ -419,13 +417,17 @@ func (w *worker) commitTransactionsWithDelay(env *environment, txs *types.Transa
|
|||
|
||||
mainloop:
|
||||
for {
|
||||
// case of interrupting by timeout
|
||||
if interruptCtx != nil {
|
||||
// case of interrupting by timeout
|
||||
select {
|
||||
case <-interruptCh:
|
||||
case <-interruptCtx.Done():
|
||||
commitInterruptCounter.Inc(1)
|
||||
log.Warn("Tx Level Interrupt")
|
||||
break mainloop
|
||||
default:
|
||||
}
|
||||
}
|
||||
// In the following three cases, we will interrupt the execution of the transaction.
|
||||
// (1) new head block event arrival, 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
|
||||
}
|
||||
|
||||
var interruptCh chan struct{}
|
||||
//nolint:contextcheck
|
||||
var interruptCtx = context.Background()
|
||||
|
||||
stopFn := func() {}
|
||||
defer func() {
|
||||
|
|
@ -589,7 +592,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
|
|||
}()
|
||||
|
||||
if !noempty {
|
||||
interruptCh, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
}
|
||||
|
||||
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
|
||||
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCh, delay)
|
||||
w.fillTransactionsWithDelay(ctx, interrupt, work, interruptCtx, delay)
|
||||
|
||||
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
|
||||
if err != nil {
|
||||
|
|
@ -627,7 +630,7 @@ func (w *worker) commitWorkWithDelay(ctx context.Context, interrupt *int32, noem
|
|||
}
|
||||
|
||||
// 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")
|
||||
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) {
|
||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCh, delay)
|
||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
|
||||
})
|
||||
|
||||
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) {
|
||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCh, delay)
|
||||
committed = w.commitTransactionsWithDelay(env, txs, interrupt, interruptCtx, delay)
|
||||
})
|
||||
|
||||
if committed {
|
||||
|
|
|
|||
|
|
@ -654,9 +654,8 @@ func (w *worker) mainLoop(ctx context.Context) {
|
|||
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, cmath.FromBig(w.current.header.BaseFee))
|
||||
tcount := w.current.tcount
|
||||
|
||||
var interruptCh chan struct{}
|
||||
|
||||
w.commitTransactions(w.current, txset, nil, interruptCh)
|
||||
//nolint:contextcheck
|
||||
w.commitTransactions(w.current, txset, nil, context.Background())
|
||||
|
||||
// Only update the snapshot if any new transactions were added
|
||||
// to the pending block
|
||||
|
|
@ -945,7 +944,7 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*
|
|||
}
|
||||
|
||||
//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
|
||||
if env.gasPool == nil {
|
||||
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||
|
|
@ -970,13 +969,17 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
|
|||
|
||||
mainloop:
|
||||
for {
|
||||
// case of interrupting by timeout
|
||||
if interruptCtx != nil {
|
||||
// case of interrupting by timeout
|
||||
select {
|
||||
case <-interruptCh:
|
||||
case <-interruptCtx.Done():
|
||||
commitInterruptCounter.Inc(1)
|
||||
log.Warn("Tx Level Interrupt")
|
||||
break mainloop
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// In the following three cases, we will interrupt the execution of the transaction.
|
||||
// (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.
|
||||
//
|
||||
//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")
|
||||
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) {
|
||||
committed = w.commitTransactions(env, txs, interrupt, interruptCh)
|
||||
committed = w.commitTransactions(env, txs, interrupt, interruptCtx)
|
||||
})
|
||||
|
||||
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) {
|
||||
committed = w.commitTransactions(env, txs, interrupt, interruptCh)
|
||||
committed = w.commitTransactions(env, txs, interrupt, interruptCtx)
|
||||
})
|
||||
|
||||
if committed {
|
||||
|
|
@ -1438,10 +1441,10 @@ func (w *worker) generateWork(ctx context.Context, params *generateParams) (*typ
|
|||
}
|
||||
defer work.discard()
|
||||
|
||||
interruptCh, stopFn := getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
interruptCtx, stopFn := getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
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)
|
||||
}
|
||||
|
|
@ -1479,7 +1482,8 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
|
|||
return
|
||||
}
|
||||
|
||||
var interruptCh chan struct{}
|
||||
//nolint:contextcheck
|
||||
var interruptCtx = context.Background()
|
||||
|
||||
stopFn := func() {}
|
||||
defer func() {
|
||||
|
|
@ -1487,7 +1491,7 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
|
|||
}()
|
||||
|
||||
if !noempty {
|
||||
interruptCh, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
interruptCtx, stopFn = getInterruptTimer(ctx, work, w.chain.CurrentBlock())
|
||||
}
|
||||
|
||||
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
|
||||
w.fillTransactions(ctx, interrupt, work, interruptCh)
|
||||
w.fillTransactions(ctx, interrupt, work, interruptCtx)
|
||||
|
||||
err = w.commit(ctx, work.copy(), w.fullTaskHook, true, start)
|
||||
if err != nil {
|
||||
|
|
@ -1524,28 +1528,25 @@ func (w *worker) commitWork(ctx context.Context, interrupt *int32, noempty bool,
|
|||
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))
|
||||
|
||||
timeoutTimer := time.NewTimer(delay)
|
||||
stopFn := func() {
|
||||
timeoutTimer.Stop()
|
||||
}
|
||||
interruptCtx, cancel := context.WithTimeout(context.Background(), delay)
|
||||
|
||||
blockNumber := current.NumberU64() + 1
|
||||
interruptCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-timeoutTimer.C:
|
||||
case <-interruptCtx.Done():
|
||||
if interruptCtx.Err() != context.Canceled {
|
||||
log.Info("Commit Interrupt. Pre-committing the current block", "block", blockNumber)
|
||||
|
||||
close(interruptCh)
|
||||
cancel()
|
||||
}
|
||||
case <-ctx.Done(): // nothing to do
|
||||
}
|
||||
}()
|
||||
|
||||
return interruptCh, stopFn
|
||||
return interruptCtx, cancel
|
||||
}
|
||||
|
||||
// commit runs any post-transaction state modifications, assembles the final block
|
||||
|
|
|
|||
|
|
@ -43,21 +43,18 @@ import (
|
|||
"github.com/ethereum/go-ethereum/tests/bor/mocks"
|
||||
)
|
||||
|
||||
// nolint : paralleltest
|
||||
func TestGenerateBlockAndImportEthash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testGenerateBlockAndImport(t, false, false)
|
||||
}
|
||||
|
||||
// nolint : paralleltest
|
||||
func TestGenerateBlockAndImportClique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testGenerateBlockAndImport(t, true, false)
|
||||
}
|
||||
|
||||
// nolint : paralleltest
|
||||
func TestGenerateBlockAndImportBor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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) {
|
||||
t.Parallel()
|
||||
// with 1 sec block time and 200 millisec tx delay we should get 5 txs per block
|
||||
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
|
||||
testCommitInterruptExperimentBor(t, 100, 10)
|
||||
}
|
||||
|
||||
// nolint:thelper
|
||||
func testCommitInterruptExperimentBor(t *testing.T, delay uint, txCount int) {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
engine consensus.Engine
|
||||
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)
|
||||
defer chain.Stop()
|
||||
|
||||
// Ignore empty commit here for less noise.
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
return len(task.receipts) == 0
|
||||
}
|
||||
|
||||
// fulfill tx pool
|
||||
const (
|
||||
totalGas = testGas + params.TxGas
|
||||
|
|
|
|||
Loading…
Reference in a new issue