mirror of
https://github.com/luxfi/geth.git
synced 2026-10-30 15:05:39 +00:00
freezer_shared.go opens each table with loadMetadata and refreshes it with metadata.reload, and chain_freezer.go's constructor takes a shared flag and a freeze threshold. Neither the metadata functions nor Open's side of that constructor were committed, so the package did not build. loadMetadata reads a table's metadata without ever writing to it. An empty file means the writer has created the table but not yet described it, which a reader waits out rather than repairs from another process. reload re-reads the record and keeps the values it already has if it catches the writer mid-rewrite: the previous tail is a better answer than an error, and the next pass picks up the new one. Open gains AncientShared and FreezeThreshold. A node reading someone else's store freezes nothing, so it runs prune in place of freeze, dropping from its own key-value store whatever the shared store already holds. Co-authored-by: Hanzo Dev <[email protected]>
225 lines
6.8 KiB
Go
225 lines
6.8 KiB
Go
// 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 rawdb
|
||
|
||
import (
|
||
"errors"
|
||
"io"
|
||
"math"
|
||
"os"
|
||
|
||
"github.com/luxfi/geth/rlp"
|
||
log "github.com/luxfi/log"
|
||
)
|
||
|
||
const (
|
||
freezerTableV1 = 1 // Initial version of metadata struct
|
||
freezerTableV2 = 2 // Add field: 'flushOffset'
|
||
freezerVersion = freezerTableV2 // The current used version
|
||
)
|
||
|
||
// freezerTableMeta is a collection of additional properties that describe the
|
||
// freezer table. These properties are designed with error resilience, allowing
|
||
// them to be automatically corrected after an error occurs without significantly
|
||
// impacting overall correctness.
|
||
type freezerTableMeta struct {
|
||
file *os.File // file handler of metadata
|
||
version uint16 // version descriptor of the freezer table
|
||
|
||
// virtualTail represents the number of items marked as deleted. It is
|
||
// calculated as the sum of items removed from the table and the items
|
||
// hidden within the table, and should never be less than the "actual
|
||
// tail".
|
||
//
|
||
// If lost due to a crash or other reasons, it will be reset to the number
|
||
// of items deleted from the table, causing the previously hidden items
|
||
// to become visible, which is an acceptable consequence.
|
||
virtualTail uint64
|
||
|
||
// flushOffset represents the offset in the index file up to which the index
|
||
// items along with the corresponding data items in data files has been flushed
|
||
// (fsync’d) to disk. Beyond this offset, data integrity is not guaranteed,
|
||
// the extra index items along with the associated data items should be removed
|
||
// during the startup.
|
||
//
|
||
// The principle is that all data items above the flush offset are considered
|
||
// volatile and should be recoverable if they are discarded after the unclean
|
||
// shutdown. If data integrity is required, manually force a sync of the
|
||
// freezer before proceeding with further operations (e.g. do freezer.Sync()
|
||
// first and then write data to key value store in some circumstances).
|
||
//
|
||
// The offset could be moved forward by applying sync operation, or be moved
|
||
// backward in cases of head/tail truncation, etc.
|
||
flushOffset int64
|
||
}
|
||
|
||
// decodeV1 attempts to decode the metadata structure in v1 format. If fails or
|
||
// the result is incompatible, nil is returned.
|
||
func decodeV1(file *os.File) *freezerTableMeta {
|
||
_, err := file.Seek(0, io.SeekStart)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
type obj struct {
|
||
Version uint16
|
||
Tail uint64
|
||
}
|
||
var o obj
|
||
if err := rlp.Decode(file, &o); err != nil {
|
||
return nil
|
||
}
|
||
if o.Version != freezerTableV1 {
|
||
return nil
|
||
}
|
||
return &freezerTableMeta{
|
||
file: file,
|
||
version: o.Version,
|
||
virtualTail: o.Tail,
|
||
}
|
||
}
|
||
|
||
// decodeV2 attempts to decode the metadata structure in v2 format. If fails or
|
||
// the result is incompatible, nil is returned.
|
||
func decodeV2(file *os.File) *freezerTableMeta {
|
||
_, err := file.Seek(0, io.SeekStart)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
type obj struct {
|
||
Version uint16
|
||
Tail uint64
|
||
Offset uint64
|
||
}
|
||
var o obj
|
||
if err := rlp.Decode(file, &o); err != nil {
|
||
return nil
|
||
}
|
||
if o.Version != freezerTableV2 {
|
||
return nil
|
||
}
|
||
if o.Offset > math.MaxInt64 {
|
||
log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name())
|
||
return nil
|
||
}
|
||
return &freezerTableMeta{
|
||
file: file,
|
||
version: freezerTableV2,
|
||
virtualTail: o.Tail,
|
||
flushOffset: int64(o.Offset),
|
||
}
|
||
}
|
||
|
||
// loadMetadata reads the metadata without ever writing to the file. A shared
|
||
// reader uses it: an empty metadata file means the writer has created the table
|
||
// but not yet described it, which is a state to read again later rather than to
|
||
// repair from another process.
|
||
func loadMetadata(file *os.File) (*freezerTableMeta, error) {
|
||
stat, err := file.Stat()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if stat.Size() == 0 {
|
||
return &freezerTableMeta{file: file, version: freezerVersion}, nil
|
||
}
|
||
if m := decodeV2(file); m != nil {
|
||
return m, nil
|
||
}
|
||
if m := decodeV1(file); m != nil {
|
||
return m, nil
|
||
}
|
||
return nil, errors.New("failed to decode metadata")
|
||
}
|
||
|
||
// reload re-reads the metadata from disk, keeping the values it already has if
|
||
// the file is momentarily undecodable. The writer rewrites the whole record in
|
||
// place, so a reader can catch it mid-rewrite; the previous tail is a better
|
||
// answer than an error, and the next pass picks up the new one.
|
||
func (m *freezerTableMeta) reload() {
|
||
if n := decodeV2(m.file); n != nil {
|
||
m.version, m.virtualTail, m.flushOffset = n.version, n.virtualTail, n.flushOffset
|
||
return
|
||
}
|
||
if n := decodeV1(m.file); n != nil {
|
||
m.version, m.virtualTail = n.version, n.virtualTail
|
||
}
|
||
}
|
||
|
||
// newMetadata initializes the metadata object, either by loading it from the file
|
||
// or by constructing a new one from scratch.
|
||
func newMetadata(file *os.File) (*freezerTableMeta, error) {
|
||
stat, err := file.Stat()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if stat.Size() == 0 {
|
||
m := &freezerTableMeta{
|
||
file: file,
|
||
version: freezerTableV2,
|
||
virtualTail: 0,
|
||
flushOffset: 0,
|
||
}
|
||
if err := m.write(true); err != nil {
|
||
return nil, err
|
||
}
|
||
return m, nil
|
||
}
|
||
if m := decodeV2(file); m != nil {
|
||
return m, nil
|
||
}
|
||
if m := decodeV1(file); m != nil {
|
||
return m, nil // legacy metadata
|
||
}
|
||
return nil, errors.New("failed to decode metadata")
|
||
}
|
||
|
||
// setVirtualTail sets the virtual tail and flushes the metadata if sync is true.
|
||
func (m *freezerTableMeta) setVirtualTail(tail uint64, sync bool) error {
|
||
m.virtualTail = tail
|
||
return m.write(sync)
|
||
}
|
||
|
||
// setFlushOffset sets the flush offset and flushes the metadata if sync is true.
|
||
func (m *freezerTableMeta) setFlushOffset(offset int64, sync bool) error {
|
||
m.flushOffset = offset
|
||
return m.write(sync)
|
||
}
|
||
|
||
// write flushes the content of metadata into file and performs a fsync if required.
|
||
func (m *freezerTableMeta) write(sync bool) error {
|
||
type obj struct {
|
||
Version uint16
|
||
Tail uint64
|
||
Offset uint64
|
||
}
|
||
var o obj
|
||
o.Version = freezerVersion // forcibly use the current version
|
||
o.Tail = m.virtualTail
|
||
o.Offset = uint64(m.flushOffset)
|
||
|
||
_, err := m.file.Seek(0, io.SeekStart)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := rlp.Encode(m.file, &o); err != nil {
|
||
return err
|
||
}
|
||
if !sync {
|
||
return nil
|
||
}
|
||
return m.file.Sync()
|
||
}
|