Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions cmd/geth/dbcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,16 +551,8 @@ func freezerInspect(ctx *cli.Context) error {
return err
}
stack, _ := makeConfigNode(ctx)
defer stack.Close()

db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close()

ancient, err := db.AncientDatadir()
if err != nil {
log.Info("Failed to retrieve ancient root", "err", err)
return err
}
ancient := stack.ResolveAncient("chaindata", ctx.String(utils.AncientFlag.Name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for this change? Directly resolve ancient datadir without involving the chain DB?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for the investigation here: #26483 (comment) , I needed to do a geth db inspect, but I didn't actually have a leveldb -- I only had a couple of index files.

This whole utils.MakeChainDatabase assumes things to be pretty well ordered, but all we use it for eventually is to help resolve the ancientdir, via db.AncientDir.

So this change has the same effect as the original code, but is more robust in case the data is not fully consistent / present.

stack.Close()
return rawdb.InspectFreezerTable(ancient, freezer, table, start, end)
}

Expand Down
3 changes: 1 addition & 2 deletions core/rawdb/chain_freezer.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,13 @@ func newChainFreezer(datadir string, namespace string, readonly bool, maxTableSi

// Close closes the chain freezer instance and terminates the background thread.
func (f *chainFreezer) Close() error {
err := f.Freezer.Close()
select {
case <-f.quit:
default:
close(f.quit)
}
f.wg.Wait()
return err
return f.Freezer.Close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

}

// freeze is a background thread that periodically checks the blockchain for any
Expand Down
55 changes: 37 additions & 18 deletions core/rawdb/freezer_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func (t *freezerTable) repair() error {
lastIndex indexEntry
contentSize int64
contentExp int64
verbose bool
)
// Read index zero, determine what file is the earliest
// and what item offset to use
Expand Down Expand Up @@ -272,17 +273,18 @@ func (t *freezerTable) repair() error {
// Keep truncating both files until they come in sync
contentExp = int64(lastIndex.offset)
for contentExp != contentSize {
verbose = true
// Truncate the head file to the last offset pointer
if contentExp < contentSize {
t.logger.Warn("Truncating dangling head", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize))
t.logger.Warn("Truncating dangling head", "indexed", contentExp, "stored", contentSize)
if err := truncateFreezerFile(t.head, contentExp); err != nil {
return err
}
contentSize = contentExp
}
// Truncate the index to point within the head file
if contentExp > contentSize {
t.logger.Warn("Truncating dangling indexes", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize))
t.logger.Warn("Truncating dangling indexes", "indexes", offsetsSize/indexEntrySize, "indexed", contentExp, "stored", contentSize)
if err := truncateFreezerFile(t.index, offsetsSize-indexEntrySize); err != nil {
return err
}
Expand Down Expand Up @@ -343,7 +345,11 @@ func (t *freezerTable) repair() error {
if err := t.preopen(); err != nil {
return err
}
t.logger.Debug("Chain freezer table opened", "items", t.items, "size", common.StorageSize(t.headBytes))
if verbose {
t.logger.Info("Chain freezer table opened", "items", t.items, "size", t.headBytes)
} else {
t.logger.Debug("Chain freezer table opened", "items", t.items, "size", common.StorageSize(t.headBytes))
}
return nil
}

Expand Down Expand Up @@ -553,21 +559,31 @@ func (t *freezerTable) Close() error {
defer t.lock.Unlock()

var errs []error
if err := t.index.Close(); err != nil {
errs = append(errs, err)
}
t.index = nil

if err := t.meta.Close(); err != nil {
errs = append(errs, err)
doClose := func(f *os.File, sync bool, close bool) {
if sync && !t.readonly {
if err := f.Sync(); err != nil {
errs = append(errs, err)
}
}
if close {
if err := f.Close(); err != nil {
errs = append(errs, err)
}
}
}
t.meta = nil

// Trying to fsync a file opened in rdonly causes "Access denied"
// error on Windows.
doClose(t.index, true, true)
doClose(t.meta, true, true)
// The preopened non-head data-files are all opened in readonly.
// The head is opened in rw-mode, so we sync it here - but since it's also
// part of t.files, it will be closed in the loop below.
doClose(t.head, true, false) // sync but do not close
for _, f := range t.files {
if err := f.Close(); err != nil {
errs = append(errs, err)
}
doClose(f, false, true) // close but do not sync
}
t.index = nil
t.meta = nil
t.head = nil

if errs != nil {
Expand Down Expand Up @@ -724,7 +740,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
defer t.lock.RUnlock()

// Ensure the table and the item are accessible
if t.index == nil || t.head == nil {
if t.index == nil || t.head == nil || t.meta == nil {
return nil, nil, errClosed
}
var (
Expand Down Expand Up @@ -869,7 +885,9 @@ func (t *freezerTable) advanceHead() error {
func (t *freezerTable) Sync() error {
t.lock.Lock()
defer t.lock.Unlock()

if t.index == nil || t.head == nil || t.meta == nil {
return errClosed
}
var err error
trackError := func(e error) {
if e != nil && err == nil {
Expand Down Expand Up @@ -900,7 +918,8 @@ func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) {
fmt.Fprintf(w, "Failed to decode freezer table %v\n", err)
return
}
fmt.Fprintf(w, "Version %d deleted %d, hidden %d\n", meta.Version, atomic.LoadUint64(&t.itemOffset), atomic.LoadUint64(&t.itemHidden))
fmt.Fprintf(w, "Version %d count %d, deleted %d, hidden %d\n", meta.Version,
atomic.LoadUint64(&t.items), atomic.LoadUint64(&t.itemOffset), atomic.LoadUint64(&t.itemHidden))

buf := make([]byte, indexEntrySize)

Expand Down
22 changes: 22 additions & 0 deletions core/rawdb/freezer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,25 @@ func TestRenameWindows(t *testing.T) {
t.Errorf("unexpected file contents. Got %v\n", buf)
}
}

func TestFreezerCloseSync(t *testing.T) {
t.Parallel()
f, _ := newFreezerForTesting(t, map[string]bool{"a": true, "b": true})
defer f.Close()

// Now, close and sync. This mimics the behaviour if the node is shut down,
// just as the chain freezer is writing.
// 1: thread-1: chain treezer writes, via freezeRange (holds lock)
// 2: thread-2: Close called, waits for write to finish
// 3: thread-1: finishes writing, releases lock
// 4: thread-2: obtains lock, completes Close()
// 5: thread-1: calls f.Sync()
if err := f.Close(); err != nil {
t.Fatal(err)
}
if err := f.Sync(); err == nil {
t.Fatalf("want error, have nil")
} else if have, want := err.Error(), "[closed closed]"; have != want {
t.Fatalf("want %v, have %v", have, want)
}
}