//go:build !wasm// Copyright (c) HashiCorp, Inc// SPDX-License-Identifier: MPL-2.0package metadbimport ()const (// FileName is the default file name for the bolt db file.FileName = "wal-meta.db"// *Bucket are the names used for internal bolt bucketsMetaBucket = "wal-meta"StableBucket = "stable"// We just need one key for now so use the byte 'm' for meta arbitrarily.MetaKey = "m")var (// ErrUnintialized is returned when any call is made before Load has opened // the DB file.ErrUnintialized = errors.New("uninitialized"))// BoltMetaDB implements types.MetaStore using BoltDB as a reliable persistent// store. See repo README for reasons for this design choice and performance// implications.typeBoltMetaDBstruct { dir string db *bbolt.DB}func ( *BoltMetaDB) ( string) error {if .dir != "" && .dir != {returnfmt.Errorf("can't load dir %s, already open in dir %s", , .dir) }if .db != nil {returnnil } := filepath.Join(, FileName) := func() error { , := bbolt.Open(, 0644, nil)if != nil {returnfmt.Errorf("failed to open %s: %w", FileName, ) } .db = .dir = returnnil }// BoltDB can get stuck in invalid states if we crash while it's initializing. // We can't distinguish those as safe to just wipe it and start again because // we don't know for sure if it's failing due to bad init or later corruption // (which would loose data if we just wipe and start over). So to ensure // initial creation of the WAL is as crash-safe as possible we will manually // detect we have an atomic init procedure: // 1. Check if file exits already. If yes, skip init and just open it. // 2. Delete any existing DB file with tmp name // 3. Creat a new BoltDB that is empty and has the buckets with a temp name. // 4. Once that's committed, rename to final name and Fsync parent dir , := os.Stat()if == nil {// File exists, just open itreturn () }if !errors.Is(, os.ErrNotExist) {// Unknown err just return thatreturnfmt.Errorf("failed to stat %s: %w", FileName, ) }// File doesn't exist, initialize a new DB in a crash-safe wayif := safeInitBoltDB(); != nil {returnfmt.Errorf("failed initializing meta DB: %w", ) }// All good, now open it!return ()}func safeInitBoltDB( string) error { := filepath.Join(, FileName+".tmp")// Delete any old attempts to init that were unsuccessfulif := os.RemoveAll(); != nil {return }// Open bolt DB at tmp file name , := bbolt.Open(, 0644, nil)if != nil {return } , := .Begin(true)defer .Rollback()if != nil {return } _, = .CreateBucket([]byte(MetaBucket))if != nil {return } _, = .CreateBucket([]byte(StableBucket))if != nil {return }if := .Commit(); != nil {return }// Close the file ready to rename into place and re-open. This probably isn't // necessary but it make it easier to reason about this code path being // totally separate from the common case.if := .Close(); != nil {return }// We created the DB OK. Now rename it to the final name.if := os.Rename(, filepath.Join(, FileName)); != nil {return }// And Fsync that parent dir to make sure the new new file with it's new name // is persisted! , := os.Open()if != nil {return } = .Sync() := .Close()if != nil {return }return}// Load loads the existing persisted state. If there is no existing state// implementations are expected to create initialize new storage and return an// empty state.func ( *BoltMetaDB) ( string) (types.PersistentState, error) {vartypes.PersistentStateif := .ensureOpen(); != nil {return , } , := .db.Begin(false)if != nil {return , }defer .Rollback() := .Bucket([]byte(MetaBucket))// We just need one key for now so use the byte 'm' for meta arbitrarily. := .Get([]byte(MetaKey))if == nil {// This is valid it's an "empty" log that will be initialized by the WAL.return , nil }if := json.Unmarshal(, &); != nil {return , fmt.Errorf("%w: failed to parse persisted state: %s", types.ErrCorrupt, ) }return , nil}// CommitState must atomically replace all persisted metadata in the current// store with the set provided. It must not return until the data is persisted// durably and in a crash-safe way otherwise the guarantees of the WAL will be// compromised. The WAL will only ever call this in a single thread at one// time and it will never be called concurrently with Load however it may be// called concurrently with Get/SetStable operations.func ( *BoltMetaDB) ( types.PersistentState) error {if .db == nil {returnErrUnintialized } , := json.Marshal()if != nil {returnfmt.Errorf("failed to encode persisted state: %w", ) } , := .db.Begin(true)if != nil {return }defer .Rollback() := .Bucket([]byte(MetaBucket))if := .Put([]byte(MetaKey), ); != nil {return }return .Commit()}// Close implements io.Closerfunc ( *BoltMetaDB) () error {if .db == nil {returnnil } := .db.Close() .db = nilreturn}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.