github.com/MetalBlockchain/metalgo@v1.11.9/database/corruptabledb/db.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package corruptabledb
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"sync"
    10  
    11  	"github.com/MetalBlockchain/metalgo/database"
    12  )
    13  
    14  var (
    15  	_ database.Database = (*Database)(nil)
    16  	_ database.Batch    = (*batch)(nil)
    17  )
    18  
    19  // CorruptableDB is a wrapper around Database
    20  // it prevents any future calls in case of a corruption occurs
    21  type Database struct {
    22  	database.Database
    23  
    24  	// initialError stores the error other than "not found" or "closed" while
    25  	// performing a db operation. If not nil, Has, Get, Put, Delete and batch
    26  	// writes will fail with initialError.
    27  	errorLock    sync.RWMutex
    28  	initialError error
    29  }
    30  
    31  // New returns a new prefixed database
    32  func New(db database.Database) *Database {
    33  	return &Database{Database: db}
    34  }
    35  
    36  // Has returns if the key is set in the database
    37  func (db *Database) Has(key []byte) (bool, error) {
    38  	if err := db.corrupted(); err != nil {
    39  		return false, err
    40  	}
    41  	has, err := db.Database.Has(key)
    42  	return has, db.handleError(err)
    43  }
    44  
    45  // Get returns the value the key maps to in the database
    46  func (db *Database) Get(key []byte) ([]byte, error) {
    47  	if err := db.corrupted(); err != nil {
    48  		return nil, err
    49  	}
    50  	value, err := db.Database.Get(key)
    51  	return value, db.handleError(err)
    52  }
    53  
    54  // Put sets the value of the provided key to the provided value
    55  func (db *Database) Put(key []byte, value []byte) error {
    56  	if err := db.corrupted(); err != nil {
    57  		return err
    58  	}
    59  	return db.handleError(db.Database.Put(key, value))
    60  }
    61  
    62  // Delete removes the key from the database
    63  func (db *Database) Delete(key []byte) error {
    64  	if err := db.corrupted(); err != nil {
    65  		return err
    66  	}
    67  	return db.handleError(db.Database.Delete(key))
    68  }
    69  
    70  func (db *Database) Compact(start []byte, limit []byte) error {
    71  	return db.handleError(db.Database.Compact(start, limit))
    72  }
    73  
    74  func (db *Database) Close() error {
    75  	return db.handleError(db.Database.Close())
    76  }
    77  
    78  func (db *Database) HealthCheck(ctx context.Context) (interface{}, error) {
    79  	if err := db.corrupted(); err != nil {
    80  		return nil, err
    81  	}
    82  	return db.Database.HealthCheck(ctx)
    83  }
    84  
    85  func (db *Database) NewBatch() database.Batch {
    86  	return &batch{
    87  		Batch: db.Database.NewBatch(),
    88  		db:    db,
    89  	}
    90  }
    91  
    92  func (db *Database) NewIterator() database.Iterator {
    93  	return &iterator{
    94  		Iterator: db.Database.NewIterator(),
    95  		db:       db,
    96  	}
    97  }
    98  
    99  func (db *Database) NewIteratorWithStart(start []byte) database.Iterator {
   100  	return &iterator{
   101  		Iterator: db.Database.NewIteratorWithStart(start),
   102  		db:       db,
   103  	}
   104  }
   105  
   106  func (db *Database) NewIteratorWithPrefix(prefix []byte) database.Iterator {
   107  	return &iterator{
   108  		Iterator: db.Database.NewIteratorWithPrefix(prefix),
   109  		db:       db,
   110  	}
   111  }
   112  
   113  func (db *Database) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
   114  	return &iterator{
   115  		Iterator: db.Database.NewIteratorWithStartAndPrefix(start, prefix),
   116  		db:       db,
   117  	}
   118  }
   119  
   120  func (db *Database) corrupted() error {
   121  	db.errorLock.RLock()
   122  	defer db.errorLock.RUnlock()
   123  
   124  	return db.initialError
   125  }
   126  
   127  func (db *Database) handleError(err error) error {
   128  	switch err {
   129  	case nil, database.ErrNotFound, database.ErrClosed:
   130  	// If we get an error other than "not found" or "closed", disallow future
   131  	// database operations to avoid possible corruption
   132  	default:
   133  		db.errorLock.Lock()
   134  		defer db.errorLock.Unlock()
   135  
   136  		// Set the initial error to the first unexpected error. Don't call
   137  		// corrupted() here since it would deadlock.
   138  		if db.initialError == nil {
   139  			db.initialError = fmt.Errorf("closed to avoid possible corruption, init error: %w", err)
   140  		}
   141  	}
   142  	return err
   143  }
   144  
   145  // batch is a wrapper around the batch to contain sizes.
   146  type batch struct {
   147  	database.Batch
   148  	db *Database
   149  }
   150  
   151  // Write flushes any accumulated data to disk.
   152  func (b *batch) Write() error {
   153  	if err := b.db.corrupted(); err != nil {
   154  		return err
   155  	}
   156  	return b.db.handleError(b.Batch.Write())
   157  }
   158  
   159  type iterator struct {
   160  	database.Iterator
   161  	db *Database
   162  }
   163  
   164  func (it *iterator) Next() bool {
   165  	if err := it.db.corrupted(); err != nil {
   166  		return false
   167  	}
   168  	val := it.Iterator.Next()
   169  	_ = it.db.handleError(it.Iterator.Error())
   170  	return val
   171  }
   172  
   173  func (it *iterator) Error() error {
   174  	if err := it.db.corrupted(); err != nil {
   175  		return err
   176  	}
   177  	return it.db.handleError(it.Iterator.Error())
   178  }