mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-25 11:16:11 +01:00
211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
|
// All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package leveldb
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
|
"github.com/syndtr/goleveldb/leveldb/journal"
|
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
|
)
|
|
|
|
// ErrManifestCorrupted records manifest corruption. This error will be
|
|
// wrapped with errors.ErrCorrupted.
|
|
type ErrManifestCorrupted struct {
|
|
Field string
|
|
Reason string
|
|
}
|
|
|
|
func (e *ErrManifestCorrupted) Error() string {
|
|
return fmt.Sprintf("leveldb: manifest corrupted (field '%s'): %s", e.Field, e.Reason)
|
|
}
|
|
|
|
func newErrManifestCorrupted(fd storage.FileDesc, field, reason string) error {
|
|
return errors.NewErrCorrupted(fd, &ErrManifestCorrupted{field, reason})
|
|
}
|
|
|
|
// session represent a persistent database session.
|
|
type session struct {
|
|
// Need 64-bit alignment.
|
|
stNextFileNum int64 // current unused file number
|
|
stJournalNum int64 // current journal file number; need external synchronization
|
|
stPrevJournalNum int64 // prev journal file number; no longer used; for compatibility with older version of leveldb
|
|
stTempFileNum int64
|
|
stSeqNum uint64 // last mem compacted seq; need external synchronization
|
|
|
|
stor *iStorage
|
|
storLock storage.Locker
|
|
o *cachedOptions
|
|
icmp *iComparer
|
|
tops *tOps
|
|
fileRef map[int64]int
|
|
|
|
manifest *journal.Writer
|
|
manifestWriter storage.Writer
|
|
manifestFd storage.FileDesc
|
|
|
|
stCompPtrs []internalKey // compaction pointers; need external synchronization
|
|
stVersion *version // current version
|
|
vmu sync.Mutex
|
|
}
|
|
|
|
// Creates new initialized session instance.
|
|
func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
|
|
if stor == nil {
|
|
return nil, os.ErrInvalid
|
|
}
|
|
storLock, err := stor.Lock()
|
|
if err != nil {
|
|
return
|
|
}
|
|
s = &session{
|
|
stor: newIStorage(stor),
|
|
storLock: storLock,
|
|
fileRef: make(map[int64]int),
|
|
}
|
|
s.setOptions(o)
|
|
s.tops = newTableOps(s)
|
|
s.setVersion(newVersion(s))
|
|
s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed")
|
|
return
|
|
}
|
|
|
|
// Close session.
|
|
func (s *session) close() {
|
|
s.tops.close()
|
|
if s.manifest != nil {
|
|
s.manifest.Close()
|
|
}
|
|
if s.manifestWriter != nil {
|
|
s.manifestWriter.Close()
|
|
}
|
|
s.manifest = nil
|
|
s.manifestWriter = nil
|
|
s.setVersion(&version{s: s, closing: true})
|
|
}
|
|
|
|
// Release session lock.
|
|
func (s *session) release() {
|
|
s.storLock.Unlock()
|
|
}
|
|
|
|
// Create a new database session; need external synchronization.
|
|
func (s *session) create() error {
|
|
// create manifest
|
|
return s.newManifest(nil, nil)
|
|
}
|
|
|
|
// Recover a database session; need external synchronization.
|
|
func (s *session) recover() (err error) {
|
|
defer func() {
|
|
if os.IsNotExist(err) {
|
|
// Don't return os.ErrNotExist if the underlying storage contains
|
|
// other files that belong to LevelDB. So the DB won't get trashed.
|
|
if fds, _ := s.stor.List(storage.TypeAll); len(fds) > 0 {
|
|
err = &errors.ErrCorrupted{Fd: storage.FileDesc{Type: storage.TypeManifest}, Err: &errors.ErrMissingFiles{}}
|
|
}
|
|
}
|
|
}()
|
|
|
|
fd, err := s.stor.GetMeta()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
reader, err := s.stor.Open(fd)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer reader.Close()
|
|
|
|
var (
|
|
// Options.
|
|
strict = s.o.GetStrict(opt.StrictManifest)
|
|
|
|
jr = journal.NewReader(reader, dropper{s, fd}, strict, true)
|
|
rec = &sessionRecord{}
|
|
staging = s.stVersion.newStaging()
|
|
)
|
|
for {
|
|
var r io.Reader
|
|
r, err = jr.Next()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = nil
|
|
break
|
|
}
|
|
return errors.SetFd(err, fd)
|
|
}
|
|
|
|
err = rec.decode(r)
|
|
if err == nil {
|
|
// save compact pointers
|
|
for _, r := range rec.compPtrs {
|
|
s.setCompPtr(r.level, internalKey(r.ikey))
|
|
}
|
|
// commit record to version staging
|
|
staging.commit(rec)
|
|
} else {
|
|
err = errors.SetFd(err, fd)
|
|
if strict || !errors.IsCorrupted(err) {
|
|
return
|
|
}
|
|
s.logf("manifest error: %v (skipped)", errors.SetFd(err, fd))
|
|
}
|
|
rec.resetCompPtrs()
|
|
rec.resetAddedTables()
|
|
rec.resetDeletedTables()
|
|
}
|
|
|
|
switch {
|
|
case !rec.has(recComparer):
|
|
return newErrManifestCorrupted(fd, "comparer", "missing")
|
|
case rec.comparer != s.icmp.uName():
|
|
return newErrManifestCorrupted(fd, "comparer", fmt.Sprintf("mismatch: want '%s', got '%s'", s.icmp.uName(), rec.comparer))
|
|
case !rec.has(recNextFileNum):
|
|
return newErrManifestCorrupted(fd, "next-file-num", "missing")
|
|
case !rec.has(recJournalNum):
|
|
return newErrManifestCorrupted(fd, "journal-file-num", "missing")
|
|
case !rec.has(recSeqNum):
|
|
return newErrManifestCorrupted(fd, "seq-num", "missing")
|
|
}
|
|
|
|
s.manifestFd = fd
|
|
s.setVersion(staging.finish())
|
|
s.setNextFileNum(rec.nextFileNum)
|
|
s.recordCommited(rec)
|
|
return nil
|
|
}
|
|
|
|
// Commit session; need external synchronization.
|
|
func (s *session) commit(r *sessionRecord) (err error) {
|
|
v := s.version()
|
|
defer v.release()
|
|
|
|
// spawn new version based on current version
|
|
nv := v.spawn(r)
|
|
|
|
if s.manifest == nil {
|
|
// manifest journal writer not yet created, create one
|
|
err = s.newManifest(r, nv)
|
|
} else {
|
|
err = s.flushManifest(r)
|
|
}
|
|
|
|
// finally, apply new version if no error rise
|
|
if err == nil {
|
|
s.setVersion(nv)
|
|
}
|
|
|
|
return
|
|
}
|