mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-05 02:45:02 +00:00
Pebble maintains a batch pool to recycle the batch object. Unfortunately batch object must be explicitly returned via `batch.Close` function. This PR extends the batch interface by adding the close function and also invoke batch.Close in some critical code paths. Memory allocation must be measured before merging this change. What's more, it's an open question that whether we should apply batch.Close as much as possible in every invocation.
396 lines
10 KiB
Go
396 lines
10 KiB
Go
// Copyright 2018 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 memorydb implements the key-value database layer based on memory maps.
|
|
package memorydb
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
)
|
|
|
|
var (
|
|
// errMemorydbClosed is returned if a memory database was already closed at the
|
|
// invocation of a data access operation.
|
|
errMemorydbClosed = errors.New("database closed")
|
|
|
|
// errMemorydbNotFound is returned if a key is requested that is not found in
|
|
// the provided memory database.
|
|
errMemorydbNotFound = errors.New("not found")
|
|
)
|
|
|
|
// Database is an ephemeral key-value store. Apart from basic data storage
|
|
// functionality it also supports batch writes and iterating over the keyspace in
|
|
// binary-alphabetical order.
|
|
type Database struct {
|
|
db map[string][]byte
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// New returns a wrapped map with all the required database interface methods
|
|
// implemented.
|
|
func New() *Database {
|
|
return &Database{
|
|
db: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
// NewWithCap returns a wrapped map pre-allocated to the provided capacity with
|
|
// all the required database interface methods implemented.
|
|
func NewWithCap(size int) *Database {
|
|
return &Database{
|
|
db: make(map[string][]byte, size),
|
|
}
|
|
}
|
|
|
|
// Close deallocates the internal map and ensures any consecutive data access op
|
|
// fails with an error.
|
|
func (db *Database) Close() error {
|
|
db.lock.Lock()
|
|
defer db.lock.Unlock()
|
|
|
|
db.db = nil
|
|
return nil
|
|
}
|
|
|
|
// Has retrieves if a key is present in the key-value store.
|
|
func (db *Database) Has(key []byte) (bool, error) {
|
|
db.lock.RLock()
|
|
defer db.lock.RUnlock()
|
|
|
|
if db.db == nil {
|
|
return false, errMemorydbClosed
|
|
}
|
|
_, ok := db.db[string(key)]
|
|
return ok, nil
|
|
}
|
|
|
|
// Get retrieves the given key if it's present in the key-value store.
|
|
func (db *Database) Get(key []byte) ([]byte, error) {
|
|
db.lock.RLock()
|
|
defer db.lock.RUnlock()
|
|
|
|
if db.db == nil {
|
|
return nil, errMemorydbClosed
|
|
}
|
|
if entry, ok := db.db[string(key)]; ok {
|
|
return common.CopyBytes(entry), nil
|
|
}
|
|
return nil, errMemorydbNotFound
|
|
}
|
|
|
|
// Put inserts the given value into the key-value store.
|
|
func (db *Database) Put(key []byte, value []byte) error {
|
|
db.lock.Lock()
|
|
defer db.lock.Unlock()
|
|
|
|
if db.db == nil {
|
|
return errMemorydbClosed
|
|
}
|
|
db.db[string(key)] = common.CopyBytes(value)
|
|
return nil
|
|
}
|
|
|
|
// Delete removes the key from the key-value store.
|
|
func (db *Database) Delete(key []byte) error {
|
|
db.lock.Lock()
|
|
defer db.lock.Unlock()
|
|
|
|
if db.db == nil {
|
|
return errMemorydbClosed
|
|
}
|
|
delete(db.db, string(key))
|
|
return nil
|
|
}
|
|
|
|
// DeleteRange deletes all of the keys (and values) in the range [start,end)
|
|
// (inclusive on start, exclusive on end). If the start is nil, it represents
|
|
// the key before all keys; if the end is nil, it represents the key after
|
|
// all keys.
|
|
func (db *Database) DeleteRange(start, end []byte) error {
|
|
db.lock.Lock()
|
|
defer db.lock.Unlock()
|
|
|
|
if db.db == nil {
|
|
return errMemorydbClosed
|
|
}
|
|
for key := range db.db {
|
|
if start != nil && key < string(start) {
|
|
continue
|
|
}
|
|
if end != nil && key >= string(end) {
|
|
continue
|
|
}
|
|
delete(db.db, key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewBatch creates a write-only key-value store that buffers changes to its host
|
|
// database until a final write is called.
|
|
func (db *Database) NewBatch() ethdb.Batch {
|
|
return &batch{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// NewBatchWithSize creates a write-only database batch with pre-allocated buffer.
|
|
func (db *Database) NewBatchWithSize(size int) ethdb.Batch {
|
|
return &batch{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// NewIterator creates a binary-alphabetical iterator over a subset
|
|
// of database content with a particular key prefix, starting at a particular
|
|
// initial key (or after, if it does not exist).
|
|
func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
|
|
db.lock.RLock()
|
|
defer db.lock.RUnlock()
|
|
|
|
var (
|
|
pr = string(prefix)
|
|
st = string(append(prefix, start...))
|
|
keys = make([]string, 0, len(db.db))
|
|
values = make([][]byte, 0, len(db.db))
|
|
)
|
|
// Collect the keys from the memory database corresponding to the given prefix
|
|
// and start
|
|
for key := range db.db {
|
|
if !strings.HasPrefix(key, pr) {
|
|
continue
|
|
}
|
|
if key >= st {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
// Sort the items and retrieve the associated values
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
values = append(values, db.db[key])
|
|
}
|
|
return &iterator{
|
|
index: -1,
|
|
keys: keys,
|
|
values: values,
|
|
}
|
|
}
|
|
|
|
// Stat returns the statistic data of the database.
|
|
func (db *Database) Stat() (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
// Compact is not supported on a memory database, but there's no need either as
|
|
// a memory database doesn't waste space anyway.
|
|
func (db *Database) Compact(start []byte, limit []byte) error {
|
|
return nil
|
|
}
|
|
|
|
// SyncKeyValue ensures that all pending writes are flushed to disk,
|
|
// guaranteeing data durability up to the point.
|
|
func (db *Database) SyncKeyValue() error {
|
|
return nil
|
|
}
|
|
|
|
// Len returns the number of entries currently present in the memory database.
|
|
//
|
|
// Note, this method is only used for testing (i.e. not public in general) and
|
|
// does not have explicit checks for closed-ness to allow simpler testing code.
|
|
func (db *Database) Len() int {
|
|
db.lock.RLock()
|
|
defer db.lock.RUnlock()
|
|
|
|
return len(db.db)
|
|
}
|
|
|
|
// keyvalue is a key-value tuple tagged with a deletion field to allow creating
|
|
// memory-database write batches.
|
|
type keyvalue struct {
|
|
key string
|
|
value []byte
|
|
delete bool
|
|
|
|
rangeFrom []byte
|
|
rangeTo []byte
|
|
}
|
|
|
|
// batch is a write-only memory batch that commits changes to its host
|
|
// database when Write is called. A batch cannot be used concurrently.
|
|
type batch struct {
|
|
db *Database
|
|
writes []keyvalue
|
|
size int
|
|
}
|
|
|
|
// Put inserts the given value into the batch for later committing.
|
|
func (b *batch) Put(key, value []byte) error {
|
|
b.writes = append(b.writes, keyvalue{key: string(key), value: common.CopyBytes(value)})
|
|
b.size += len(key) + len(value)
|
|
return nil
|
|
}
|
|
|
|
// Delete inserts the key removal into the batch for later committing.
|
|
func (b *batch) Delete(key []byte) error {
|
|
b.writes = append(b.writes, keyvalue{key: string(key), delete: true})
|
|
b.size += len(key)
|
|
return nil
|
|
}
|
|
|
|
// DeleteRange removes all keys in the range [start, end) from the batch for later committing.
|
|
func (b *batch) DeleteRange(start, end []byte) error {
|
|
b.writes = append(b.writes, keyvalue{
|
|
rangeFrom: bytes.Clone(start),
|
|
rangeTo: bytes.Clone(end),
|
|
delete: true,
|
|
})
|
|
b.size += len(start) + len(end)
|
|
return nil
|
|
}
|
|
|
|
// ValueSize retrieves the amount of data queued up for writing.
|
|
func (b *batch) ValueSize() int {
|
|
return b.size
|
|
}
|
|
|
|
// Write flushes any accumulated data to the memory database.
|
|
func (b *batch) Write() error {
|
|
b.db.lock.Lock()
|
|
defer b.db.lock.Unlock()
|
|
|
|
if b.db.db == nil {
|
|
return errMemorydbClosed
|
|
}
|
|
for _, entry := range b.writes {
|
|
if entry.delete {
|
|
if entry.key != "" {
|
|
// Single key deletion
|
|
delete(b.db.db, entry.key)
|
|
} else {
|
|
// Range deletion (inclusive of start, exclusive of end)
|
|
for key := range b.db.db {
|
|
if entry.rangeFrom != nil && key < string(entry.rangeFrom) {
|
|
continue
|
|
}
|
|
if entry.rangeTo != nil && key >= string(entry.rangeTo) {
|
|
continue
|
|
}
|
|
delete(b.db.db, key)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
b.db.db[entry.key] = entry.value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Reset resets the batch for reuse.
|
|
func (b *batch) Reset() {
|
|
b.writes = b.writes[:0]
|
|
b.size = 0
|
|
}
|
|
|
|
// Replay replays the batch contents.
|
|
func (b *batch) Replay(w ethdb.KeyValueWriter) error {
|
|
for _, entry := range b.writes {
|
|
if entry.delete {
|
|
if entry.key != "" {
|
|
// Single key deletion
|
|
if err := w.Delete([]byte(entry.key)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Range deletion
|
|
if rangeDeleter, ok := w.(ethdb.KeyValueRangeDeleter); ok {
|
|
if err := rangeDeleter.DeleteRange(entry.rangeFrom, entry.rangeTo); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return errors.New("ethdb.KeyValueWriter does not implement DeleteRange")
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if err := w.Put([]byte(entry.key), entry.value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close closes the batch and releases all associated resources.
|
|
func (b *batch) Close() {}
|
|
|
|
// iterator can walk over the (potentially partial) keyspace of a memory key
|
|
// value store. Internally it is a deep copy of the entire iterated state,
|
|
// sorted by keys.
|
|
type iterator struct {
|
|
index int
|
|
keys []string
|
|
values [][]byte
|
|
}
|
|
|
|
// Next moves the iterator to the next key/value pair. It returns whether the
|
|
// iterator is exhausted.
|
|
func (it *iterator) Next() bool {
|
|
// Short circuit if iterator is already exhausted in the forward direction.
|
|
if it.index >= len(it.keys) {
|
|
return false
|
|
}
|
|
it.index += 1
|
|
return it.index < len(it.keys)
|
|
}
|
|
|
|
// Error returns any accumulated error. Exhausting all the key/value pairs
|
|
// is not considered to be an error. A memory iterator cannot encounter errors.
|
|
func (it *iterator) Error() error {
|
|
return nil
|
|
}
|
|
|
|
// Key returns the key of the current key/value pair, or nil if done. The caller
|
|
// should not modify the contents of the returned slice, and its contents may
|
|
// change on the next call to Next.
|
|
func (it *iterator) Key() []byte {
|
|
// Short circuit if iterator is not in a valid position
|
|
if it.index < 0 || it.index >= len(it.keys) {
|
|
return nil
|
|
}
|
|
return []byte(it.keys[it.index])
|
|
}
|
|
|
|
// Value returns the value of the current key/value pair, or nil if done. The
|
|
// caller should not modify the contents of the returned slice, and its contents
|
|
// may change on the next call to Next.
|
|
func (it *iterator) Value() []byte {
|
|
// Short circuit if iterator is not in a valid position
|
|
if it.index < 0 || it.index >= len(it.keys) {
|
|
return nil
|
|
}
|
|
return it.values[it.index]
|
|
}
|
|
|
|
// Release releases associated resources. Release should always succeed and can
|
|
// be called multiple times without causing error.
|
|
func (it *iterator) Release() {
|
|
it.index, it.keys, it.values = -1, nil, nil
|
|
}
|