// Copyright 2012 The LevelDB-Go and Pebble 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 pebble import ( "context" "io" "math" "github.com/cockroachdb/pebble/internal/keyspan" ) // Snapshot provides a read-only point-in-time view of the DB state. type Snapshot struct { // The db the snapshot was created from. db *DB seqNum uint64 // The list the snapshot is linked into. list *snapshotList // The next/prev link for the snapshotList doubly-linked list of snapshots. prev, next *Snapshot } var _ Reader = (*Snapshot)(nil) // Get gets the value for the given key. It returns ErrNotFound if the Snapshot // does not contain the key. // // The caller should not modify the contents of the returned slice, but it is // safe to modify the contents of the argument after Get returns. The returned // slice will remain valid until the returned Closer is closed. On success, the // caller MUST call closer.Close() or a memory leak will occur. func (s *Snapshot) Get(key []byte) ([]byte, io.Closer, error) { if s.db == nil { panic(ErrClosed) } return s.db.getInternal(key, nil /* batch */, s) } // NewIter returns an iterator that is unpositioned (Iterator.Valid() will // return false). The iterator can be positioned via a call to SeekGE, // SeekLT, First or Last. func (s *Snapshot) NewIter(o *IterOptions) *Iterator { return s.NewIterWithContext(context.Background(), o) } // NewIterWithContext is like NewIter, and additionally accepts a context for // tracing. func (s *Snapshot) NewIterWithContext(ctx context.Context, o *IterOptions) *Iterator { if s.db == nil { panic(ErrClosed) } return s.db.newIter(ctx, nil /* batch */, s, o) } // ScanInternal scans all internal keys within the specified bounds, truncating // any rangedels and rangekeys to those bounds. For use when an external user // needs to be aware of all internal keys that make up a key range. // // See comment on db.ScanInternal for the behaviour that can be expected of // point keys deleted by range dels and keys masked by range keys. func (s *Snapshot) ScanInternal( ctx context.Context, lower, upper []byte, visitPointKey func(key *InternalKey, value LazyValue) error, visitRangeDel func(start, end []byte, seqNum uint64) error, visitRangeKey func(start, end []byte, keys []keyspan.Key) error, visitSharedFile func(sst *SharedSSTMeta) error, ) error { if s.db == nil { panic(ErrClosed) } iter := s.db.newInternalIter(s, &scanInternalOptions{ IterOptions: IterOptions{ KeyTypes: IterKeyTypePointsAndRanges, LowerBound: lower, UpperBound: upper, }, skipSharedLevels: visitSharedFile != nil, }) defer iter.close() return scanInternalImpl(ctx, lower, upper, iter, visitPointKey, visitRangeDel, visitRangeKey, visitSharedFile) } // Close closes the snapshot, releasing its resources. Close must be called. // Failure to do so will result in a tiny memory leak and a large leak of // resources on disk due to the entries the snapshot is preventing from being // deleted. func (s *Snapshot) Close() error { if s.db == nil { panic(ErrClosed) } s.db.mu.Lock() s.db.mu.snapshots.remove(s) // If s was the previous earliest snapshot, we might be able to reclaim // disk space by dropping obsolete records that were pinned by s. if e := s.db.mu.snapshots.earliest(); e > s.seqNum { s.db.maybeScheduleCompactionPicker(pickElisionOnly) } s.db.mu.Unlock() s.db = nil return nil } type snapshotList struct { root Snapshot } func (l *snapshotList) init() { l.root.next = &l.root l.root.prev = &l.root } func (l *snapshotList) empty() bool { return l.root.next == &l.root } func (l *snapshotList) count() int { if l.empty() { return 0 } var count int for i := l.root.next; i != &l.root; i = i.next { count++ } return count } func (l *snapshotList) earliest() uint64 { v := uint64(math.MaxUint64) if !l.empty() { v = l.root.next.seqNum } return v } func (l *snapshotList) toSlice() []uint64 { if l.empty() { return nil } var results []uint64 for i := l.root.next; i != &l.root; i = i.next { results = append(results, i.seqNum) } return results } func (l *snapshotList) pushBack(s *Snapshot) { if s.list != nil || s.prev != nil || s.next != nil { panic("pebble: snapshot list is inconsistent") } s.prev = l.root.prev s.prev.next = s s.next = &l.root s.next.prev = s s.list = l } func (l *snapshotList) remove(s *Snapshot) { if s == &l.root { panic("pebble: cannot remove snapshot list root node") } if s.list != l { panic("pebble: snapshot list is inconsistent") } s.prev.next = s.next s.next.prev = s.prev s.next = nil // avoid memory leaks s.prev = nil // avoid memory leaks s.list = nil // avoid memory leaks }