eth/filters: fix potential deadlock in filter timeout loop (#22178)
This fixes #22131 and adds a test reproducing the issue.
This commit is contained in:
@ -34,10 +34,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline
|
||||
)
|
||||
|
||||
// filter is a helper struct that holds meta information over the filter type
|
||||
// and associated subscription in the event system.
|
||||
type filter struct {
|
||||
@ -59,25 +55,28 @@ type PublicFilterAPI struct {
|
||||
events *EventSystem
|
||||
filtersMu sync.Mutex
|
||||
filters map[rpc.ID]*filter
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
|
||||
func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI {
|
||||
func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI {
|
||||
api := &PublicFilterAPI{
|
||||
backend: backend,
|
||||
chainDb: backend.ChainDb(),
|
||||
events: NewEventSystem(backend, lightMode),
|
||||
filters: make(map[rpc.ID]*filter),
|
||||
timeout: timeout,
|
||||
}
|
||||
go api.timeoutLoop()
|
||||
go api.timeoutLoop(timeout)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// timeoutLoop runs every 5 minutes and deletes filters that have not been recently used.
|
||||
// Tt is started when the api is created.
|
||||
func (api *PublicFilterAPI) timeoutLoop() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) {
|
||||
var toUninstall []*Subscription
|
||||
ticker := time.NewTicker(timeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
<-ticker.C
|
||||
@ -85,13 +84,21 @@ func (api *PublicFilterAPI) timeoutLoop() {
|
||||
for id, f := range api.filters {
|
||||
select {
|
||||
case <-f.deadline.C:
|
||||
f.s.Unsubscribe()
|
||||
toUninstall = append(toUninstall, f.s)
|
||||
delete(api.filters, id)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
api.filtersMu.Unlock()
|
||||
|
||||
// Unsubscribes are processed outside the lock to avoid the following scenario:
|
||||
// event loop attempts broadcasting events to still active filters while
|
||||
// Unsubscribe is waiting for it to process the uninstall request.
|
||||
for _, s := range toUninstall {
|
||||
s.Unsubscribe()
|
||||
}
|
||||
toUninstall = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +116,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
|
||||
)
|
||||
|
||||
api.filtersMu.Lock()
|
||||
api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
|
||||
api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub}
|
||||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
@ -179,7 +186,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
|
||||
)
|
||||
|
||||
api.filtersMu.Lock()
|
||||
api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
|
||||
api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub}
|
||||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
@ -296,7 +303,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
|
||||
}
|
||||
|
||||
api.filtersMu.Lock()
|
||||
api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub}
|
||||
api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub}
|
||||
api.filtersMu.Unlock()
|
||||
|
||||
go func() {
|
||||
@ -421,7 +428,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
|
||||
// receive timer value and reset timer
|
||||
<-f.deadline.C
|
||||
}
|
||||
f.deadline.Reset(deadline)
|
||||
f.deadline.Reset(api.timeout)
|
||||
|
||||
switch f.typ {
|
||||
case PendingTransactionsSubscription, BlocksSubscription:
|
||||
|
Reference in New Issue
Block a user