-
Notifications
You must be signed in to change notification settings - Fork 20.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eth/traces: add state limit (#25812)
This PR introduces a new mechanism in chain tracer for preventing creating too many trace states. The workflow of chain tracer can be divided into several parts: - state creator generates trace state in a thread - state tracer retrieves the trace state and applies the tracing on top in another thread - state collector gathers all result from state tracer and stream to users It's basically a producer-consumer model here, while if we imagine that the state producer generates states too fast, then it will lead to accumulate lots of unused states in memory. Even worse, in path-based state scheme it will only keep the latest 128 states in memory, and the newly generated state will invalidate the oldest one by marking it as stale. The solution for fixing it is to limit the speed of state generation. If there are over 128 states un-consumed in memory, then the creation will be paused until the states are be consumed properly.
- Loading branch information
1 parent
5d52a35
commit e50aeac
Showing
3 changed files
with
309 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// Copyright 2022 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package tracers | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
// stateTracker is an auxiliary tool used to cache the release functions of all | ||
// used trace states, and to determine whether the creation of trace state needs | ||
// to be paused in case there are too many states waiting for tracing. | ||
type stateTracker struct { | ||
limit int // Maximum number of states allowed waiting for tracing | ||
oldest uint64 // The number of the oldest state which is still using for trace | ||
used []bool // List of flags indicating whether the trace state has been used up | ||
releases []StateReleaseFunc // List of trace state release functions waiting to be called | ||
cond *sync.Cond | ||
lock *sync.RWMutex | ||
} | ||
|
||
// newStateTracker initializes the tracker with provided state limits and | ||
// the number of the first state that will be used. | ||
func newStateTracker(limit int, oldest uint64) *stateTracker { | ||
lock := new(sync.RWMutex) | ||
return &stateTracker{ | ||
limit: limit, | ||
oldest: oldest, | ||
used: make([]bool, limit), | ||
cond: sync.NewCond(lock), | ||
lock: lock, | ||
} | ||
} | ||
|
||
// releaseState marks the state specified by the number as released and caches | ||
// the corresponding release functions internally. | ||
func (t *stateTracker) releaseState(number uint64, release StateReleaseFunc) { | ||
t.lock.Lock() | ||
defer t.lock.Unlock() | ||
|
||
// Set the state as used, the corresponding flag is indexed by | ||
// the distance between the specified state and the oldest state | ||
// which is still using for trace. | ||
t.used[int(number-t.oldest)] = true | ||
|
||
// If the oldest state is used up, update the oldest marker by moving | ||
// it to the next state which is not used up. | ||
if number == t.oldest { | ||
var count int | ||
for _, used := range t.used { | ||
if !used { | ||
break | ||
} | ||
count += 1 | ||
} | ||
t.oldest += uint64(count) | ||
copy(t.used, t.used[count:]) | ||
|
||
// Clean up the array tail since they are useless now. | ||
for i := t.limit - count; i < t.limit; i++ { | ||
t.used[i] = false | ||
} | ||
// Fire the signal to all waiters that oldest marker is updated. | ||
t.cond.Broadcast() | ||
} | ||
t.releases = append(t.releases, release) | ||
} | ||
|
||
// callReleases invokes all cached release functions. | ||
func (t *stateTracker) callReleases() { | ||
t.lock.Lock() | ||
defer t.lock.Unlock() | ||
|
||
for _, release := range t.releases { | ||
release() | ||
} | ||
t.releases = t.releases[:0] | ||
} | ||
|
||
// wait blocks until the accumulated trace states are less than the limit. | ||
func (t *stateTracker) wait(number uint64) error { | ||
t.lock.Lock() | ||
defer t.lock.Unlock() | ||
|
||
for { | ||
if number < t.oldest { | ||
return fmt.Errorf("invalid state number %d head %d", number, t.oldest) | ||
} | ||
if number < t.oldest+uint64(t.limit) { | ||
// number is now within limit, wait over | ||
return nil | ||
} | ||
t.cond.Wait() | ||
} | ||
} |
Oops, something went wrong.