mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-04-10 03:47:30 +00:00
Seems the `signal.result` was not sent back in shorten case, this will cause a deadlock. --------- Signed-off-by: jsvisa <delweng@gmail.com> Co-authored-by: Gary Rong <garyrong0905@gmail.com>
677 lines
20 KiB
Go
677 lines
20 KiB
Go
// Copyright 2025 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 pathdb
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
const (
|
|
// The batch size for reading state histories
|
|
historyReadBatch = 1000
|
|
|
|
stateIndexV0 = uint8(0) // initial version of state index structure
|
|
stateIndexVersion = stateIndexV0 // the current state index version
|
|
)
|
|
|
|
type indexMetadata struct {
|
|
Version uint8
|
|
Last uint64
|
|
}
|
|
|
|
func loadIndexMetadata(db ethdb.KeyValueReader) *indexMetadata {
|
|
blob := rawdb.ReadStateHistoryIndexMetadata(db)
|
|
if len(blob) == 0 {
|
|
return nil
|
|
}
|
|
var m indexMetadata
|
|
if err := rlp.DecodeBytes(blob, &m); err != nil {
|
|
log.Error("Failed to decode index metadata", "err", err)
|
|
return nil
|
|
}
|
|
return &m
|
|
}
|
|
|
|
func storeIndexMetadata(db ethdb.KeyValueWriter, last uint64) {
|
|
var m indexMetadata
|
|
m.Version = stateIndexVersion
|
|
m.Last = last
|
|
blob, err := rlp.EncodeToBytes(m)
|
|
if err != nil {
|
|
log.Crit("Failed to encode index metadata", "err", err)
|
|
}
|
|
rawdb.WriteStateHistoryIndexMetadata(db, blob)
|
|
}
|
|
|
|
// batchIndexer is a structure designed to perform batch indexing or unindexing
|
|
// of state histories atomically.
|
|
type batchIndexer struct {
|
|
accounts map[common.Hash][]uint64 // History ID list, Keyed by the hash of account address
|
|
storages map[common.Hash]map[common.Hash][]uint64 // History ID list, Keyed by the hash of account address and the hash of raw storage key
|
|
counter int // The counter of processed states
|
|
delete bool // Index or unindex mode
|
|
lastID uint64 // The ID of latest processed history
|
|
db ethdb.KeyValueStore
|
|
}
|
|
|
|
// newBatchIndexer constructs the batch indexer with the supplied mode.
|
|
func newBatchIndexer(db ethdb.KeyValueStore, delete bool) *batchIndexer {
|
|
return &batchIndexer{
|
|
accounts: make(map[common.Hash][]uint64),
|
|
storages: make(map[common.Hash]map[common.Hash][]uint64),
|
|
delete: delete,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// process iterates through the accounts and their associated storage slots in the
|
|
// state history, tracking the mapping between state and history IDs.
|
|
func (b *batchIndexer) process(h *history, historyID uint64) error {
|
|
for _, address := range h.accountList {
|
|
addrHash := crypto.Keccak256Hash(address.Bytes())
|
|
b.counter += 1
|
|
b.accounts[addrHash] = append(b.accounts[addrHash], historyID)
|
|
|
|
for _, slotKey := range h.storageList[address] {
|
|
b.counter += 1
|
|
if _, ok := b.storages[addrHash]; !ok {
|
|
b.storages[addrHash] = make(map[common.Hash][]uint64)
|
|
}
|
|
// The hash of the storage slot key is used as the identifier because the
|
|
// legacy history does not include the raw storage key, therefore, the
|
|
// conversion from storage key to hash is necessary for non-v0 histories.
|
|
slotHash := slotKey
|
|
if h.meta.version != stateHistoryV0 {
|
|
slotHash = crypto.Keccak256Hash(slotKey.Bytes())
|
|
}
|
|
b.storages[addrHash][slotHash] = append(b.storages[addrHash][slotHash], historyID)
|
|
}
|
|
}
|
|
b.lastID = historyID
|
|
return b.finish(false)
|
|
}
|
|
|
|
// finish writes the accumulated state indexes into the disk if either the
|
|
// memory limitation is reached or it's requested forcibly.
|
|
func (b *batchIndexer) finish(force bool) error {
|
|
if b.counter == 0 {
|
|
return nil
|
|
}
|
|
if !force && b.counter < historyIndexBatch {
|
|
return nil
|
|
}
|
|
var (
|
|
batch = b.db.NewBatch()
|
|
batchMu sync.RWMutex
|
|
storages int
|
|
start = time.Now()
|
|
eg errgroup.Group
|
|
)
|
|
eg.SetLimit(runtime.NumCPU())
|
|
|
|
for addrHash, idList := range b.accounts {
|
|
eg.Go(func() error {
|
|
if !b.delete {
|
|
iw, err := newIndexWriter(b.db, newAccountIdent(addrHash))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, n := range idList {
|
|
if err := iw.append(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
batchMu.Lock()
|
|
iw.finish(batch)
|
|
batchMu.Unlock()
|
|
} else {
|
|
id, err := newIndexDeleter(b.db, newAccountIdent(addrHash))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, n := range idList {
|
|
if err := id.pop(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
batchMu.Lock()
|
|
id.finish(batch)
|
|
batchMu.Unlock()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
for addrHash, slots := range b.storages {
|
|
storages += len(slots)
|
|
for storageHash, idList := range slots {
|
|
eg.Go(func() error {
|
|
if !b.delete {
|
|
iw, err := newIndexWriter(b.db, newStorageIdent(addrHash, storageHash))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, n := range idList {
|
|
if err := iw.append(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
batchMu.Lock()
|
|
iw.finish(batch)
|
|
batchMu.Unlock()
|
|
} else {
|
|
id, err := newIndexDeleter(b.db, newStorageIdent(addrHash, storageHash))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, n := range idList {
|
|
if err := id.pop(n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
batchMu.Lock()
|
|
id.finish(batch)
|
|
batchMu.Unlock()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
if err := eg.Wait(); err != nil {
|
|
return err
|
|
}
|
|
// Update the position of last indexed state history
|
|
if !b.delete {
|
|
storeIndexMetadata(batch, b.lastID)
|
|
} else {
|
|
if b.lastID == 1 {
|
|
rawdb.DeleteStateHistoryIndexMetadata(batch)
|
|
} else {
|
|
storeIndexMetadata(batch, b.lastID-1)
|
|
}
|
|
}
|
|
if err := batch.Write(); err != nil {
|
|
return err
|
|
}
|
|
log.Debug("Committed batch indexer", "accounts", len(b.accounts), "storages", storages, "records", b.counter, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
b.counter = 0
|
|
b.accounts = make(map[common.Hash][]uint64)
|
|
b.storages = make(map[common.Hash]map[common.Hash][]uint64)
|
|
return nil
|
|
}
|
|
|
|
// indexSingle processes the state history with the specified ID for indexing.
|
|
func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader) error {
|
|
start := time.Now()
|
|
defer func() {
|
|
indexHistoryTimer.UpdateSince(start)
|
|
}()
|
|
|
|
metadata := loadIndexMetadata(db)
|
|
if metadata == nil || metadata.Last+1 != historyID {
|
|
last := "null"
|
|
if metadata != nil {
|
|
last = fmt.Sprintf("%v", metadata.Last)
|
|
}
|
|
return fmt.Errorf("history indexing is out of order, last: %s, requested: %d", last, historyID)
|
|
}
|
|
h, err := readHistory(freezer, historyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b := newBatchIndexer(db, false)
|
|
if err := b.process(h, historyID); err != nil {
|
|
return err
|
|
}
|
|
if err := b.finish(true); err != nil {
|
|
return err
|
|
}
|
|
log.Debug("Indexed state history", "id", historyID, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
return nil
|
|
}
|
|
|
|
// unindexSingle processes the state history with the specified ID for unindexing.
|
|
func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader) error {
|
|
start := time.Now()
|
|
defer func() {
|
|
unindexHistoryTimer.UpdateSince(start)
|
|
}()
|
|
|
|
metadata := loadIndexMetadata(db)
|
|
if metadata == nil || metadata.Last != historyID {
|
|
last := "null"
|
|
if metadata != nil {
|
|
last = fmt.Sprintf("%v", metadata.Last)
|
|
}
|
|
return fmt.Errorf("history unindexing is out of order, last: %s, requested: %d", last, historyID)
|
|
}
|
|
h, err := readHistory(freezer, historyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b := newBatchIndexer(db, true)
|
|
if err := b.process(h, historyID); err != nil {
|
|
return err
|
|
}
|
|
if err := b.finish(true); err != nil {
|
|
return err
|
|
}
|
|
log.Debug("Unindexed state history", "id", historyID, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
return nil
|
|
}
|
|
|
|
type interruptSignal struct {
|
|
newLastID uint64
|
|
result chan error
|
|
}
|
|
|
|
// indexIniter is responsible for completing the indexing of remaining state
|
|
// histories in batch. It runs as a one-time background thread and terminates
|
|
// once all available state histories are indexed.
|
|
//
|
|
// Afterward, new state histories should be indexed synchronously alongside
|
|
// the state data itself, ensuring both the history and its index are available.
|
|
// If a state history is removed due to a rollback, the associated indexes should
|
|
// be unmarked accordingly.
|
|
type indexIniter struct {
|
|
disk ethdb.KeyValueStore
|
|
freezer ethdb.AncientStore
|
|
interrupt chan *interruptSignal
|
|
done chan struct{}
|
|
closed chan struct{}
|
|
|
|
// indexing progress
|
|
indexed atomic.Uint64 // the id of latest indexed state
|
|
last atomic.Uint64 // the id of the target state to be indexed
|
|
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newIndexIniter(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastID uint64) *indexIniter {
|
|
initer := &indexIniter{
|
|
disk: disk,
|
|
freezer: freezer,
|
|
interrupt: make(chan *interruptSignal),
|
|
done: make(chan struct{}),
|
|
closed: make(chan struct{}),
|
|
}
|
|
// Load indexing progress
|
|
initer.last.Store(lastID)
|
|
metadata := loadIndexMetadata(disk)
|
|
if metadata != nil {
|
|
initer.indexed.Store(metadata.Last)
|
|
}
|
|
|
|
// Launch background indexer
|
|
initer.wg.Add(1)
|
|
go initer.run(lastID)
|
|
return initer
|
|
}
|
|
|
|
func (i *indexIniter) close() {
|
|
select {
|
|
case <-i.closed:
|
|
return
|
|
default:
|
|
close(i.closed)
|
|
i.wg.Wait()
|
|
}
|
|
}
|
|
|
|
func (i *indexIniter) inited() bool {
|
|
select {
|
|
case <-i.closed:
|
|
return false
|
|
case <-i.done:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (i *indexIniter) remain() uint64 {
|
|
select {
|
|
case <-i.closed:
|
|
return 0
|
|
case <-i.done:
|
|
return 0
|
|
default:
|
|
last, indexed := i.last.Load(), i.indexed.Load()
|
|
if last < indexed {
|
|
log.Error("Invalid state indexing range", "last", last, "indexed", indexed)
|
|
return 0
|
|
}
|
|
return last - indexed
|
|
}
|
|
}
|
|
|
|
func (i *indexIniter) run(lastID uint64) {
|
|
defer i.wg.Done()
|
|
|
|
// Launch background indexing thread
|
|
var (
|
|
done = make(chan struct{})
|
|
interrupt = new(atomic.Int32)
|
|
|
|
// checkDone indicates whether all requested state histories
|
|
// have been fully indexed.
|
|
checkDone = func() bool {
|
|
metadata := loadIndexMetadata(i.disk)
|
|
return metadata != nil && metadata.Last == lastID
|
|
}
|
|
)
|
|
go i.index(done, interrupt, lastID)
|
|
|
|
for {
|
|
select {
|
|
case signal := <-i.interrupt:
|
|
// The indexing limit can only be extended or shortened continuously.
|
|
newLastID := signal.newLastID
|
|
if newLastID != lastID+1 && newLastID != lastID-1 {
|
|
signal.result <- fmt.Errorf("invalid history id, last: %d, got: %d", lastID, newLastID)
|
|
continue
|
|
}
|
|
i.last.Store(newLastID) // update indexing range
|
|
|
|
// The index limit is extended by one, update the limit without
|
|
// interrupting the current background process.
|
|
if newLastID == lastID+1 {
|
|
lastID = newLastID
|
|
signal.result <- nil
|
|
log.Debug("Extended state history range", "last", lastID)
|
|
continue
|
|
}
|
|
// The index limit is shortened by one, interrupt the current background
|
|
// process and relaunch with new target.
|
|
interrupt.Store(1)
|
|
<-done
|
|
|
|
// If all state histories, including the one to be reverted, have
|
|
// been fully indexed, unindex it here and shut down the initializer.
|
|
if checkDone() {
|
|
log.Info("Truncate the extra history", "id", lastID)
|
|
if err := unindexSingle(lastID, i.disk, i.freezer); err != nil {
|
|
signal.result <- err
|
|
return
|
|
}
|
|
close(i.done)
|
|
signal.result <- nil
|
|
log.Info("State histories have been fully indexed", "last", lastID-1)
|
|
return
|
|
}
|
|
// Adjust the indexing target and relaunch the process
|
|
lastID = newLastID
|
|
signal.result <- nil
|
|
|
|
done, interrupt = make(chan struct{}), new(atomic.Int32)
|
|
go i.index(done, interrupt, lastID)
|
|
log.Debug("Shortened state history range", "last", lastID)
|
|
|
|
case <-done:
|
|
if checkDone() {
|
|
close(i.done)
|
|
log.Info("State histories have been fully indexed", "last", lastID)
|
|
return
|
|
}
|
|
// Relaunch the background runner if some tasks are left
|
|
done, interrupt = make(chan struct{}), new(atomic.Int32)
|
|
go i.index(done, interrupt, lastID)
|
|
|
|
case <-i.closed:
|
|
interrupt.Store(1)
|
|
log.Info("Waiting background history index initer to exit")
|
|
<-done
|
|
|
|
if checkDone() {
|
|
close(i.done)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// next returns the ID of the next state history to be indexed.
|
|
func (i *indexIniter) next() (uint64, error) {
|
|
tail, err := i.freezer.Tail()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
tailID := tail + 1 // compute the id of the oldest history
|
|
|
|
// Start indexing from scratch if nothing has been indexed
|
|
metadata := loadIndexMetadata(i.disk)
|
|
if metadata == nil {
|
|
log.Debug("Initialize state history indexing from scratch", "id", tailID)
|
|
return tailID, nil
|
|
}
|
|
// Resume indexing from the last interrupted position
|
|
if metadata.Last+1 >= tailID {
|
|
log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID)
|
|
return metadata.Last + 1, nil
|
|
}
|
|
// History has been shortened without indexing. Discard the gapped segment
|
|
// in the history and shift to the first available element.
|
|
//
|
|
// The missing indexes corresponding to the gapped histories won't be visible.
|
|
// It's fine to leave them unindexed.
|
|
log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID)
|
|
return tailID, nil
|
|
}
|
|
|
|
func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID uint64) {
|
|
defer close(done)
|
|
|
|
beginID, err := i.next()
|
|
if err != nil {
|
|
log.Error("Failed to find next state history for indexing", "err", err)
|
|
return
|
|
}
|
|
// All available state histories have been indexed, and the last indexed one
|
|
// exceeds the most recent available state history. This situation may occur
|
|
// when the state is reverted manually (chain.SetHead) or the deep reorg is
|
|
// encountered. In such cases, no indexing should be scheduled.
|
|
if beginID > lastID {
|
|
if lastID == 0 && beginID == 1 {
|
|
// Initialize the indexing flag if the state history is empty by
|
|
// using zero as the disk layer ID. This is a common case that
|
|
// can occur after snap sync.
|
|
//
|
|
// This step is essential to avoid spinning up indexing thread
|
|
// endlessly until a history object is produced.
|
|
storeIndexMetadata(i.disk, 0)
|
|
log.Info("Initialized history indexing flag")
|
|
} else {
|
|
log.Debug("State history is fully indexed", "last", lastID)
|
|
}
|
|
return
|
|
}
|
|
log.Info("Start history indexing", "beginID", beginID, "lastID", lastID)
|
|
|
|
var (
|
|
current = beginID
|
|
start = time.Now()
|
|
logged = time.Now()
|
|
batch = newBatchIndexer(i.disk, false)
|
|
)
|
|
for current <= lastID {
|
|
count := lastID - current + 1
|
|
if count > historyReadBatch {
|
|
count = historyReadBatch
|
|
}
|
|
histories, err := readHistories(i.freezer, current, count)
|
|
if err != nil {
|
|
// The history read might fall if the history is truncated from
|
|
// head due to revert operation.
|
|
log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err)
|
|
return
|
|
}
|
|
for _, h := range histories {
|
|
if err := batch.process(h, current); err != nil {
|
|
log.Error("Failed to index history", "err", err)
|
|
return
|
|
}
|
|
current += 1
|
|
|
|
// Occasionally report the indexing progress
|
|
if time.Since(logged) > time.Second*8 {
|
|
logged = time.Now()
|
|
|
|
var (
|
|
left = lastID - current + 1
|
|
done = current - beginID
|
|
speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
|
|
)
|
|
// Override the ETA if larger than the largest until now
|
|
eta := time.Duration(left/speed) * time.Millisecond
|
|
log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta))
|
|
}
|
|
}
|
|
i.indexed.Store(current - 1) // update indexing progress
|
|
|
|
// Check interruption signal and abort process if it's fired
|
|
if interrupt != nil {
|
|
if signal := interrupt.Load(); signal != 0 {
|
|
if err := batch.finish(true); err != nil {
|
|
log.Error("Failed to flush index", "err", err)
|
|
}
|
|
log.Info("State indexing interrupted")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if err := batch.finish(true); err != nil {
|
|
log.Error("Failed to flush index", "err", err)
|
|
}
|
|
log.Info("Indexed state history", "from", beginID, "to", lastID, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
}
|
|
|
|
// historyIndexer manages the indexing and unindexing of state histories,
|
|
// providing access to historical states.
|
|
//
|
|
// Upon initialization, historyIndexer starts a one-time background process
|
|
// to complete the indexing of any remaining state histories. Once this
|
|
// process is finished, all state histories are marked as fully indexed,
|
|
// enabling handling of requests for historical states. Thereafter, any new
|
|
// state histories must be indexed or unindexed synchronously, ensuring that
|
|
// the history index is created or removed along with the corresponding
|
|
// state history.
|
|
type historyIndexer struct {
|
|
initer *indexIniter
|
|
disk ethdb.KeyValueStore
|
|
freezer ethdb.AncientStore
|
|
}
|
|
|
|
// checkVersion checks whether the index data in the database matches the version.
|
|
func checkVersion(disk ethdb.KeyValueStore) {
|
|
blob := rawdb.ReadStateHistoryIndexMetadata(disk)
|
|
if len(blob) == 0 {
|
|
return
|
|
}
|
|
var m indexMetadata
|
|
err := rlp.DecodeBytes(blob, &m)
|
|
if err == nil && m.Version == stateIndexVersion {
|
|
return
|
|
}
|
|
// TODO(rjl493456442) would be better to group them into a batch.
|
|
rawdb.DeleteStateHistoryIndexMetadata(disk)
|
|
rawdb.DeleteStateHistoryIndex(disk)
|
|
|
|
version := "unknown"
|
|
if err == nil {
|
|
version = fmt.Sprintf("%d", m.Version)
|
|
}
|
|
log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion)
|
|
}
|
|
|
|
// newHistoryIndexer constructs the history indexer and launches the background
|
|
// initer to complete the indexing of any remaining state histories.
|
|
func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer {
|
|
checkVersion(disk)
|
|
return &historyIndexer{
|
|
initer: newIndexIniter(disk, freezer, lastHistoryID),
|
|
disk: disk,
|
|
freezer: freezer,
|
|
}
|
|
}
|
|
|
|
func (i *historyIndexer) close() {
|
|
i.initer.close()
|
|
}
|
|
|
|
// inited returns a flag indicating whether the existing state histories
|
|
// have been fully indexed, in other words, whether they are available
|
|
// for external access.
|
|
func (i *historyIndexer) inited() bool {
|
|
return i.initer.inited()
|
|
}
|
|
|
|
// extend sends the notification that new state history with specified ID
|
|
// has been written into the database and is ready for indexing.
|
|
func (i *historyIndexer) extend(historyID uint64) error {
|
|
signal := &interruptSignal{
|
|
newLastID: historyID,
|
|
result: make(chan error, 1),
|
|
}
|
|
select {
|
|
case <-i.initer.closed:
|
|
return errors.New("indexer is closed")
|
|
case <-i.initer.done:
|
|
return indexSingle(historyID, i.disk, i.freezer)
|
|
case i.initer.interrupt <- signal:
|
|
return <-signal.result
|
|
}
|
|
}
|
|
|
|
// shorten sends the notification that state history with specified ID
|
|
// is about to be deleted from the database and should be unindexed.
|
|
func (i *historyIndexer) shorten(historyID uint64) error {
|
|
signal := &interruptSignal{
|
|
newLastID: historyID - 1,
|
|
result: make(chan error, 1),
|
|
}
|
|
select {
|
|
case <-i.initer.closed:
|
|
return errors.New("indexer is closed")
|
|
case <-i.initer.done:
|
|
return unindexSingle(historyID, i.disk, i.freezer)
|
|
case i.initer.interrupt <- signal:
|
|
return <-signal.result
|
|
}
|
|
}
|
|
|
|
// progress returns the indexing progress made so far. It provides the number
|
|
// of states that remain unindexed.
|
|
func (i *historyIndexer) progress() (uint64, error) {
|
|
select {
|
|
case <-i.initer.closed:
|
|
return 0, errors.New("indexer is closed")
|
|
default:
|
|
return i.initer.remain(), nil
|
|
}
|
|
}
|