Skip to content

Commit 2e4fdce

Browse files
committed
Merge pull request ethereum#1932 from fjl/gpo-defootgunize
eth, xeth: fix GasPriceOracle goroutine leak
2 parents 57ab147 + ae1b5b3 commit 2e4fdce

File tree

2 files changed

+75
-73
lines changed

2 files changed

+75
-73
lines changed

eth/gasprice.go

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,49 +23,66 @@ import (
2323

2424
"github.com/ethereum/go-ethereum/core"
2525
"github.com/ethereum/go-ethereum/core/types"
26-
"github.com/ethereum/go-ethereum/event"
2726
"github.com/ethereum/go-ethereum/logger"
2827
"github.com/ethereum/go-ethereum/logger/glog"
2928
)
3029

31-
const gpoProcessPastBlocks = 100
30+
const (
31+
gpoProcessPastBlocks = 100
32+
33+
// for testing
34+
gpoDefaultBaseCorrectionFactor = 110
35+
gpoDefaultMinGasPrice = 10000000000000
36+
)
3237

3338
type blockPriceInfo struct {
3439
baseGasPrice *big.Int
3540
}
3641

42+
// GasPriceOracle recommends gas prices based on the content of recent
43+
// blocks.
3744
type GasPriceOracle struct {
38-
eth *Ethereum
39-
chain *core.BlockChain
40-
events event.Subscription
45+
eth *Ethereum
46+
initOnce sync.Once
47+
minPrice *big.Int
48+
lastBaseMutex sync.Mutex
49+
lastBase *big.Int
50+
51+
// state of listenLoop
4152
blocks map[uint64]*blockPriceInfo
4253
firstProcessed, lastProcessed uint64
43-
lastBaseMutex sync.Mutex
44-
lastBase, minBase *big.Int
54+
minBase *big.Int
55+
}
56+
57+
// NewGasPriceOracle returns a new oracle.
58+
func NewGasPriceOracle(eth *Ethereum) *GasPriceOracle {
59+
minprice := eth.GpoMinGasPrice
60+
if minprice == nil {
61+
minprice = big.NewInt(gpoDefaultMinGasPrice)
62+
}
63+
minbase := new(big.Int).Mul(minprice, big.NewInt(100))
64+
if eth.GpobaseCorrectionFactor > 0 {
65+
minbase = minbase.Div(minbase, big.NewInt(int64(eth.GpobaseCorrectionFactor)))
66+
}
67+
return &GasPriceOracle{
68+
eth: eth,
69+
blocks: make(map[uint64]*blockPriceInfo),
70+
minBase: minbase,
71+
minPrice: minprice,
72+
lastBase: minprice,
73+
}
4574
}
4675

