Skip to content

Commit ae1b5b3

Browse files
committed
eth, xeth: fix GasPriceOracle goroutine leak
XEth.gpo was being initialized as needed. WithState copies the XEth struct including the gpo field. If gpo was nil at the time of the copy and Call or Transact were invoked on it, an additional GPO listenLoop would be spawned. Move the lazy initialization to GasPriceOracle instead so the same GPO instance is shared among all created XEths. Fixes ethereum#1317 Might help with ethereum#1930
1 parent 77878f7 commit ae1b5b3

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)