mirror of https://github.com/perkeep/perkeep.git
319 lines
9.2 KiB
Go
319 lines
9.2 KiB
Go
// Copyright 2011 The LevelDB-Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package memdb provides a memory-backed implementation of the db.DB
|
|
// interface.
|
|
//
|
|
// A MemDB's memory consumption increases monotonically, even if keys are
|
|
// deleted or values are updated with shorter slices. Callers of the package
|
|
// are responsible for explicitly compacting a MemDB into a separate DB
|
|
// (whether in-memory or on-disk) when appropriate.
|
|
package memdb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/rand"
|
|
"sync"
|
|
|
|
"camlistore.org/third_party/code.google.com/p/leveldb-go/leveldb/db"
|
|
)
|
|
|
|
// maxHeight is the maximum height of a MemDB's skiplist.
|
|
const maxHeight = 12
|
|
|
|
// A MemDB's skiplist consists of a number of nodes, and each node is
|
|
// represented by a variable number of ints: a key-offset, a value-offset, and
|
|
// between 1 and maxHeight next nodes. The key-offset and value-offset encode
|
|
// the node's key/value pair and are offsets into a MemDB's kvData slice.
|
|
// The remaining ints, for the next nodes in the skiplist's linked lists, are
|
|
// offsets into a MemDB's nodeData slice.
|
|
//
|
|
// The fXxx constants represent how to find the Xxx field of a node in the
|
|
// nodeData. For example, given an int 30 representing a node, and given
|
|
// nodeData[30:36] that looked like [60, 71, 82, 83, 84, 85], then
|
|
// nodeData[30 + fKey] = 60 would be the node's key-offset,
|
|
// nodeData[30 + fVal] = 71 would be the node's value-offset, and
|
|
// nodeData[30 + fNxt + 0] = 82 would be the next node at the height-0 list,
|
|
// nodeData[30 + fNxt + 1] = 83 would be the next node at the height-1 list,
|
|
// and so on. A node's height is implied by the skiplist construction: a node
|
|
// of height x appears in the height-h list iff 0 <= h && h < x.
|
|
const (
|
|
fKey = iota
|
|
fVal
|
|
fNxt
|
|
)
|
|
|
|
const (
|
|
// zeroNode represents the end of a linked list.
|
|
zeroNode = 0
|
|
// headNode represents the start of the linked list. It is equal to -fNxt
|
|
// so that the next nodes at height-h are at nodeData[h].
|
|
// The head node is an artificial node and has no key or value.
|
|
headNode = -fNxt
|
|
)
|
|
|
|
// A node's key-offset and value-offset fields are offsets into a MemDB's
|
|
// kvData slice that stores varint-prefixed strings: the node's key and value.
|
|
// A negative offset means a zero-length string, whether explicitly set to
|
|
// empty or implicitly set by deletion.
|
|
const (
|
|
kvOffsetEmptySlice = -1
|
|
kvOffsetDeletedNode = -2
|
|
)
|
|
|
|
// MemDB is a memory-backed implementation of the db.DB interface.
|
|
//
|
|
// It is safe to call Get, Set, Delete and Find concurrently.
|
|
type MemDB struct {
|
|
mutex sync.RWMutex
|
|
// height is the number of such lists, which can increase over time.
|
|
height int
|
|
// cmp defines an ordering on keys.
|
|
cmp db.Comparer
|
|
// kvData is an append-only buffer that holds varint-prefixed strings.
|
|
kvData []byte
|
|
// nodeData is an append-only buffer that holds a node's fields.
|
|
nodeData []int
|
|
}
|
|
|
|
// MemDB implements the db.DB interface.
|
|
var _ db.DB = &MemDB{}
|
|
|
|
// load loads a []byte from m.kvData.
|
|
func (m *MemDB) load(kvOffset int) (b []byte) {
|
|
if kvOffset < 0 {
|
|
return nil
|
|
}
|
|
bLen, n := binary.Uvarint(m.kvData[kvOffset:])
|
|
return m.kvData[kvOffset+n : kvOffset+n+int(bLen)]
|
|
}
|
|
|
|
// save saves a []byte to m.kvData.
|
|
func (m *MemDB) save(b []byte) (kvOffset int) {
|
|
if len(b) == 0 {
|
|
return kvOffsetEmptySlice
|
|
}
|
|
kvOffset = len(m.kvData)
|
|
var buf [binary.MaxVarintLen64]byte
|
|
length := binary.PutUvarint(buf[:], uint64(len(b)))
|
|
m.kvData = append(m.kvData, buf[:length]...)
|
|
m.kvData = append(m.kvData, b...)
|
|
return kvOffset
|
|
}
|
|
|
|
// findNode returns the first node n whose key is >= the given key (or nil if
|
|
// there is no such node) and whether n's key equals key. The search is based
|
|
// solely on the contents of a node's key. Whether or not that key was
|
|
// previously deleted from the MemDB is not relevant.
|
|
//
|
|
// If prev is non-nil, it also sets the first m.height elements of prev to the
|
|
// preceding node at each height.
|
|
func (m *MemDB) findNode(key []byte, prev *[maxHeight]int) (n int, exactMatch bool) {
|
|
for h, p := m.height-1, headNode; h >= 0; h-- {
|
|
// Walk the skiplist at height h until we find either a zero node
|
|
// or one whose key is >= the given key.
|
|
n = m.nodeData[p+fNxt+h]
|
|
for {
|
|
if n == zeroNode {
|
|
exactMatch = false
|
|
break
|
|
}
|
|
kOff := m.nodeData[n+fKey]
|
|
if c := m.cmp.Compare(m.load(kOff), key); c >= 0 {
|
|
exactMatch = c == 0
|
|
break
|
|
}
|
|
p, n = n, m.nodeData[n+fNxt+h]
|
|
}
|
|
if prev != nil {
|
|
(*prev)[h] = p
|
|
}
|
|
}
|
|
return n, exactMatch
|
|
}
|
|
|
|
// Get implements DB.Get, as documented in the leveldb/db package.
|
|
func (m *MemDB) Get(key []byte, o *db.ReadOptions) (value []byte, err error) {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
n, exactMatch := m.findNode(key, nil)
|
|
vOff := m.nodeData[n+fVal]
|
|
if !exactMatch || vOff == kvOffsetDeletedNode {
|
|
return nil, db.ErrNotFound
|
|
}
|
|
return m.load(vOff), nil
|
|
}
|
|
|
|
// Set implements DB.Set, as documented in the leveldb/db package.
|
|
func (m *MemDB) Set(key, value []byte, o *db.WriteOptions) error {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
// Find the node, and its predecessors at all heights.
|
|
var prev [maxHeight]int
|
|
n, exactMatch := m.findNode(key, &prev)
|
|
if exactMatch {
|
|
m.nodeData[n+fVal] = m.save(value)
|
|
return nil
|
|
}
|
|
// Choose the new node's height, branching with 25% probability.
|
|
h := 1
|
|
for h < maxHeight && rand.Intn(4) == 0 {
|
|
h++
|
|
}
|
|
// Raise the skiplist's height to the node's height, if necessary.
|
|
if m.height < h {
|
|
for i := m.height; i < h; i++ {
|
|
prev[i] = headNode
|
|
}
|
|
m.height = h
|
|
}
|
|
// Insert the new node.
|
|
var x [fNxt + maxHeight]int
|
|
n1 := len(m.nodeData)
|
|
x[fKey] = m.save(key)
|
|
x[fVal] = m.save(value)
|
|
for i := 0; i < h; i++ {
|
|
j := prev[i] + fNxt + i
|
|
x[fNxt+i] = m.nodeData[j]
|
|
m.nodeData[j] = n1
|
|
}
|
|
m.nodeData = append(m.nodeData, x[:fNxt+h]...)
|
|
return nil
|
|
}
|
|
|
|
// Delete implements DB.Delete, as documented in the leveldb/db package.
|
|
func (m *MemDB) Delete(key []byte, o *db.WriteOptions) error {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
n, exactMatch := m.findNode(key, nil)
|
|
if !exactMatch || m.nodeData[n+fVal] == kvOffsetDeletedNode {
|
|
return db.ErrNotFound
|
|
}
|
|
m.nodeData[n+fVal] = kvOffsetDeletedNode
|
|
return nil
|
|
}
|
|
|
|
// Find implements DB.Find, as documented in the leveldb/db package.
|
|
func (m *MemDB) Find(key []byte, o *db.ReadOptions) db.Iterator {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
n, _ := m.findNode(key, nil)
|
|
for n != zeroNode && m.nodeData[n+fVal] == kvOffsetDeletedNode {
|
|
n = m.nodeData[n+fNxt]
|
|
}
|
|
t := &iterator{
|
|
m: m,
|
|
restartNode: n,
|
|
}
|
|
t.fill()
|
|
// The iterator is positioned at the first node >= key. The iterator API
|
|
// requires that the caller the Next first, so we set t.i0 to -1.
|
|
t.i0 = -1
|
|
return t
|
|
}
|
|
|
|
// Close implements DB.Close, as documented in the leveldb/db package.
|
|
func (m *MemDB) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// ApproximateMemoryUsage returns the approximate memory usage of the MemDB.
|
|
func (m *MemDB) ApproximateMemoryUsage() int {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
return len(m.kvData)
|
|
}
|
|
|
|
// New returns a new MemDB.
|
|
func New(o *db.Options) *MemDB {
|
|
return &MemDB{
|
|
height: 1,
|
|
cmp: o.GetComparer(),
|
|
kvData: make([]byte, 0, 4096),
|
|
// The first maxHeight values of nodeData are the next nodes after the
|
|
// head node at each possible height. Their initial value is zeroNode.
|
|
nodeData: make([]int, maxHeight, 256),
|
|
}
|
|
}
|
|
|
|
// iterator is a MemDB iterator that buffers upcoming results, so that it does
|
|
// not have to acquire the MemDB's mutex on each Next call.
|
|
type iterator struct {
|
|
m *MemDB
|
|
// restartNode is the node to start refilling the buffer from.
|
|
restartNode int
|
|
// i0 is the current iterator position with respect to buf. A value of -1
|
|
// means that the iterator is at the start, end or both of the iteration.
|
|
// i1 is the number of buffered entries.
|
|
// Invariant: -1 <= i0 && i0 < i1 && i1 <= len(buf).
|
|
i0, i1 int
|
|
// buf buffers up to 32 key/value pairs.
|
|
buf [32][2][]byte
|
|
}
|
|
|
|
// iterator implements the db.Iterator interface.
|
|
var _ db.Iterator = &iterator{}
|
|
|
|
// fill fills the iterator's buffer with key/value pairs from the MemDB.
|
|
//
|
|
// Precondition: t.m.mutex is locked for reading.
|
|
func (t *iterator) fill() {
|
|
i, n := 0, t.restartNode
|
|
for i < len(t.buf) && n != zeroNode {
|
|
if t.m.nodeData[n+fVal] != kvOffsetDeletedNode {
|
|
t.buf[i][fKey] = t.m.load(t.m.nodeData[n+fKey])
|
|
t.buf[i][fVal] = t.m.load(t.m.nodeData[n+fVal])
|
|
i++
|
|
}
|
|
n = t.m.nodeData[n+fNxt]
|
|
}
|
|
if i == 0 {
|
|
// There were no non-deleted nodes on or after t.restartNode.
|
|
// The iterator is exhausted.
|
|
t.i0 = -1
|
|
} else {
|
|
t.i0 = 0
|
|
}
|
|
t.i1 = i
|
|
t.restartNode = n
|
|
}
|
|
|
|
// Next implements Iterator.Next, as documented in the leveldb/db package.
|
|
func (t *iterator) Next() bool {
|
|
t.i0++
|
|
if t.i0 < t.i1 {
|
|
return true
|
|
}
|
|
if t.restartNode == zeroNode {
|
|
t.i0 = -1
|
|
t.i1 = 0
|
|
return false
|
|
}
|
|
t.m.mutex.RLock()
|
|
defer t.m.mutex.RUnlock()
|
|
t.fill()
|
|
return true
|
|
}
|
|
|
|
// Key implements Iterator.Key, as documented in the leveldb/db package.
|
|
func (t *iterator) Key() []byte {
|
|
if t.i0 < 0 {
|
|
return nil
|
|
}
|
|
return t.buf[t.i0][fKey]
|
|
}
|
|
|
|
// Value implements Iterator.Value, as documented in the leveldb/db package.
|
|
func (t *iterator) Value() []byte {
|
|
if t.i0 < 0 {
|
|
return nil
|
|
}
|
|
return t.buf[t.i0][fVal]
|
|
}
|
|
|
|
// Close implements Iterator.Close, as documented in the leveldb/db package.
|
|
func (t *iterator) Close() error {
|
|
return nil
|
|
}
|