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 }