47-
func NewGasPriceOracle(eth *Ethereum) (self *GasPriceOracle) {
48-
self = &GasPriceOracle{}
49-
self.blocks = make(map[uint64]*blockPriceInfo)
50-
self.eth = eth
51-
self.chain = eth.blockchain
52-
self.events = eth.EventMux().Subscribe(
53-
core.ChainEvent{},
54-
core.ChainSplitEvent{},
55-
)
56-
57-
minbase := new(big.Int).Mul(self.eth.GpoMinGasPrice, big.NewInt(100))
58-
minbase = minbase.Div(minbase, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
59-
self.minBase = minbase
60-
61-
self.processPastBlocks()
62-
go self.listenLoop()
63-
return
76+
func (gpo *GasPriceOracle) init() {
77+
gpo.initOnce.Do(func() {
78+
gpo.processPastBlocks(gpo.eth.BlockChain())
79+
go gpo.listenLoop()
80+
})
6481
}
6582

66-
func (self *GasPriceOracle) processPastBlocks() {
83+
func (self *GasPriceOracle) processPastBlocks(chain *core.BlockChain) {
6784
last := int64(-1)
68-
cblock := self.chain.CurrentBlock()
85+
cblock := chain.CurrentBlock()
6986
if cblock != nil {
7087
last = int64(cblock.NumberU64())
7188
}
@@ -75,7 +92,7 @@ func (self *GasPriceOracle) processPastBlocks() {
7592
}
7693
self.firstProcessed = uint64(first)
7794
for i := first; i <= last; i++ {
78-
block := self.chain.GetBlockByNumber(uint64(i))
95+
block := chain.GetBlockByNumber(uint64(i))
7996
if block != nil {
8097
self.processBlock(block)
8198
}
@@ -84,9 +101,10 @@ func (self *GasPriceOracle) processPastBlocks() {
84101
}
85102

86103
func (self *GasPriceOracle) listenLoop() {
87-
defer self.events.Unsubscribe()
104+
events := self.eth.EventMux().Subscribe(core.ChainEvent{}, core.ChainSplitEvent{})
105+
defer events.Unsubscribe()
88106

89-
for event := range self.events.Chan() {
107+
for event := range events.Chan() {
90108
switch event := event.Data.(type) {
91109
case core.ChainEvent:
92110
self.processBlock(event.Block)
@@ -102,7 +120,7 @@ func (self *GasPriceOracle) processBlock(block *types.Block) {
102120
self.lastProcessed = i
103121
}
104122

105-
lastBase := self.eth.GpoMinGasPrice
123+
lastBase := self.minPrice
106124
bpl := self.blocks[i-1]
107125
if bpl != nil {
108126
lastBase = bpl.baseGasPrice
@@ -176,28 +194,19 @@ func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int {
176194
return minPrice
177195
}
178196

197+
// SuggestPrice returns the recommended gas price.
179198
func (self *GasPriceOracle) SuggestPrice() *big.Int {
199+
self.init()
180200
self.lastBaseMutex.Lock()
181-
base := self.lastBase
201+
price := new(big.Int).Set(self.lastBase)
182202
self.lastBaseMutex.Unlock()
183203

184-
if base == nil {
185-
base = self.eth.GpoMinGasPrice
204+
price.Mul(price, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
205+
price.Div(price, big.NewInt(100))
206+
if price.Cmp(self.minPrice) < 0 {
207+
price.Set(self.minPrice)
208+
} else if self.eth.GpoMaxGasPrice != nil && price.Cmp(self.eth.GpoMaxGasPrice) > 0 {
209+
price.Set(self.eth.GpoMaxGasPrice)
186210
}
187-
if base == nil {
188-
return big.NewInt(10000000000000) // apparently MinGasPrice is not initialized during some tests
189-
}
190-
191-
baseCorr := new(big.Int).Mul(base, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
192-
baseCorr.Div(baseCorr, big.NewInt(100))
193-
194-
if baseCorr.Cmp(self.eth.GpoMinGasPrice) < 0 {
195-
return self.eth.GpoMinGasPrice
196-
}
197-
198-
if baseCorr.Cmp(self.eth.GpoMaxGasPrice) > 0 {
199-
return self.eth.GpoMaxGasPrice
200-
}
201-
202-
return baseCorr
211+
return price
203212
}

xeth/xeth.go

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,24 +59,8 @@ const (
5959
LogFilterTy
6060
)
6161

62-
func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) }
63-
64-
func (self *XEth) DefaultGasPrice() *big.Int {
65-
if self.gpo == nil {
66-
self.gpo = eth.NewGasPriceOracle(self.backend)
67-
}
68-
return self.gpo.SuggestPrice()
69-
}
70-
7162
type XEth struct {
72-
backend *eth.Ethereum
73-
frontend Frontend
74-
75-
state *State
76-
whisper *Whisper
77-
78-
quit chan struct{}
79-
filterManager *filters.FilterSystem
63+
quit chan struct{}
8064

8165
logMu sync.RWMutex
8266
logQueue map[int]*logQueue
@@ -92,16 +76,18 @@ type XEth struct {
9276

9377
transactMu sync.Mutex
9478

95-
agent *miner.RemoteAgent
96-
97-
gpo *eth.GasPriceOracle
79+
// read-only fields
80+
backend *eth.Ethereum
81+
frontend Frontend
82+
agent *miner.RemoteAgent
83+
gpo *eth.GasPriceOracle
84+
state *State
85+
whisper *Whisper
86+
filterManager *filters.FilterSystem
9887
}
9988

10089
func NewTest(eth *eth.Ethereum, frontend Frontend) *XEth {
101-
return &XEth{
102-
backend: eth,
103-
frontend: frontend,
104-
}
90+
return &XEth{backend: eth, frontend: frontend}
10591
}
10692

10793
// New creates an XEth that uses the given frontend.
@@ -118,6 +104,7 @@ func New(ethereum *eth.Ethereum, frontend Frontend) *XEth {
118104
transactionQueue: make(map[int]*hashQueue),
119105
messages: make(map[int]*whisperFilter),
120106
agent: miner.NewRemoteAgent(),
107+
gpo: eth.NewGasPriceOracle(ethereum),
121108
}
122109
if ethereum.Whisper() != nil {
123110
xeth.whisper = NewWhisper(ethereum.Whisper())
@@ -207,6 +194,12 @@ func cTopics(t [][]string) [][]common.Hash {
207194
return topics
208195
}
209196

197+
func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) }
198+
199+
func (self *XEth) DefaultGasPrice() *big.Int {
200+
return self.gpo.SuggestPrice()
201+
}
202+
210203
func (self *XEth) RemoteMining() *miner.RemoteAgent { return self.agent }
211204

212205
func (self *XEth) AtStateNum(num int64) *XEth {

0 commit comments

Comments
 (0)