mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-21 21:33:22 +01:00
chore(deps): bump go.etcd.io/bbolt from 1.3.11 to 1.4.0
Bumps [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt) from 1.3.11 to 1.4.0. - [Release notes](https://github.com/etcd-io/bbolt/releases) - [Commits](https://github.com/etcd-io/bbolt/compare/v1.3.11...v1.4.0) --- updated-dependencies: - dependency-name: go.etcd.io/bbolt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
4
go.mod
4
go.mod
@@ -40,7 +40,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/pretty v1.2.1
|
||||
go.etcd.io/bbolt v1.3.11
|
||||
go.etcd.io/bbolt v1.4.1
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/sys v0.29.0
|
||||
google.golang.org/grpc v1.67.0
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -304,8 +304,8 @@ github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsB
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -335,8 +335,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI=
|
||||
go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
|
||||
12
vendor/github.com/spf13/pflag/.editorconfig
generated
vendored
Normal file
12
vendor/github.com/spf13/pflag/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
4
vendor/github.com/spf13/pflag/.golangci.yaml
generated
vendored
Normal file
4
vendor/github.com/spf13/pflag/.golangci.yaml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- nolintlint
|
||||
29
vendor/github.com/spf13/pflag/flag.go
generated
vendored
29
vendor/github.com/spf13/pflag/flag.go
generated
vendored
@@ -160,7 +160,7 @@ type FlagSet struct {
|
||||
args []string // arguments after flags
|
||||
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
|
||||
errorHandling ErrorHandling
|
||||
output io.Writer // nil means stderr; use out() accessor
|
||||
output io.Writer // nil means stderr; use Output() accessor
|
||||
interspersed bool // allow interspersed option/non-option args
|
||||
normalizeNameFunc func(f *FlagSet, name string) NormalizedName
|
||||
|
||||
@@ -255,13 +255,20 @@ func (f *FlagSet) normalizeFlagName(name string) NormalizedName {
|
||||
return n(f, name)
|
||||
}
|
||||
|
||||
func (f *FlagSet) out() io.Writer {
|
||||
// Output returns the destination for usage and error messages. os.Stderr is returned if
|
||||
// output was not set or was set to nil.
|
||||
func (f *FlagSet) Output() io.Writer {
|
||||
if f.output == nil {
|
||||
return os.Stderr
|
||||
}
|
||||
return f.output
|
||||
}
|
||||
|
||||
// Name returns the name of the flag set.
|
||||
func (f *FlagSet) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// SetOutput sets the destination for usage and error messages.
|
||||
// If output is nil, os.Stderr is used.
|
||||
func (f *FlagSet) SetOutput(output io.Writer) {
|
||||
@@ -358,7 +365,7 @@ func (f *FlagSet) ShorthandLookup(name string) *Flag {
|
||||
}
|
||||
if len(name) > 1 {
|
||||
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
|
||||
fmt.Fprintf(f.out(), msg)
|
||||
fmt.Fprintf(f.Output(), msg)
|
||||
panic(msg)
|
||||
}
|
||||
c := name[0]
|
||||
@@ -482,7 +489,7 @@ func (f *FlagSet) Set(name, value string) error {
|
||||
}
|
||||
|
||||
if flag.Deprecated != "" {
|
||||
fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
|
||||
fmt.Fprintf(f.Output(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -523,7 +530,7 @@ func Set(name, value string) error {
|
||||
// otherwise, the default values of all defined flags in the set.
|
||||
func (f *FlagSet) PrintDefaults() {
|
||||
usages := f.FlagUsages()
|
||||
fmt.Fprint(f.out(), usages)
|
||||
fmt.Fprint(f.Output(), usages)
|
||||
}
|
||||
|
||||
// defaultIsZeroValue returns true if the default value for this flag represents
|
||||
@@ -758,7 +765,7 @@ func PrintDefaults() {
|
||||
|
||||
// defaultUsage is the default function to print a usage message.
|
||||
func defaultUsage(f *FlagSet) {
|
||||
fmt.Fprintf(f.out(), "Usage of %s:\n", f.name)
|
||||
fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
@@ -844,7 +851,7 @@ func (f *FlagSet) AddFlag(flag *Flag) {
|
||||
_, alreadyThere := f.formal[normalizedFlagName]
|
||||
if alreadyThere {
|
||||
msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name)
|
||||
fmt.Fprintln(f.out(), msg)
|
||||
fmt.Fprintln(f.Output(), msg)
|
||||
panic(msg) // Happens only if flags are declared with identical names
|
||||
}
|
||||
if f.formal == nil {
|
||||
@@ -860,7 +867,7 @@ func (f *FlagSet) AddFlag(flag *Flag) {
|
||||
}
|
||||
if len(flag.Shorthand) > 1 {
|
||||
msg := fmt.Sprintf("%q shorthand is more than one ASCII character", flag.Shorthand)
|
||||
fmt.Fprintf(f.out(), msg)
|
||||
fmt.Fprintf(f.Output(), msg)
|
||||
panic(msg)
|
||||
}
|
||||
if f.shorthands == nil {
|
||||
@@ -870,7 +877,7 @@ func (f *FlagSet) AddFlag(flag *Flag) {
|
||||
used, alreadyThere := f.shorthands[c]
|
||||
if alreadyThere {
|
||||
msg := fmt.Sprintf("unable to redefine %q shorthand in %q flagset: it's already used for %q flag", c, f.name, used.Name)
|
||||
fmt.Fprintf(f.out(), msg)
|
||||
fmt.Fprintf(f.Output(), msg)
|
||||
panic(msg)
|
||||
}
|
||||
f.shorthands[c] = flag
|
||||
@@ -909,7 +916,7 @@ func VarP(value Value, name, shorthand, usage string) {
|
||||
func (f *FlagSet) failf(format string, a ...interface{}) error {
|
||||
err := fmt.Errorf(format, a...)
|
||||
if f.errorHandling != ContinueOnError {
|
||||
fmt.Fprintln(f.out(), err)
|
||||
fmt.Fprintln(f.Output(), err)
|
||||
f.usage()
|
||||
}
|
||||
return err
|
||||
@@ -1060,7 +1067,7 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
|
||||
}
|
||||
|
||||
if flag.ShorthandDeprecated != "" {
|
||||
fmt.Fprintf(f.out(), "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
|
||||
fmt.Fprintf(f.Output(), "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
|
||||
}
|
||||
|
||||
err = fn(flag, value)
|
||||
|
||||
3
vendor/github.com/spf13/pflag/ip.go
generated
vendored
3
vendor/github.com/spf13/pflag/ip.go
generated
vendored
@@ -16,6 +16,9 @@ func newIPValue(val net.IP, p *net.IP) *ipValue {
|
||||
|
||||
func (i *ipValue) String() string { return net.IP(*i).String() }
|
||||
func (i *ipValue) Set(s string) error {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
ip := net.ParseIP(strings.TrimSpace(s))
|
||||
if ip == nil {
|
||||
return fmt.Errorf("failed to parse IP: %q", s)
|
||||
|
||||
147
vendor/github.com/spf13/pflag/ipnet_slice.go
generated
vendored
Normal file
147
vendor/github.com/spf13/pflag/ipnet_slice.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
package pflag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// -- ipNetSlice Value
|
||||
type ipNetSliceValue struct {
|
||||
value *[]net.IPNet
|
||||
changed bool
|
||||
}
|
||||
|
||||
func newIPNetSliceValue(val []net.IPNet, p *[]net.IPNet) *ipNetSliceValue {
|
||||
ipnsv := new(ipNetSliceValue)
|
||||
ipnsv.value = p
|
||||
*ipnsv.value = val
|
||||
return ipnsv
|
||||
}
|
||||
|
||||
// Set converts, and assigns, the comma-separated IPNet argument string representation as the []net.IPNet value of this flag.
|
||||
// If Set is called on a flag that already has a []net.IPNet assigned, the newly converted values will be appended.
|
||||
func (s *ipNetSliceValue) Set(val string) error {
|
||||
|
||||
// remove all quote characters
|
||||
rmQuote := strings.NewReplacer(`"`, "", `'`, "", "`", "")
|
||||
|
||||
// read flag arguments with CSV parser
|
||||
ipNetStrSlice, err := readAsCSV(rmQuote.Replace(val))
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse ip values into slice
|
||||
out := make([]net.IPNet, 0, len(ipNetStrSlice))
|
||||
for _, ipNetStr := range ipNetStrSlice {
|
||||
_, n, err := net.ParseCIDR(strings.TrimSpace(ipNetStr))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid string being converted to CIDR: %s", ipNetStr)
|
||||
}
|
||||
out = append(out, *n)
|
||||
}
|
||||
|
||||
if !s.changed {
|
||||
*s.value = out
|
||||
} else {
|
||||
*s.value = append(*s.value, out...)
|
||||
}
|
||||
|
||||
s.changed = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns a string that uniquely represents this flag's type.
|
||||
func (s *ipNetSliceValue) Type() string {
|
||||
return "ipNetSlice"
|
||||
}
|
||||
|
||||
// String defines a "native" format for this net.IPNet slice flag value.
|
||||
func (s *ipNetSliceValue) String() string {
|
||||
|
||||
ipNetStrSlice := make([]string, len(*s.value))
|
||||
for i, n := range *s.value {
|
||||
ipNetStrSlice[i] = n.String()
|
||||
}
|
||||
|
||||
out, _ := writeAsCSV(ipNetStrSlice)
|
||||
return "[" + out + "]"
|
||||
}
|
||||
|
||||
func ipNetSliceConv(val string) (interface{}, error) {
|
||||
val = strings.Trim(val, "[]")
|
||||
// Emtpy string would cause a slice with one (empty) entry
|
||||
if len(val) == 0 {
|
||||
return []net.IPNet{}, nil
|
||||
}
|
||||
ss := strings.Split(val, ",")
|
||||
out := make([]net.IPNet, len(ss))
|
||||
for i, sval := range ss {
|
||||
_, n, err := net.ParseCIDR(strings.TrimSpace(sval))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid string being converted to CIDR: %s", sval)
|
||||
}
|
||||
out[i] = *n
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetIPNetSlice returns the []net.IPNet value of a flag with the given name
|
||||
func (f *FlagSet) GetIPNetSlice(name string) ([]net.IPNet, error) {
|
||||
val, err := f.getFlagType(name, "ipNetSlice", ipNetSliceConv)
|
||||
if err != nil {
|
||||
return []net.IPNet{}, err
|
||||
}
|
||||
return val.([]net.IPNet), nil
|
||||
}
|
||||
|
||||
// IPNetSliceVar defines a ipNetSlice flag with specified name, default value, and usage string.
|
||||
// The argument p points to a []net.IPNet variable in which to store the value of the flag.
|
||||
func (f *FlagSet) IPNetSliceVar(p *[]net.IPNet, name string, value []net.IPNet, usage string) {
|
||||
f.VarP(newIPNetSliceValue(value, p), name, "", usage)
|
||||
}
|
||||
|
||||
// IPNetSliceVarP is like IPNetSliceVar, but accepts a shorthand letter that can be used after a single dash.
|
||||
func (f *FlagSet) IPNetSliceVarP(p *[]net.IPNet, name, shorthand string, value []net.IPNet, usage string) {
|
||||
f.VarP(newIPNetSliceValue(value, p), name, shorthand, usage)
|
||||
}
|
||||
|
||||
// IPNetSliceVar defines a []net.IPNet flag with specified name, default value, and usage string.
|
||||
// The argument p points to a []net.IPNet variable in which to store the value of the flag.
|
||||
func IPNetSliceVar(p *[]net.IPNet, name string, value []net.IPNet, usage string) {
|
||||
CommandLine.VarP(newIPNetSliceValue(value, p), name, "", usage)
|
||||
}
|
||||
|
||||
// IPNetSliceVarP is like IPNetSliceVar, but accepts a shorthand letter that can be used after a single dash.
|
||||
func IPNetSliceVarP(p *[]net.IPNet, name, shorthand string, value []net.IPNet, usage string) {
|
||||
CommandLine.VarP(newIPNetSliceValue(value, p), name, shorthand, usage)
|
||||
}
|
||||
|
||||
// IPNetSlice defines a []net.IPNet flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a []net.IPNet variable that stores the value of that flag.
|
||||
func (f *FlagSet) IPNetSlice(name string, value []net.IPNet, usage string) *[]net.IPNet {
|
||||
p := []net.IPNet{}
|
||||
f.IPNetSliceVarP(&p, name, "", value, usage)
|
||||
return &p
|
||||
}
|
||||
|
||||
// IPNetSliceP is like IPNetSlice, but accepts a shorthand letter that can be used after a single dash.
|
||||
func (f *FlagSet) IPNetSliceP(name, shorthand string, value []net.IPNet, usage string) *[]net.IPNet {
|
||||
p := []net.IPNet{}
|
||||
f.IPNetSliceVarP(&p, name, shorthand, value, usage)
|
||||
return &p
|
||||
}
|
||||
|
||||
// IPNetSlice defines a []net.IPNet flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a []net.IP variable that stores the value of the flag.
|
||||
func IPNetSlice(name string, value []net.IPNet, usage string) *[]net.IPNet {
|
||||
return CommandLine.IPNetSliceP(name, "", value, usage)
|
||||
}
|
||||
|
||||
// IPNetSliceP is like IPNetSlice, but accepts a shorthand letter that can be used after a single dash.
|
||||
func IPNetSliceP(name, shorthand string, value []net.IPNet, usage string) *[]net.IPNet {
|
||||
return CommandLine.IPNetSliceP(name, shorthand, value, usage)
|
||||
}
|
||||
4
vendor/github.com/spf13/pflag/string_array.go
generated
vendored
4
vendor/github.com/spf13/pflag/string_array.go
generated
vendored
@@ -31,11 +31,7 @@ func (s *stringArrayValue) Append(val string) error {
|
||||
func (s *stringArrayValue) Replace(val []string) error {
|
||||
out := make([]string, len(val))
|
||||
for i, d := range val {
|
||||
var err error
|
||||
out[i] = d
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*s.value = out
|
||||
return nil
|
||||
|
||||
2
vendor/go.etcd.io/bbolt/.gitignore
generated
vendored
2
vendor/go.etcd.io/bbolt/.gitignore
generated
vendored
@@ -6,5 +6,7 @@ cover.out
|
||||
cover-*.out
|
||||
/.idea
|
||||
*.iml
|
||||
/bbolt
|
||||
/cmd/bbolt/bbolt
|
||||
.DS_Store
|
||||
|
||||
|
||||
2
vendor/go.etcd.io/bbolt/.go-version
generated
vendored
2
vendor/go.etcd.io/bbolt/.go-version
generated
vendored
@@ -1 +1 @@
|
||||
1.22.6
|
||||
1.23.9
|
||||
|
||||
60
vendor/go.etcd.io/bbolt/Makefile
generated
vendored
60
vendor/go.etcd.io/bbolt/Makefile
generated
vendored
@@ -1,6 +1,7 @@
|
||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
COMMIT=`git rev-parse --short HEAD`
|
||||
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||
GOFILES = $(shell find . -name \*.go)
|
||||
|
||||
TESTFLAGS_RACE=-race=false
|
||||
ifdef ENABLE_RACE
|
||||
@@ -13,9 +14,26 @@ ifdef CPU
|
||||
endif
|
||||
TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS)
|
||||
|
||||
TESTFLAGS_TIMEOUT=30m
|
||||
ifdef TIMEOUT
|
||||
TESTFLAGS_TIMEOUT=$(TIMEOUT)
|
||||
endif
|
||||
|
||||
TESTFLAGS_ENABLE_STRICT_MODE=false
|
||||
ifdef ENABLE_STRICT_MODE
|
||||
TESTFLAGS_ENABLE_STRICT_MODE=$(ENABLE_STRICT_MODE)
|
||||
endif
|
||||
|
||||
.EXPORT_ALL_VARIABLES:
|
||||
TEST_ENABLE_STRICT_MODE=${TESTFLAGS_ENABLE_STRICT_MODE}
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]')
|
||||
@echo "Verifying gofmt, failures can be fixed with ./scripts/fix.sh"
|
||||
@!(gofmt -l -s -d ${GOFILES} | grep '[a-z]')
|
||||
|
||||
@echo "Verifying goimports, failures can be fixed with ./scripts/fix.sh"
|
||||
@!(go run golang.org/x/tools/cmd/goimports@latest -l -d ${GOFILES} | grep '[a-z]')
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@@ -24,21 +42,23 @@ lint:
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./internal/...
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT}
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./internal/...
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v -timeout 30m \
|
||||
TEST_FREELIST_TYPE=hashmap go test -v -timeout ${TESTFLAGS_TIMEOUT} \
|
||||
-coverprofile cover-freelist-hashmap.out -covermode atomic
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v -timeout 30m \
|
||||
TEST_FREELIST_TYPE=array go test -v -timeout ${TESTFLAGS_TIMEOUT} \
|
||||
-coverprofile cover-freelist-array.out -covermode atomic
|
||||
|
||||
BOLT_CMD=bbolt
|
||||
@@ -55,7 +75,7 @@ gofail-enable: install-gofail
|
||||
gofail enable .
|
||||
|
||||
.PHONY: gofail-disable
|
||||
gofail-disable:
|
||||
gofail-disable: install-gofail
|
||||
gofail disable .
|
||||
|
||||
.PHONY: install-gofail
|
||||
@@ -65,12 +85,24 @@ install-gofail:
|
||||
.PHONY: test-failpoint
|
||||
test-failpoint:
|
||||
@echo "[failpoint] hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
@echo "[failpoint] array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
.PHONY: test-robustness # Running robustness tests requires root permission
|
||||
test-robustness:
|
||||
go test -v ${TESTFLAGS} ./tests/dmflakey -test.root
|
||||
go test -v ${TESTFLAGS} ./tests/robustness -test.root
|
||||
.PHONY: test-robustness # Running robustness tests requires root permission for now
|
||||
# TODO: Remove sudo once we fully migrate to the prow infrastructure
|
||||
test-robustness: gofail-enable build
|
||||
sudo env PATH=$$PATH go test -v ${TESTFLAGS} ./tests/dmflakey -test.root
|
||||
sudo env PATH=$(PWD)/bin:$$PATH go test -v ${TESTFLAGS} ${ROBUSTNESS_TESTFLAGS} ./tests/robustness -test.root
|
||||
|
||||
.PHONY: test-benchmark-compare
|
||||
# Runs benchmark tests on the current git ref and the given REF, and compares
|
||||
# the two.
|
||||
test-benchmark-compare: install-benchstat
|
||||
@git fetch
|
||||
./scripts/compare_benchmarks.sh $(REF)
|
||||
|
||||
.PHONY: install-benchstat
|
||||
install-benchstat:
|
||||
go install golang.org/x/perf/cmd/benchstat@latest
|
||||
|
||||
10
vendor/go.etcd.io/bbolt/OWNERS
generated
vendored
Normal file
10
vendor/go.etcd.io/bbolt/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- ahrtr # Benjamin Wang <benjamin.ahrtr@gmail.com> <benjamin.wang@broadcom.com>
|
||||
- serathius # Marek Siarkowicz <siarkowicz@google.com> <marek.siarkowicz@gmail.com>
|
||||
- ptabor # Piotr Tabor <piotr.tabor@gmail.com>
|
||||
- spzala # Sahdev Zala <spzala@us.ibm.com>
|
||||
reviewers:
|
||||
- fuweid # Wei Fu <fuweid89@gmail.com>
|
||||
- tjungblu # Thomas Jungblut <tjungblu@redhat.com>
|
||||
68
vendor/go.etcd.io/bbolt/README.md
generated
vendored
68
vendor/go.etcd.io/bbolt/README.md
generated
vendored
@@ -1,10 +1,8 @@
|
||||
bbolt
|
||||
=====
|
||||
|
||||
[](https://goreportcard.com/report/github.com/etcd-io/bbolt)
|
||||
[](https://codecov.io/gh/etcd-io/bbolt)
|
||||
[](https://travis-ci.com/etcd-io/bbolt)
|
||||
[](https://godoc.org/github.com/etcd-io/bbolt)
|
||||
[](https://goreportcard.com/report/go.etcd.io/bbolt)
|
||||
[](https://pkg.go.dev/go.etcd.io/bbolt)
|
||||
[](https://github.com/etcd-io/bbolt/releases)
|
||||
[](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
|
||||
|
||||
@@ -71,13 +69,14 @@ New minor versions may add additional features to the API.
|
||||
- [LMDB](#lmdb)
|
||||
- [Caveats & Limitations](#caveats--limitations)
|
||||
- [Reading the Source](#reading-the-source)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Other Projects Using Bolt](#other-projects-using-bolt)
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installing
|
||||
|
||||
To start using Bolt, install Go and run `go get`:
|
||||
To start using `bbolt`, install Go and run `go get`:
|
||||
```sh
|
||||
$ go get go.etcd.io/bbolt@latest
|
||||
```
|
||||
@@ -103,7 +102,7 @@ To use bbolt as an embedded key-value store, import as:
|
||||
```go
|
||||
import bolt "go.etcd.io/bbolt"
|
||||
|
||||
db, err := bolt.Open(path, 0666, nil)
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -298,6 +297,17 @@ db.Update(func(tx *bolt.Tx) error {
|
||||
})
|
||||
```
|
||||
|
||||
You can retrieve an existing bucket using the `Tx.Bucket()` function:
|
||||
```go
|
||||
db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("MyBucket"))
|
||||
if b == nil {
|
||||
return errors.New("bucket does not exist")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
You can also create a bucket only if it doesn't exist by using the
|
||||
`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this
|
||||
function for all your top-level buckets after you open your database so you can
|
||||
@@ -305,6 +315,17 @@ guarantee that they exist for future transactions.
|
||||
|
||||
To delete a bucket, simply call the `Tx.DeleteBucket()` function.
|
||||
|
||||
You can also iterate over all existing top-level buckets with `Tx.ForEach()`:
|
||||
|
||||
```go
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
fmt.Println(string(name))
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
### Using key/value pairs
|
||||
|
||||
@@ -336,7 +357,17 @@ exists then it will return its byte slice value. If it doesn't exist then it
|
||||
will return `nil`. It's important to note that you can have a zero-length value
|
||||
set to a key which is different than the key not existing.
|
||||
|
||||
Use the `Bucket.Delete()` function to delete a key from the bucket.
|
||||
Use the `Bucket.Delete()` function to delete a key from the bucket:
|
||||
|
||||
```go
|
||||
db.Update(func (tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("MyBucket"))
|
||||
err := b.Delete([]byte("answer"))
|
||||
return err
|
||||
})
|
||||
```
|
||||
|
||||
This will delete the key `answers` from the bucket `MyBucket`.
|
||||
|
||||
Please note that values returned from `Get()` are only valid while the
|
||||
transaction is open. If you need to use a value outside of the transaction
|
||||
@@ -654,7 +685,7 @@ uses a shared lock to allow multiple processes to read from the database but
|
||||
it will block any processes from opening the database in read-write mode.
|
||||
|
||||
```go
|
||||
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
|
||||
db, err := bolt.Open("my.db", 0600, &bolt.Options{ReadOnly: true})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -890,7 +921,7 @@ The best places to start are the main entry points into Bolt:
|
||||
|
||||
- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
|
||||
arguments, a cursor is used to traverse the B+tree to the page and position
|
||||
where they key & value will be written. Once the position is found, the bucket
|
||||
where the key & value will be written. Once the position is found, the bucket
|
||||
materializes the underlying page and the page's parent pages into memory as
|
||||
"nodes". These nodes are where mutations occur during read-write transactions.
|
||||
These changes get flushed to disk during commit.
|
||||
@@ -919,6 +950,21 @@ The best places to start are the main entry points into Bolt:
|
||||
If you have additional notes that could be helpful for others, please submit
|
||||
them via pull request.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- bbolt might run into data corruption issue on Linux when the feature
|
||||
[ext4: fast commit](https://lwn.net/Articles/842385/), which was introduced in
|
||||
linux kernel version v5.10, is enabled. The fixes to the issue were included in
|
||||
linux kernel version v5.17, please refer to links below,
|
||||
|
||||
* [ext4: fast commit may miss tracking unwritten range during ftruncate](https://lore.kernel.org/linux-ext4/20211223032337.5198-3-yinxin.x@bytedance.com/)
|
||||
* [ext4: fast commit may not fallback for ineligible commit](https://lore.kernel.org/lkml/202201091544.W5HHEXAp-lkp@intel.com/T/#ma0768815e4b5f671e9e451d578256ef9a76fe30e)
|
||||
* [ext4 updates for 5.17](https://lore.kernel.org/lkml/YdyxjTFaLWif6BCM@mit.edu/)
|
||||
|
||||
Please also refer to the discussion in https://github.com/etcd-io/bbolt/issues/562.
|
||||
|
||||
- Writing a value with a length of 0 will always result in reading back an empty `[]byte{}` value.
|
||||
Please refer to [issues/726#issuecomment-2061694802](https://github.com/etcd-io/bbolt/issues/726#issuecomment-2061694802).
|
||||
|
||||
## Other Projects Using Bolt
|
||||
|
||||
@@ -934,13 +980,16 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
|
||||
* [BoltDB Viewer](https://github.com/zc310/rich_boltdb) - A BoltDB Viewer Can run on Windows、Linux、Android system.
|
||||
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
|
||||
* [bstore](https://github.com/mjl-/bstore) - Database library storing Go values, with referential/unique/nonzero constraints, indices, automatic schema management with struct tags, and a query API.
|
||||
* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
|
||||
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
|
||||
simple tx and key scans.
|
||||
* [Buildkit](https://github.com/moby/buildkit) - concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit
|
||||
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
|
||||
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
|
||||
* [🌰 Chestnut](https://github.com/jrapoport/chestnut) - Chestnut is encrypted storage for Go.
|
||||
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
|
||||
* [Containerd](https://github.com/containerd/containerd) - An open and reliable container runtime
|
||||
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
|
||||
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
|
||||
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
|
||||
@@ -964,6 +1013,7 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
||||
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
|
||||
* [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage.
|
||||
* [Portainer](https://github.com/portainer/portainer) - A lightweight service delivery platform for containerized applications that can be used to manage Docker, Swarm, Kubernetes and ACI environments.
|
||||
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
|
||||
* [Rain](https://github.com/cenkalti/rain) - BitTorrent client and library.
|
||||
* [reef-pi](https://github.com/reef-pi/reef-pi) - reef-pi is an award winning, modular, DIY reef tank controller using easy to learn electronics based on a Raspberry Pi.
|
||||
|
||||
7
vendor/go.etcd.io/bbolt/bolt_386.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_386.go
generated
vendored
@@ -1,7 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
1
vendor/go.etcd.io/bbolt/bolt_unix_aix.go → vendor/go.etcd.io/bbolt/bolt_aix.go
generated
vendored
1
vendor/go.etcd.io/bbolt/bolt_unix_aix.go → vendor/go.etcd.io/bbolt/bolt_aix.go
generated
vendored
@@ -1,5 +1,4 @@
|
||||
//go:build aix
|
||||
// +build aix
|
||||
|
||||
package bbolt
|
||||
|
||||
7
vendor/go.etcd.io/bbolt/bolt_amd64.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_amd64.go
generated
vendored
@@ -1,7 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
90
vendor/go.etcd.io/bbolt/bolt_android.go
generated
vendored
Normal file
90
vendor/go.etcd.io/bbolt/bolt_android.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// flock acquires an advisory lock on a file descriptor.
|
||||
func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
var t time.Time
|
||||
if timeout != 0 {
|
||||
t = time.Now()
|
||||
}
|
||||
fd := db.file.Fd()
|
||||
var lockType int16
|
||||
if exclusive {
|
||||
lockType = syscall.F_WRLCK
|
||||
} else {
|
||||
lockType = syscall.F_RDLCK
|
||||
}
|
||||
for {
|
||||
// Attempt to obtain an exclusive lock.
|
||||
lock := syscall.Flock_t{Type: lockType}
|
||||
err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if err != syscall.EAGAIN {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we timed out then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
time.Sleep(flockRetryTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// funlock releases an advisory lock on a file descriptor.
|
||||
func funlock(db *DB) error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Type = syscall.F_UNLCK
|
||||
lock.Whence = 0
|
||||
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
|
||||
}
|
||||
|
||||
// mmap memory maps a DB's data file.
|
||||
func mmap(db *DB, sz int) error {
|
||||
// Map the data file to memory.
|
||||
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Advise the kernel that the mmap is accessed randomly.
|
||||
err = unix.Madvise(b, syscall.MADV_RANDOM)
|
||||
if err != nil && err != syscall.ENOSYS {
|
||||
// Ignore not implemented error in kernel because it still works.
|
||||
return fmt.Errorf("madvise: %s", err)
|
||||
}
|
||||
|
||||
// Save the original byte slice and convert to a byte array pointer.
|
||||
db.dataref = b
|
||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||
db.datasz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
// munmap unmaps a DB's data file from memory.
|
||||
func munmap(db *DB) error {
|
||||
// Ignore the unmap if we have no mapped data.
|
||||
if db.dataref == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmap using the original byte slice.
|
||||
err := unix.Munmap(db.dataref)
|
||||
db.dataref = nil
|
||||
db.data = nil
|
||||
db.datasz = 0
|
||||
return err
|
||||
}
|
||||
7
vendor/go.etcd.io/bbolt/bolt_arm.go
generated
vendored
7
vendor/go.etcd.io/bbolt/bolt_arm.go
generated
vendored
@@ -1,7 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_arm64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_arm64.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build arm64
|
||||
// +build arm64
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_loong64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_loong64.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build loong64
|
||||
// +build loong64
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_mips64x.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_mips64x.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build mips64 || mips64le
|
||||
// +build mips64 mips64le
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x8000000000 // 512GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_mipsx.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_mipsx.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build mips || mipsle
|
||||
// +build mips mipsle
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x40000000 // 1GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_ppc.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_ppc.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build ppc
|
||||
// +build ppc
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_ppc64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_ppc64.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build ppc64
|
||||
// +build ppc64
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_ppc64le.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_ppc64le.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build ppc64le
|
||||
// +build ppc64le
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_riscv64.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_riscv64.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build riscv64
|
||||
// +build riscv64
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_s390x.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_s390x.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
//go:build s390x
|
||||
// +build s390x
|
||||
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
10
vendor/go.etcd.io/bbolt/bolt_unix.go
generated
vendored
10
vendor/go.etcd.io/bbolt/bolt_unix.go
generated
vendored
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9 && !solaris && !aix
|
||||
// +build !windows,!plan9,!solaris,!aix
|
||||
//go:build !windows && !plan9 && !solaris && !aix && !android
|
||||
|
||||
package bbolt
|
||||
|
||||
@@ -10,6 +9,9 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// flock acquires an advisory lock on a file descriptor.
|
||||
@@ -36,7 +38,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
|
||||
// If we timed out then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
return errors.ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
@@ -66,7 +68,7 @@ func mmap(db *DB, sz int) error {
|
||||
|
||||
// Save the original byte slice and convert to a byte array pointer.
|
||||
db.dataref = b
|
||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||
db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||
db.datasz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
9
vendor/go.etcd.io/bbolt/bolt_windows.go
generated
vendored
9
vendor/go.etcd.io/bbolt/bolt_windows.go
generated
vendored
@@ -8,6 +8,9 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// fdatasync flushes written data to a file descriptor.
|
||||
@@ -42,7 +45,7 @@ func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
|
||||
// If we timed oumercit then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
return errors.ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
@@ -70,7 +73,7 @@ func mmap(db *DB, sz int) error {
|
||||
return fmt.Errorf("truncate: %s", err)
|
||||
}
|
||||
sizehi = uint32(sz >> 32)
|
||||
sizelo = uint32(sz) & 0xffffffff
|
||||
sizelo = uint32(sz)
|
||||
}
|
||||
|
||||
// Open a file mapping handle.
|
||||
@@ -93,7 +96,7 @@ func mmap(db *DB, sz int) error {
|
||||
}
|
||||
|
||||
// Convert to a byte array.
|
||||
db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr)))
|
||||
db.data = (*[common.MaxMapSize]byte)(unsafe.Pointer(addr))
|
||||
db.datasz = sz
|
||||
|
||||
return nil
|
||||
|
||||
1
vendor/go.etcd.io/bbolt/boltsync_unix.go
generated
vendored
1
vendor/go.etcd.io/bbolt/boltsync_unix.go
generated
vendored
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9 && !linux && !openbsd
|
||||
// +build !windows,!plan9,!linux,!openbsd
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
484
vendor/go.etcd.io/bbolt/bucket.go
generated
vendored
484
vendor/go.etcd.io/bbolt/bucket.go
generated
vendored
@@ -4,6 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -14,8 +17,6 @@ const (
|
||||
MaxValueSize = (1 << 31) - 2
|
||||
)
|
||||
|
||||
const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
|
||||
|
||||
const (
|
||||
minFillPercent = 0.1
|
||||
maxFillPercent = 1.0
|
||||
@@ -27,12 +28,12 @@ const DefaultFillPercent = 0.5
|
||||
|
||||
// Bucket represents a collection of key/value pairs inside the database.
|
||||
type Bucket struct {
|
||||
*bucket
|
||||
*common.InBucket
|
||||
tx *Tx // the associated transaction
|
||||
buckets map[string]*Bucket // subbucket cache
|
||||
page *page // inline page reference
|
||||
page *common.Page // inline page reference
|
||||
rootNode *node // materialized node for the root page.
|
||||
nodes map[pgid]*node // node cache
|
||||
nodes map[common.Pgid]*node // node cache
|
||||
|
||||
// Sets the threshold for filling nodes when they split. By default,
|
||||
// the bucket will fill to 50% but it can be useful to increase this
|
||||
@@ -42,21 +43,12 @@ type Bucket struct {
|
||||
FillPercent float64
|
||||
}
|
||||
|
||||
// bucket represents the on-file representation of a bucket.
|
||||
// This is stored as the "value" of a bucket key. If the bucket is small enough,
|
||||
// then its root page can be stored inline in the "value", after the bucket
|
||||
// header. In the case of inline buckets, the "root" will be 0.
|
||||
type bucket struct {
|
||||
root pgid // page id of the bucket's root-level page
|
||||
sequence uint64 // monotonically incrementing, used by NextSequence()
|
||||
}
|
||||
|
||||
// newBucket returns a new bucket associated with a transaction.
|
||||
func newBucket(tx *Tx) Bucket {
|
||||
var b = Bucket{tx: tx, FillPercent: DefaultFillPercent}
|
||||
if tx.writable {
|
||||
b.buckets = make(map[string]*Bucket)
|
||||
b.nodes = make(map[pgid]*node)
|
||||
b.nodes = make(map[common.Pgid]*node)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -67,8 +59,8 @@ func (b *Bucket) Tx() *Tx {
|
||||
}
|
||||
|
||||
// Root returns the root of the bucket.
|
||||
func (b *Bucket) Root() pgid {
|
||||
return b.root
|
||||
func (b *Bucket) Root() common.Pgid {
|
||||
return b.RootPage()
|
||||
}
|
||||
|
||||
// Writable returns whether the bucket is writable.
|
||||
@@ -105,7 +97,7 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||
k, v, flags := c.seek(name)
|
||||
|
||||
// Return nil if the key doesn't exist or it is not a bucket.
|
||||
if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 {
|
||||
if !bytes.Equal(name, k) || (flags&common.BucketLeafFlag) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,8 +117,8 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
|
||||
// Unaligned access requires a copy to be made.
|
||||
const unalignedMask = unsafe.Alignof(struct {
|
||||
bucket
|
||||
page
|
||||
common.InBucket
|
||||
common.Page
|
||||
}{}) - 1
|
||||
unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0
|
||||
if unaligned {
|
||||
@@ -136,15 +128,15 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
// If this is a writable transaction then we need to copy the bucket entry.
|
||||
// Read-only transactions can point directly at the mmap entry.
|
||||
if b.tx.writable && !unaligned {
|
||||
child.bucket = &bucket{}
|
||||
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
|
||||
child.InBucket = &common.InBucket{}
|
||||
*child.InBucket = *(*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
} else {
|
||||
child.bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
child.InBucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
}
|
||||
|
||||
// Save a reference to the inline page if the bucket is inline.
|
||||
if child.root == 0 {
|
||||
child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||
if child.RootPage() == 0 {
|
||||
child.page = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
|
||||
}
|
||||
|
||||
return &child
|
||||
@@ -153,13 +145,23 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
// CreateBucket creates a new bucket at the given key and returns the new bucket.
|
||||
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
func (b *Bucket) CreateBucket(key []byte) (rb *Bucket, err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Creating bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Creating bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Creating bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if b.tx.db == nil {
|
||||
return nil, ErrTxClosed
|
||||
return nil, errors.ErrTxClosed
|
||||
} else if !b.tx.writable {
|
||||
return nil, ErrTxNotWritable
|
||||
return nil, errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return nil, ErrBucketNameRequired
|
||||
return nil, errors.ErrBucketNameRequired
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
@@ -173,21 +175,21 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
|
||||
// Return an error if there is an existing key.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return nil, ErrBucketExists
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return nil, errors.ErrBucketExists
|
||||
}
|
||||
return nil, ErrIncompatibleValue
|
||||
return nil, errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Create empty, inline bucket.
|
||||
var bucket = Bucket{
|
||||
bucket: &bucket{},
|
||||
InBucket: &common.InBucket{},
|
||||
rootNode: &node{isLeaf: true},
|
||||
FillPercent: DefaultFillPercent,
|
||||
}
|
||||
var value = bucket.write()
|
||||
|
||||
c.node().put(newKey, newKey, value, 0, bucketLeafFlag)
|
||||
c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
|
||||
|
||||
// Since subbuckets are not allowed on inline buckets, we need to
|
||||
// dereference the inline page, if it exists. This will cause the bucket
|
||||
@@ -200,39 +202,108 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
|
||||
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
||||
child, err := b.CreateBucket(key)
|
||||
if err == ErrBucketExists {
|
||||
return b.Bucket(key), nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
func (b *Bucket) CreateBucketIfNotExists(key []byte) (rb *Bucket, err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Creating bucket if not exist %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Creating bucket if not exist %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Creating bucket if not exist %q successfully", key)
|
||||
}
|
||||
return child, nil
|
||||
}()
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket at the given key.
|
||||
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
|
||||
func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return nil, errors.ErrTxClosed
|
||||
} else if !b.tx.writable {
|
||||
return nil, errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return nil, errors.ErrBucketNameRequired
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
// Tip: Use a new variable `newKey` instead of reusing the existing `key` to prevent
|
||||
// it from being marked as leaking, and accordingly cannot be allocated on stack.
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
if b.buckets != nil {
|
||||
if child := b.buckets[string(newKey)]; child != nil {
|
||||
return child, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, _, flags := c.seek(key)
|
||||
k, v, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing non-bucket key.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
var child = b.openBucket(v)
|
||||
if b.buckets != nil {
|
||||
b.buckets[string(newKey)] = child
|
||||
}
|
||||
|
||||
return child, nil
|
||||
}
|
||||
return nil, errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Create empty, inline bucket.
|
||||
var bucket = Bucket{
|
||||
InBucket: &common.InBucket{},
|
||||
rootNode: &node{isLeaf: true},
|
||||
FillPercent: DefaultFillPercent,
|
||||
}
|
||||
var value = bucket.write()
|
||||
|
||||
c.node().put(newKey, newKey, value, 0, common.BucketLeafFlag)
|
||||
|
||||
// Since subbuckets are not allowed on inline buckets, we need to
|
||||
// dereference the inline page, if it exists. This will cause the bucket
|
||||
// to be treated as a regular, non-inline bucket for the rest of the tx.
|
||||
b.page = nil
|
||||
|
||||
return b.Bucket(newKey), nil
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket at the given key.
|
||||
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
|
||||
func (b *Bucket) DeleteBucket(key []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Deleting bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Deleting bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Deleting bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil {
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, _, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if bucket doesn't exist or is not a bucket.
|
||||
if !bytes.Equal(key, k) {
|
||||
return ErrBucketNotFound
|
||||
} else if (flags & bucketLeafFlag) == 0 {
|
||||
return ErrIncompatibleValue
|
||||
if !bytes.Equal(newKey, k) {
|
||||
return errors.ErrBucketNotFound
|
||||
} else if (flags & common.BucketLeafFlag) == 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Recursively delete all child buckets.
|
||||
child := b.Bucket(key)
|
||||
err := child.ForEachBucket(func(k []byte) error {
|
||||
child := b.Bucket(newKey)
|
||||
err = child.ForEachBucket(func(k []byte) error {
|
||||
if err := child.DeleteBucket(k); err != nil {
|
||||
return fmt.Errorf("delete bucket: %s", err)
|
||||
}
|
||||
@@ -243,7 +314,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
}
|
||||
|
||||
// Remove cached copy.
|
||||
delete(b.buckets, string(key))
|
||||
delete(b.buckets, string(newKey))
|
||||
|
||||
// Release all bucket pages to freelist.
|
||||
child.nodes = nil
|
||||
@@ -251,19 +322,119 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
child.free()
|
||||
|
||||
// Delete the node if we have a matching key.
|
||||
c.node().del(key)
|
||||
c.node().del(newKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveBucket moves a sub-bucket from the source bucket to the destination bucket.
|
||||
// Returns an error if
|
||||
// 1. the sub-bucket cannot be found in the source bucket;
|
||||
// 2. or the key already exists in the destination bucket;
|
||||
// 3. or the key represents a non-bucket value;
|
||||
// 4. the source and destination buckets are the same.
|
||||
func (b *Bucket) MoveBucket(key []byte, dstBucket *Bucket) (err error) {
|
||||
lg := b.tx.db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Debugf("Moving bucket %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Moving bucket %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Moving bucket %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil || dstBucket.tx.db == nil {
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() || !dstBucket.Writable() {
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
if b.tx.db.Path() != dstBucket.tx.db.Path() || b.tx != dstBucket.tx {
|
||||
lg.Errorf("The source and target buckets are not in the same db file, source bucket in %s and target bucket in %s", b.tx.db.Path(), dstBucket.tx.db.Path())
|
||||
return errors.ErrDifferentDB
|
||||
}
|
||||
|
||||
newKey := cloneBytes(key)
|
||||
|
||||
// Move cursor to correct position.
|
||||
c := b.Cursor()
|
||||
k, v, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if bucket doesn't exist or is not a bucket.
|
||||
if !bytes.Equal(newKey, k) {
|
||||
return errors.ErrBucketNotFound
|
||||
} else if (flags & common.BucketLeafFlag) == 0 {
|
||||
lg.Errorf("An incompatible key %s exists in the source bucket", newKey)
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Do nothing (return true directly) if the source bucket and the
|
||||
// destination bucket are actually the same bucket.
|
||||
if b == dstBucket || (b.RootPage() == dstBucket.RootPage() && b.RootPage() != 0) {
|
||||
lg.Errorf("The source bucket (%s) and the target bucket (%s) are the same bucket", b, dstBucket)
|
||||
return errors.ErrSameBuckets
|
||||
}
|
||||
|
||||
// check whether the key already exists in the destination bucket
|
||||
curDst := dstBucket.Cursor()
|
||||
k, _, flags = curDst.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing key in the destination bucket.
|
||||
if bytes.Equal(newKey, k) {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrBucketExists
|
||||
}
|
||||
lg.Errorf("An incompatible key %s exists in the target bucket", newKey)
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// remove the sub-bucket from the source bucket
|
||||
delete(b.buckets, string(newKey))
|
||||
c.node().del(newKey)
|
||||
|
||||
// add te sub-bucket to the destination bucket
|
||||
newValue := cloneBytes(v)
|
||||
curDst.node().put(newKey, newKey, newValue, 0, common.BucketLeafFlag)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inspect returns the structure of the bucket.
|
||||
func (b *Bucket) Inspect() BucketStructure {
|
||||
return b.recursivelyInspect([]byte("root"))
|
||||
}
|
||||
|
||||
func (b *Bucket) recursivelyInspect(name []byte) BucketStructure {
|
||||
bs := BucketStructure{Name: string(name)}
|
||||
|
||||
keyN := 0
|
||||
c := b.Cursor()
|
||||
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
|
||||
if flags&common.BucketLeafFlag != 0 {
|
||||
childBucket := b.Bucket(k)
|
||||
childBS := childBucket.recursivelyInspect(k)
|
||||
bs.Children = append(bs.Children, childBS)
|
||||
} else {
|
||||
keyN++
|
||||
}
|
||||
}
|
||||
bs.KeyN = keyN
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
// Get retrieves the value for a key in the bucket.
|
||||
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
||||
// The returned value is only valid for the life of the transaction.
|
||||
// The returned memory is owned by bbolt and must never be modified; writing to this memory might corrupt the database.
|
||||
func (b *Bucket) Get(key []byte) []byte {
|
||||
k, v, flags := b.Cursor().seek(key)
|
||||
|
||||
// Return nil if this is a bucket.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -278,17 +449,27 @@ func (b *Bucket) Get(key []byte) []byte {
|
||||
// If the key exist then its previous value will be overwritten.
|
||||
// Supplied value must remain valid for the life of the transaction.
|
||||
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
|
||||
func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
func (b *Bucket) Put(key []byte, value []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Putting key %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Putting key %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Putting key %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
} else if len(key) == 0 {
|
||||
return ErrKeyRequired
|
||||
return errors.ErrKeyRequired
|
||||
} else if len(key) > MaxKeySize {
|
||||
return ErrKeyTooLarge
|
||||
return errors.ErrKeyTooLarge
|
||||
} else if int64(len(value)) > MaxValueSize {
|
||||
return ErrValueTooLarge
|
||||
return errors.ErrValueTooLarge
|
||||
}
|
||||
|
||||
// Insert into node.
|
||||
@@ -301,8 +482,8 @@ func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
k, _, flags := c.seek(newKey)
|
||||
|
||||
// Return an error if there is an existing key with a bucket value.
|
||||
if bytes.Equal(newKey, k) && (flags&bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if bytes.Equal(newKey, k) && (flags&common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// gofail: var beforeBucketPut struct{}
|
||||
@@ -315,11 +496,22 @@ func (b *Bucket) Put(key []byte, value []byte) error {
|
||||
// Delete removes a key from the bucket.
|
||||
// If the key does not exist then nothing is done and a nil error is returned.
|
||||
// Returns an error if the bucket was created from a read-only transaction.
|
||||
func (b *Bucket) Delete(key []byte) error {
|
||||
func (b *Bucket) Delete(key []byte) (err error) {
|
||||
if lg := b.tx.db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Deleting key %q", key)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Deleting key %q failed: %v", key, err)
|
||||
} else {
|
||||
lg.Debugf("Deleting key %q successfully", key)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Move cursor to correct position.
|
||||
@@ -332,8 +524,8 @@ func (b *Bucket) Delete(key []byte) error {
|
||||
}
|
||||
|
||||
// Return an error if there is already existing bucket value.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Delete the node if we have a matching key.
|
||||
@@ -343,44 +535,46 @@ func (b *Bucket) Delete(key []byte) error {
|
||||
}
|
||||
|
||||
// Sequence returns the current integer for the bucket without incrementing it.
|
||||
func (b *Bucket) Sequence() uint64 { return b.bucket.sequence }
|
||||
func (b *Bucket) Sequence() uint64 {
|
||||
return b.InSequence()
|
||||
}
|
||||
|
||||
// SetSequence updates the sequence number for the bucket.
|
||||
func (b *Bucket) SetSequence(v uint64) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Materialize the root node if it hasn't been already so that the
|
||||
// bucket will be saved during commit.
|
||||
if b.rootNode == nil {
|
||||
_ = b.node(b.root, nil)
|
||||
_ = b.node(b.RootPage(), nil)
|
||||
}
|
||||
|
||||
// Set the sequence.
|
||||
b.bucket.sequence = v
|
||||
b.SetInSequence(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextSequence returns an autoincrementing integer for the bucket.
|
||||
func (b *Bucket) NextSequence() (uint64, error) {
|
||||
if b.tx.db == nil {
|
||||
return 0, ErrTxClosed
|
||||
return 0, errors.ErrTxClosed
|
||||
} else if !b.Writable() {
|
||||
return 0, ErrTxNotWritable
|
||||
return 0, errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// Materialize the root node if it hasn't been already so that the
|
||||
// bucket will be saved during commit.
|
||||
if b.rootNode == nil {
|
||||
_ = b.node(b.root, nil)
|
||||
_ = b.node(b.RootPage(), nil)
|
||||
}
|
||||
|
||||
// Increment and return the sequence.
|
||||
b.bucket.sequence++
|
||||
return b.bucket.sequence, nil
|
||||
b.IncSequence()
|
||||
return b.Sequence(), nil
|
||||
}
|
||||
|
||||
// ForEach executes a function for each key/value pair in a bucket.
|
||||
@@ -390,7 +584,7 @@ func (b *Bucket) NextSequence() (uint64, error) {
|
||||
// the bucket; this will result in undefined behavior.
|
||||
func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
@@ -403,11 +597,11 @@ func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
|
||||
func (b *Bucket) ForEachBucket(fn func(k []byte) error) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, _, flags := c.first(); k != nil; k, _, flags = c.next() {
|
||||
if flags&bucketLeafFlag != 0 {
|
||||
if flags&common.BucketLeafFlag != 0 {
|
||||
if err := fn(k); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -421,64 +615,64 @@ func (b *Bucket) Stats() BucketStats {
|
||||
var s, subStats BucketStats
|
||||
pageSize := b.tx.db.pageSize
|
||||
s.BucketN += 1
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
s.InlineBucketN += 1
|
||||
}
|
||||
b.forEachPage(func(p *page, depth int, pgstack []pgid) {
|
||||
if (p.flags & leafPageFlag) != 0 {
|
||||
s.KeyN += int(p.count)
|
||||
b.forEachPage(func(p *common.Page, depth int, pgstack []common.Pgid) {
|
||||
if p.IsLeafPage() {
|
||||
s.KeyN += int(p.Count())
|
||||
|
||||
// used totals the used bytes for the page
|
||||
used := pageHeaderSize
|
||||
used := common.PageHeaderSize
|
||||
|
||||
if p.count != 0 {
|
||||
if p.Count() != 0 {
|
||||
// If page has any elements, add all element headers.
|
||||
used += leafPageElementSize * uintptr(p.count-1)
|
||||
used += common.LeafPageElementSize * uintptr(p.Count()-1)
|
||||
|
||||
// Add all element key, value sizes.
|
||||
// The computation takes advantage of the fact that the position
|
||||
// of the last element's key/value equals to the total of the sizes
|
||||
// of all previous elements' keys and values.
|
||||
// It also includes the last element's header.
|
||||
lastElement := p.leafPageElement(p.count - 1)
|
||||
used += uintptr(lastElement.pos + lastElement.ksize + lastElement.vsize)
|
||||
lastElement := p.LeafPageElement(p.Count() - 1)
|
||||
used += uintptr(lastElement.Pos() + lastElement.Ksize() + lastElement.Vsize())
|
||||
}
|
||||
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
// For inlined bucket just update the inline stats
|
||||
s.InlineBucketInuse += int(used)
|
||||
} else {
|
||||
// For non-inlined bucket update all the leaf stats
|
||||
s.LeafPageN++
|
||||
s.LeafInuse += int(used)
|
||||
s.LeafOverflowN += int(p.overflow)
|
||||
s.LeafOverflowN += int(p.Overflow())
|
||||
|
||||
// Collect stats from sub-buckets.
|
||||
// Do that by iterating over all element headers
|
||||
// looking for the ones with the bucketLeafFlag.
|
||||
for i := uint16(0); i < p.count; i++ {
|
||||
e := p.leafPageElement(i)
|
||||
if (e.flags & bucketLeafFlag) != 0 {
|
||||
for i := uint16(0); i < p.Count(); i++ {
|
||||
e := p.LeafPageElement(i)
|
||||
if (e.Flags() & common.BucketLeafFlag) != 0 {
|
||||
// For any bucket element, open the element value
|
||||
// and recursively call Stats on the contained bucket.
|
||||
subStats.Add(b.openBucket(e.value()).Stats())
|
||||
subStats.Add(b.openBucket(e.Value()).Stats())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (p.flags & branchPageFlag) != 0 {
|
||||
} else if p.IsBranchPage() {
|
||||
s.BranchPageN++
|
||||
lastElement := p.branchPageElement(p.count - 1)
|
||||
lastElement := p.BranchPageElement(p.Count() - 1)
|
||||
|
||||
// used totals the used bytes for the page
|
||||
// Add header and all element headers.
|
||||
used := pageHeaderSize + (branchPageElementSize * uintptr(p.count-1))
|
||||
used := common.PageHeaderSize + (common.BranchPageElementSize * uintptr(p.Count()-1))
|
||||
|
||||
// Add size of all keys and values.
|
||||
// Again, use the fact that last element's position equals to
|
||||
// the total of key, value sizes of all previous elements.
|
||||
used += uintptr(lastElement.pos + lastElement.ksize)
|
||||
used += uintptr(lastElement.Pos() + lastElement.Ksize())
|
||||
s.BranchInuse += int(used)
|
||||
s.BranchOverflowN += int(p.overflow)
|
||||
s.BranchOverflowN += int(p.Overflow())
|
||||
}
|
||||
|
||||
// Keep track of maximum page depth.
|
||||
@@ -499,29 +693,29 @@ func (b *Bucket) Stats() BucketStats {
|
||||
}
|
||||
|
||||
// forEachPage iterates over every page in a bucket, including inline pages.
|
||||
func (b *Bucket) forEachPage(fn func(*page, int, []pgid)) {
|
||||
func (b *Bucket) forEachPage(fn func(*common.Page, int, []common.Pgid)) {
|
||||
// If we have an inline page then just use that.
|
||||
if b.page != nil {
|
||||
fn(b.page, 0, []pgid{b.root})
|
||||
fn(b.page, 0, []common.Pgid{b.RootPage()})
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise traverse the page hierarchy.
|
||||
b.tx.forEachPage(b.root, fn)
|
||||
b.tx.forEachPage(b.RootPage(), fn)
|
||||
}
|
||||
|
||||
// forEachPageNode iterates over every page (or node) in a bucket.
|
||||
// This also includes inline pages.
|
||||
func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) {
|
||||
func (b *Bucket) forEachPageNode(fn func(*common.Page, *node, int)) {
|
||||
// If we have an inline page or root node then just use that.
|
||||
if b.page != nil {
|
||||
fn(b.page, nil, 0)
|
||||
return
|
||||
}
|
||||
b._forEachPageNode(b.root, 0, fn)
|
||||
b._forEachPageNode(b.RootPage(), 0, fn)
|
||||
}
|
||||
|
||||
func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, int)) {
|
||||
func (b *Bucket) _forEachPageNode(pgId common.Pgid, depth int, fn func(*common.Page, *node, int)) {
|
||||
var p, n = b.pageNode(pgId)
|
||||
|
||||
// Execute function.
|
||||
@@ -529,16 +723,16 @@ func (b *Bucket) _forEachPageNode(pgId pgid, depth int, fn func(*page, *node, in
|
||||
|
||||
// Recursively loop over children.
|
||||
if p != nil {
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
b._forEachPageNode(elem.pgid, depth+1, fn)
|
||||
if p.IsBranchPage() {
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
b._forEachPageNode(elem.Pgid(), depth+1, fn)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !n.isLeaf {
|
||||
for _, inode := range n.inodes {
|
||||
b._forEachPageNode(inode.pgid, depth+1, fn)
|
||||
b._forEachPageNode(inode.Pgid(), depth+1, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,9 +755,9 @@ func (b *Bucket) spill() error {
|
||||
}
|
||||
|
||||
// Update the child bucket header in this bucket.
|
||||
value = make([]byte, unsafe.Sizeof(bucket{}))
|
||||
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *child.bucket
|
||||
value = make([]byte, unsafe.Sizeof(common.InBucket{}))
|
||||
var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *child.InBucket
|
||||
}
|
||||
|
||||
// Skip writing the bucket if there are no materialized nodes.
|
||||
@@ -577,10 +771,10 @@ func (b *Bucket) spill() error {
|
||||
if !bytes.Equal([]byte(name), k) {
|
||||
panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k))
|
||||
}
|
||||
if flags&bucketLeafFlag == 0 {
|
||||
if flags&common.BucketLeafFlag == 0 {
|
||||
panic(fmt.Sprintf("unexpected bucket header flag: %x", flags))
|
||||
}
|
||||
c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag)
|
||||
c.node().put([]byte(name), []byte(name), value, 0, common.BucketLeafFlag)
|
||||
}
|
||||
|
||||
// Ignore if there's not a materialized root node.
|
||||
@@ -595,16 +789,16 @@ func (b *Bucket) spill() error {
|
||||
b.rootNode = b.rootNode.root()
|
||||
|
||||
// Update the root node for this bucket.
|
||||
if b.rootNode.pgid >= b.tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid))
|
||||
if b.rootNode.pgid >= b.tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.Pgid()))
|
||||
}
|
||||
b.root = b.rootNode.pgid
|
||||
b.SetRootPage(b.rootNode.pgid)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// inlineable returns true if a bucket is small enough to be written inline
|
||||
// and if it contains no subbuckets. Otherwise returns false.
|
||||
// and if it contains no subbuckets. Otherwise, returns false.
|
||||
func (b *Bucket) inlineable() bool {
|
||||
var n = b.rootNode
|
||||
|
||||
@@ -615,11 +809,11 @@ func (b *Bucket) inlineable() bool {
|
||||
|
||||
// Bucket is not inlineable if it contains subbuckets or if it goes beyond
|
||||
// our threshold for inline bucket size.
|
||||
var size = pageHeaderSize
|
||||
var size = common.PageHeaderSize
|
||||
for _, inode := range n.inodes {
|
||||
size += leafPageElementSize + uintptr(len(inode.key)) + uintptr(len(inode.value))
|
||||
size += common.LeafPageElementSize + uintptr(len(inode.Key())) + uintptr(len(inode.Value()))
|
||||
|
||||
if inode.flags&bucketLeafFlag != 0 {
|
||||
if inode.Flags()&common.BucketLeafFlag != 0 {
|
||||
return false
|
||||
} else if size > b.maxInlineBucketSize() {
|
||||
return false
|
||||
@@ -638,14 +832,14 @@ func (b *Bucket) maxInlineBucketSize() uintptr {
|
||||
func (b *Bucket) write() []byte {
|
||||
// Allocate the appropriate size.
|
||||
var n = b.rootNode
|
||||
var value = make([]byte, bucketHeaderSize+n.size())
|
||||
var value = make([]byte, common.BucketHeaderSize+n.size())
|
||||
|
||||
// Write a bucket header.
|
||||
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *b.bucket
|
||||
var bucket = (*common.InBucket)(unsafe.Pointer(&value[0]))
|
||||
*bucket = *b.InBucket
|
||||
|
||||
// Convert byte slice to a fake page and write the root node.
|
||||
var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||
var p = (*common.Page)(unsafe.Pointer(&value[common.BucketHeaderSize]))
|
||||
n.write(p)
|
||||
|
||||
return value
|
||||
@@ -662,8 +856,8 @@ func (b *Bucket) rebalance() {
|
||||
}
|
||||
|
||||
// node creates a node from a page and associates it with a given parent.
|
||||
func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
_assert(b.nodes != nil, "nodes map expected")
|
||||
func (b *Bucket) node(pgId common.Pgid, parent *node) *node {
|
||||
common.Assert(b.nodes != nil, "nodes map expected")
|
||||
|
||||
// Retrieve node if it's already been created.
|
||||
if n := b.nodes[pgId]; n != nil {
|
||||
@@ -682,6 +876,12 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
var p = b.page
|
||||
if p == nil {
|
||||
p = b.tx.page(pgId)
|
||||
} else {
|
||||
// if p isn't nil, then it's an inline bucket.
|
||||
// The pgId must be 0 in this case.
|
||||
common.Verify(func() {
|
||||
common.Assert(pgId == 0, "The page ID (%d) isn't 0 for an inline bucket", pgId)
|
||||
})
|
||||
}
|
||||
|
||||
// Read the page into the node and cache it.
|
||||
@@ -696,19 +896,19 @@ func (b *Bucket) node(pgId pgid, parent *node) *node {
|
||||
|
||||
// free recursively frees all pages in the bucket.
|
||||
func (b *Bucket) free() {
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var tx = b.tx
|
||||
b.forEachPageNode(func(p *page, n *node, _ int) {
|
||||
b.forEachPageNode(func(p *common.Page, n *node, _ int) {
|
||||
if p != nil {
|
||||
tx.db.freelist.free(tx.meta.txid, p)
|
||||
tx.db.freelist.Free(tx.meta.Txid(), p)
|
||||
} else {
|
||||
n.free()
|
||||
}
|
||||
})
|
||||
b.root = 0
|
||||
b.SetRootPage(0)
|
||||
}
|
||||
|
||||
// dereference removes all references to the old mmap.
|
||||
@@ -723,11 +923,11 @@ func (b *Bucket) dereference() {
|
||||
}
|
||||
|
||||
// pageNode returns the in-memory node, if it exists.
|
||||
// Otherwise returns the underlying page.
|
||||
func (b *Bucket) pageNode(id pgid) (*page, *node) {
|
||||
// Otherwise, returns the underlying page.
|
||||
func (b *Bucket) pageNode(id common.Pgid) (*common.Page, *node) {
|
||||
// Inline buckets have a fake page embedded in their value so treat them
|
||||
// differently. We'll return the rootNode (if available) or the fake page.
|
||||
if b.root == 0 {
|
||||
if b.RootPage() == 0 {
|
||||
if id != 0 {
|
||||
panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id))
|
||||
}
|
||||
@@ -797,3 +997,9 @@ func cloneBytes(v []byte) []byte {
|
||||
copy(clone, v)
|
||||
return clone
|
||||
}
|
||||
|
||||
type BucketStructure struct {
|
||||
Name string `json:"name"` // name of the bucket
|
||||
KeyN int `json:"keyN"` // number of key/value pairs
|
||||
Children []BucketStructure `json:"buckets,omitempty"` // child buckets
|
||||
}
|
||||
|
||||
99
vendor/go.etcd.io/bbolt/cursor.go
generated
vendored
99
vendor/go.etcd.io/bbolt/cursor.go
generated
vendored
@@ -4,6 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket
|
||||
@@ -30,9 +33,9 @@ func (c *Cursor) Bucket() *Bucket {
|
||||
// If the bucket is empty then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) First() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.first()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -40,7 +43,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
|
||||
|
||||
func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
c.stack = c.stack[:0]
|
||||
p, n := c.bucket.pageNode(c.bucket.root)
|
||||
p, n := c.bucket.pageNode(c.bucket.RootPage())
|
||||
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||
c.goToFirstElementOnTheStack()
|
||||
|
||||
@@ -51,7 +54,7 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
}
|
||||
|
||||
k, v, flags := c.keyValue()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil, flags
|
||||
}
|
||||
return k, v, flags
|
||||
@@ -61,9 +64,9 @@ func (c *Cursor) first() (key []byte, value []byte, flags uint32) {
|
||||
// If the bucket is empty then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
c.stack = c.stack[:0]
|
||||
p, n := c.bucket.pageNode(c.bucket.root)
|
||||
p, n := c.bucket.pageNode(c.bucket.RootPage())
|
||||
ref := elemRef{page: p, node: n}
|
||||
ref.index = ref.count() - 1
|
||||
c.stack = append(c.stack, ref)
|
||||
@@ -80,7 +83,7 @@ func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
}
|
||||
|
||||
k, v, flags := c.keyValue()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -90,9 +93,9 @@ func (c *Cursor) Last() (key []byte, value []byte) {
|
||||
// If the cursor is at the end of the bucket then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Next() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.next()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -102,9 +105,9 @@ func (c *Cursor) Next() (key []byte, value []byte) {
|
||||
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
k, v, flags := c.prev()
|
||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -115,7 +118,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||
// follow, a nil key is returned.
|
||||
// The returned key and value are only valid for the life of the transaction.
|
||||
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||
common.Assert(c.bucket.tx.db != nil, "tx closed")
|
||||
|
||||
k, v, flags := c.seek(seek)
|
||||
|
||||
@@ -126,7 +129,7 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
|
||||
if k == nil {
|
||||
return nil, nil
|
||||
} else if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||
} else if (flags & uint32(common.BucketLeafFlag)) != 0 {
|
||||
return k, nil
|
||||
}
|
||||
return k, v
|
||||
@@ -136,15 +139,15 @@ func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||
// Delete fails if current key/value is a bucket or if the transaction is not writable.
|
||||
func (c *Cursor) Delete() error {
|
||||
if c.bucket.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return errors.ErrTxClosed
|
||||
} else if !c.bucket.Writable() {
|
||||
return ErrTxNotWritable
|
||||
return errors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
key, _, flags := c.keyValue()
|
||||
// Return an error if current value is a bucket.
|
||||
if (flags & bucketLeafFlag) != 0 {
|
||||
return ErrIncompatibleValue
|
||||
if (flags & common.BucketLeafFlag) != 0 {
|
||||
return errors.ErrIncompatibleValue
|
||||
}
|
||||
c.node().del(key)
|
||||
|
||||
@@ -156,7 +159,7 @@ func (c *Cursor) Delete() error {
|
||||
func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
|
||||
// Start from root page/node and traverse to correct page.
|
||||
c.stack = c.stack[:0]
|
||||
c.search(seek, c.bucket.root)
|
||||
c.search(seek, c.bucket.RootPage())
|
||||
|
||||
// If this is a bucket then return a nil value.
|
||||
return c.keyValue()
|
||||
@@ -172,11 +175,11 @@ func (c *Cursor) goToFirstElementOnTheStack() {
|
||||
}
|
||||
|
||||
// Keep adding pages pointing to the first element to the stack.
|
||||
var pgId pgid
|
||||
var pgId common.Pgid
|
||||
if ref.node != nil {
|
||||
pgId = ref.node.inodes[ref.index].pgid
|
||||
pgId = ref.node.inodes[ref.index].Pgid()
|
||||
} else {
|
||||
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||
pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
|
||||
}
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||
@@ -193,11 +196,11 @@ func (c *Cursor) last() {
|
||||
}
|
||||
|
||||
// Keep adding pages pointing to the last element in the stack.
|
||||
var pgId pgid
|
||||
var pgId common.Pgid
|
||||
if ref.node != nil {
|
||||
pgId = ref.node.inodes[ref.index].pgid
|
||||
pgId = ref.node.inodes[ref.index].Pgid()
|
||||
} else {
|
||||
pgId = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||
pgId = ref.page.BranchPageElement(uint16(ref.index)).Pgid()
|
||||
}
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
|
||||
@@ -277,10 +280,10 @@ func (c *Cursor) prev() (key []byte, value []byte, flags uint32) {
|
||||
}
|
||||
|
||||
// search recursively performs a binary search against a given page/node until it finds a given key.
|
||||
func (c *Cursor) search(key []byte, pgId pgid) {
|
||||
func (c *Cursor) search(key []byte, pgId common.Pgid) {
|
||||
p, n := c.bucket.pageNode(pgId)
|
||||
if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
|
||||
panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
|
||||
if p != nil && !p.IsBranchPage() && !p.IsLeafPage() {
|
||||
panic(fmt.Sprintf("invalid page type: %d: %x", p.Id(), p.Flags()))
|
||||
}
|
||||
e := elemRef{page: p, node: n}
|
||||
c.stack = append(c.stack, e)
|
||||
@@ -303,7 +306,7 @@ func (c *Cursor) searchNode(key []byte, n *node) {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||
ret := bytes.Compare(n.inodes[i].key, key)
|
||||
ret := bytes.Compare(n.inodes[i].Key(), key)
|
||||
if ret == 0 {
|
||||
exact = true
|
||||
}
|
||||
@@ -315,18 +318,18 @@ func (c *Cursor) searchNode(key []byte, n *node) {
|
||||
c.stack[len(c.stack)-1].index = index
|
||||
|
||||
// Recursively search to the next page.
|
||||
c.search(key, n.inodes[index].pgid)
|
||||
c.search(key, n.inodes[index].Pgid())
|
||||
}
|
||||
|
||||
func (c *Cursor) searchPage(key []byte, p *page) {
|
||||
func (c *Cursor) searchPage(key []byte, p *common.Page) {
|
||||
// Binary search for the correct range.
|
||||
inodes := p.branchPageElements()
|
||||
inodes := p.BranchPageElements()
|
||||
|
||||
var exact bool
|
||||
index := sort.Search(int(p.count), func(i int) bool {
|
||||
index := sort.Search(int(p.Count()), func(i int) bool {
|
||||
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||
ret := bytes.Compare(inodes[i].key(), key)
|
||||
ret := bytes.Compare(inodes[i].Key(), key)
|
||||
if ret == 0 {
|
||||
exact = true
|
||||
}
|
||||
@@ -338,7 +341,7 @@ func (c *Cursor) searchPage(key []byte, p *page) {
|
||||
c.stack[len(c.stack)-1].index = index
|
||||
|
||||
// Recursively search to the next page.
|
||||
c.search(key, inodes[index].pgid)
|
||||
c.search(key, inodes[index].Pgid())
|
||||
}
|
||||
|
||||
// nsearch searches the leaf node on the top of the stack for a key.
|
||||
@@ -349,16 +352,16 @@ func (c *Cursor) nsearch(key []byte) {
|
||||
// If we have a node then search its inodes.
|
||||
if n != nil {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||
return bytes.Compare(n.inodes[i].key, key) != -1
|
||||
return bytes.Compare(n.inodes[i].Key(), key) != -1
|
||||
})
|
||||
e.index = index
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a page then search its leaf elements.
|
||||
inodes := p.leafPageElements()
|
||||
index := sort.Search(int(p.count), func(i int) bool {
|
||||
return bytes.Compare(inodes[i].key(), key) != -1
|
||||
inodes := p.LeafPageElements()
|
||||
index := sort.Search(int(p.Count()), func(i int) bool {
|
||||
return bytes.Compare(inodes[i].Key(), key) != -1
|
||||
})
|
||||
e.index = index
|
||||
}
|
||||
@@ -375,17 +378,17 @@ func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
|
||||
// Retrieve value from node.
|
||||
if ref.node != nil {
|
||||
inode := &ref.node.inodes[ref.index]
|
||||
return inode.key, inode.value, inode.flags
|
||||
return inode.Key(), inode.Value(), inode.Flags()
|
||||
}
|
||||
|
||||
// Or retrieve value from page.
|
||||
elem := ref.page.leafPageElement(uint16(ref.index))
|
||||
return elem.key(), elem.value(), elem.flags
|
||||
elem := ref.page.LeafPageElement(uint16(ref.index))
|
||||
return elem.Key(), elem.Value(), elem.Flags()
|
||||
}
|
||||
|
||||
// node returns the node that the cursor is currently positioned on.
|
||||
func (c *Cursor) node() *node {
|
||||
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
||||
common.Assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
||||
|
||||
// If the top of the stack is a leaf node then just return it.
|
||||
if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
|
||||
@@ -395,19 +398,19 @@ func (c *Cursor) node() *node {
|
||||
// Start from root and traverse down the hierarchy.
|
||||
var n = c.stack[0].node
|
||||
if n == nil {
|
||||
n = c.bucket.node(c.stack[0].page.id, nil)
|
||||
n = c.bucket.node(c.stack[0].page.Id(), nil)
|
||||
}
|
||||
for _, ref := range c.stack[:len(c.stack)-1] {
|
||||
_assert(!n.isLeaf, "expected branch node")
|
||||
common.Assert(!n.isLeaf, "expected branch node")
|
||||
n = n.childAt(ref.index)
|
||||
}
|
||||
_assert(n.isLeaf, "expected leaf node")
|
||||
common.Assert(n.isLeaf, "expected leaf node")
|
||||
return n
|
||||
}
|
||||
|
||||
// elemRef represents a reference to an element on a given page/node.
|
||||
type elemRef struct {
|
||||
page *page
|
||||
page *common.Page
|
||||
node *node
|
||||
index int
|
||||
}
|
||||
@@ -417,7 +420,7 @@ func (r *elemRef) isLeaf() bool {
|
||||
if r.node != nil {
|
||||
return r.node.isLeaf
|
||||
}
|
||||
return (r.page.flags & leafPageFlag) != 0
|
||||
return r.page.IsLeafPage()
|
||||
}
|
||||
|
||||
// count returns the number of inodes or page elements.
|
||||
@@ -425,5 +428,5 @@ func (r *elemRef) count() int {
|
||||
if r.node != nil {
|
||||
return len(r.node.inodes)
|
||||
}
|
||||
return int(r.page.count)
|
||||
return int(r.page.Count())
|
||||
}
|
||||
|
||||
467
vendor/go.etcd.io/bbolt/db.go
generated
vendored
467
vendor/go.etcd.io/bbolt/db.go
generated
vendored
@@ -3,49 +3,28 @@ package bbolt
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
berrors "go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
fl "go.etcd.io/bbolt/internal/freelist"
|
||||
)
|
||||
|
||||
// The largest step that can be taken when remapping the mmap.
|
||||
const maxMmapStep = 1 << 30 // 1GB
|
||||
|
||||
// The data file format version.
|
||||
const version = 2
|
||||
|
||||
// Represents a marker value to indicate that a file is a Bolt DB.
|
||||
const magic uint32 = 0xED0CDAED
|
||||
|
||||
const pgidNoFreelist pgid = 0xffffffffffffffff
|
||||
|
||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||
// syncing changes to a file. This is required as some operating systems,
|
||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||
// must be synchronized using the msync(2) syscall.
|
||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||
|
||||
// Default values if not set in a DB instance.
|
||||
const (
|
||||
DefaultMaxBatchSize int = 1000
|
||||
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||
DefaultAllocSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
// default page size for db is set to the OS page size.
|
||||
var defaultPageSize = os.Getpagesize()
|
||||
|
||||
// The time elapsed between consecutive file locking attempts.
|
||||
const flockRetryTimeout = 50 * time.Millisecond
|
||||
|
||||
// FreelistType is the type of the freelist backend
|
||||
type FreelistType string
|
||||
|
||||
// TODO(ahrtr): eventually we should (step by step)
|
||||
// 1. default to `FreelistMapType`;
|
||||
// 2. remove the `FreelistArrayType`, do not export `FreelistMapType`
|
||||
// and remove field `FreelistType' from both `DB` and `Options`;
|
||||
const (
|
||||
// FreelistArrayType indicates backend freelist type is array
|
||||
FreelistArrayType = FreelistType("array")
|
||||
@@ -137,6 +116,8 @@ type DB struct {
|
||||
// Supported only on Unix via mlock/munlock syscalls.
|
||||
Mlock bool
|
||||
|
||||
logger Logger
|
||||
|
||||
path string
|
||||
openFile func(string, int, os.FileMode) (*os.File, error)
|
||||
file *os.File
|
||||
@@ -144,17 +125,16 @@ type DB struct {
|
||||
// always fails on Windows platform.
|
||||
//nolint
|
||||
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||
data *[maxMapSize]byte
|
||||
data *[common.MaxMapSize]byte
|
||||
datasz int
|
||||
filesz int // current on disk file size
|
||||
meta0 *meta
|
||||
meta1 *meta
|
||||
meta0 *common.Meta
|
||||
meta1 *common.Meta
|
||||
pageSize int
|
||||
opened bool
|
||||
rwtx *Tx
|
||||
txs []*Tx
|
||||
|
||||
freelist *freelist
|
||||
freelist fl.Interface
|
||||
freelistLoad sync.Once
|
||||
|
||||
pagePool sync.Pool
|
||||
@@ -191,13 +171,15 @@ func (db *DB) String() string {
|
||||
return fmt.Sprintf("DB<%q>", db.path)
|
||||
}
|
||||
|
||||
// Open creates and opens a database at the given path.
|
||||
// If the file does not exist then it will be created automatically.
|
||||
// Open creates and opens a database at the given path with a given file mode.
|
||||
// If the file does not exist then it will be created automatically with a given file mode.
|
||||
// Passing in nil options will cause Bolt to open the database with the default options.
|
||||
func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
db := &DB{
|
||||
// Note: For read/write transactions, ensure the owner has write permission on the created/opened database file, e.g. 0600
|
||||
func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
|
||||
db = &DB{
|
||||
opened: true,
|
||||
}
|
||||
|
||||
// Set default options if no options are provided.
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
@@ -211,9 +193,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
db.Mlock = options.Mlock
|
||||
|
||||
// Set default values for later DB operations.
|
||||
db.MaxBatchSize = DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = DefaultMaxBatchDelay
|
||||
db.AllocSize = DefaultAllocSize
|
||||
db.MaxBatchSize = common.DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = common.DefaultMaxBatchDelay
|
||||
db.AllocSize = common.DefaultAllocSize
|
||||
|
||||
if options.Logger == nil {
|
||||
db.logger = getDiscardLogger()
|
||||
} else {
|
||||
db.logger = options.Logger
|
||||
}
|
||||
|
||||
lg := db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Infof("Opening db file (%s) with mode %s and with options: %s", path, mode, options)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Opening bbolt db (%s) failed: %v", path, err)
|
||||
} else {
|
||||
lg.Infof("Opening bbolt db (%s) successfully", path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
flag := os.O_RDWR
|
||||
if options.ReadOnly {
|
||||
@@ -222,6 +222,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
} else {
|
||||
// always load free pages in write mode
|
||||
db.PreLoadFreelist = true
|
||||
flag |= os.O_CREATE
|
||||
}
|
||||
|
||||
db.openFile = options.OpenFile
|
||||
@@ -230,9 +231,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
}
|
||||
|
||||
// Open data file and separate sync handler for metadata writes.
|
||||
var err error
|
||||
if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil {
|
||||
if db.file, err = db.openFile(path, flag, mode); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to open db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
db.path = db.file.Name()
|
||||
@@ -244,8 +245,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
// if !options.ReadOnly.
|
||||
// The database file is locked using the shared lock (more than one process may
|
||||
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
||||
if err := flock(db, !db.readOnly, options.Timeout); err != nil {
|
||||
if err = flock(db, !db.readOnly, options.Timeout); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to lock db file (%s), readonly: %t, error: %v", path, db.readOnly, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -254,27 +256,28 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
|
||||
if db.pageSize = options.PageSize; db.pageSize == 0 {
|
||||
// Set the default page size to the OS page size.
|
||||
db.pageSize = defaultPageSize
|
||||
db.pageSize = common.DefaultPageSize
|
||||
}
|
||||
|
||||
// Initialize the database if it doesn't exist.
|
||||
if info, err := db.file.Stat(); err != nil {
|
||||
if info, statErr := db.file.Stat(); statErr != nil {
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
lg.Errorf("failed to get db file's stats (%s): %v", path, err)
|
||||
return nil, statErr
|
||||
} else if info.Size() == 0 {
|
||||
// Initialize new files with meta pages.
|
||||
if err := db.init(); err != nil {
|
||||
if err = db.init(); err != nil {
|
||||
// clean up file descriptor on initialization fail
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to initialize db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// try to get the page size from the metadata pages
|
||||
if pgSize, err := db.getPageSize(); err == nil {
|
||||
db.pageSize = pgSize
|
||||
} else {
|
||||
if db.pageSize, err = db.getPageSize(); err != nil {
|
||||
_ = db.close()
|
||||
return nil, ErrInvalid
|
||||
lg.Errorf("failed to get page size from db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +289,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
}
|
||||
|
||||
// Memory map the data file.
|
||||
if err := db.mmap(options.InitialMmapSize); err != nil {
|
||||
if err = db.mmap(options.InitialMmapSize); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to map db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -302,13 +306,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
// Flush freelist when transitioning from no sync to sync so
|
||||
// NoFreelistSync unaware boltdb can open the db later.
|
||||
if !db.NoFreelistSync && !db.hasSyncedFreelist() {
|
||||
tx, err := db.Begin(true)
|
||||
tx, txErr := db.Begin(true)
|
||||
if tx != nil {
|
||||
err = tx.Commit()
|
||||
txErr = tx.Commit()
|
||||
}
|
||||
if err != nil {
|
||||
if txErr != nil {
|
||||
lg.Errorf("starting readwrite transaction failed: %v", txErr)
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
return nil, txErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +357,7 @@ func (db *DB) getPageSize() (int, error) {
|
||||
return db.pageSize, nil
|
||||
}
|
||||
|
||||
return 0, ErrInvalid
|
||||
return 0, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromFirstMeta reads the pageSize from the first meta page
|
||||
@@ -361,11 +366,11 @@ func (db *DB) getPageSizeFromFirstMeta() (int, bool, error) {
|
||||
var metaCanRead bool
|
||||
if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromSecondMeta reads the pageSize from the second meta page
|
||||
@@ -397,13 +402,13 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
|
||||
bw, err := db.file.ReadAt(buf[:], pos)
|
||||
if (err == nil && bw == len(buf)) || (err == io.EOF && int64(bw) == (fileSize-pos)) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// loadFreelist reads the freelist if it is synced, or reconstructs it
|
||||
@@ -414,17 +419,29 @@ func (db *DB) loadFreelist() {
|
||||
db.freelist = newFreelist(db.FreelistType)
|
||||
if !db.hasSyncedFreelist() {
|
||||
// Reconstruct free list by scanning the DB.
|
||||
db.freelist.readIDs(db.freepages())
|
||||
db.freelist.Init(db.freepages())
|
||||
} else {
|
||||
// Read free list from freelist page.
|
||||
db.freelist.read(db.page(db.meta().freelist))
|
||||
db.freelist.Read(db.page(db.meta().Freelist()))
|
||||
}
|
||||
db.stats.FreePageN = db.freelist.free_count()
|
||||
db.stats.FreePageN = db.freelist.FreeCount()
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) hasSyncedFreelist() bool {
|
||||
return db.meta().freelist != pgidNoFreelist
|
||||
return db.meta().Freelist() != common.PgidNoFreelist
|
||||
}
|
||||
|
||||
func (db *DB) fileSize() (int, error) {
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("file stat error: %w", err)
|
||||
}
|
||||
sz := int(info.Size())
|
||||
if sz < db.pageSize*2 {
|
||||
return 0, fmt.Errorf("file size too small %d", sz)
|
||||
}
|
||||
return sz, nil
|
||||
}
|
||||
|
||||
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
||||
@@ -433,21 +450,22 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
db.mmaplock.Lock()
|
||||
defer db.mmaplock.Unlock()
|
||||
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mmap stat error: %s", err)
|
||||
} else if int(info.Size()) < db.pageSize*2 {
|
||||
return fmt.Errorf("file size too small")
|
||||
}
|
||||
lg := db.Logger()
|
||||
|
||||
// Ensure the size is at least the minimum size.
|
||||
fileSize := int(info.Size())
|
||||
var fileSize int
|
||||
fileSize, err = db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
var size = fileSize
|
||||
if size < minsz {
|
||||
size = minsz
|
||||
}
|
||||
size, err = db.mmapSize(size)
|
||||
if err != nil {
|
||||
lg.Errorf("getting map size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -472,6 +490,7 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
// gofail: var mapError string
|
||||
// return errors.New(mapError)
|
||||
if err = mmap(db, size); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] mmap failed, size: %d, error: %v", runtime.GOOS, runtime.GOARCH, size, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -493,15 +512,16 @@ func (db *DB) mmap(minsz int) (err error) {
|
||||
}
|
||||
|
||||
// Save references to the meta pages.
|
||||
db.meta0 = db.page(0).meta()
|
||||
db.meta1 = db.page(1).meta()
|
||||
db.meta0 = db.page(0).Meta()
|
||||
db.meta1 = db.page(1).Meta()
|
||||
|
||||
// Validate the meta pages. We only return an error if both meta pages fail
|
||||
// validation, since meta0 failing validation means that it wasn't saved
|
||||
// properly -- but we can recover using meta1. And vice-versa.
|
||||
err0 := db.meta0.validate()
|
||||
err1 := db.meta1.validate()
|
||||
err0 := db.meta0.Validate()
|
||||
err1 := db.meta1.Validate()
|
||||
if err0 != nil && err1 != nil {
|
||||
lg.Errorf("both meta pages are invalid, meta0: %v, meta1: %v", err0, err1)
|
||||
return err0
|
||||
}
|
||||
|
||||
@@ -524,6 +544,7 @@ func (db *DB) munmap() error {
|
||||
// gofail: var unmapError string
|
||||
// return errors.New(unmapError)
|
||||
if err := munmap(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munmap failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("unmap error: %v", err.Error())
|
||||
}
|
||||
|
||||
@@ -542,14 +563,14 @@ func (db *DB) mmapSize(size int) (int, error) {
|
||||
}
|
||||
|
||||
// Verify the requested size is not above the maximum allowed.
|
||||
if size > maxMapSize {
|
||||
return 0, fmt.Errorf("mmap too large")
|
||||
if size > common.MaxMapSize {
|
||||
return 0, errors.New("mmap too large")
|
||||
}
|
||||
|
||||
// If larger than 1GB then grow by 1GB at a time.
|
||||
sz := int64(size)
|
||||
if remainder := sz % int64(maxMmapStep); remainder > 0 {
|
||||
sz += int64(maxMmapStep) - remainder
|
||||
if remainder := sz % int64(common.MaxMmapStep); remainder > 0 {
|
||||
sz += int64(common.MaxMmapStep) - remainder
|
||||
}
|
||||
|
||||
// Ensure that the mmap size is a multiple of the page size.
|
||||
@@ -560,8 +581,8 @@ func (db *DB) mmapSize(size int) (int, error) {
|
||||
}
|
||||
|
||||
// If we've exceeded the max size then only grow up to the max size.
|
||||
if sz > maxMapSize {
|
||||
sz = maxMapSize
|
||||
if sz > common.MaxMapSize {
|
||||
sz = common.MaxMapSize
|
||||
}
|
||||
|
||||
return int(sz), nil
|
||||
@@ -571,6 +592,7 @@ func (db *DB) munlock(fileSize int) error {
|
||||
// gofail: var munlockError string
|
||||
// return errors.New(munlockError)
|
||||
if err := munlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("munlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -580,6 +602,7 @@ func (db *DB) mlock(fileSize int) error {
|
||||
// gofail: var mlockError string
|
||||
// return errors.New(mlockError)
|
||||
if err := mlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] mlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("mlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
@@ -600,42 +623,43 @@ func (db *DB) init() error {
|
||||
// Create two meta pages on a buffer.
|
||||
buf := make([]byte, db.pageSize*4)
|
||||
for i := 0; i < 2; i++ {
|
||||
p := db.pageInBuffer(buf, pgid(i))
|
||||
p.id = pgid(i)
|
||||
p.flags = metaPageFlag
|
||||
p := db.pageInBuffer(buf, common.Pgid(i))
|
||||
p.SetId(common.Pgid(i))
|
||||
p.SetFlags(common.MetaPageFlag)
|
||||
|
||||
// Initialize the meta page.
|
||||
m := p.meta()
|
||||
m.magic = magic
|
||||
m.version = version
|
||||
m.pageSize = uint32(db.pageSize)
|
||||
m.freelist = 2
|
||||
m.root = bucket{root: 3}
|
||||
m.pgid = 4
|
||||
m.txid = txid(i)
|
||||
m.checksum = m.sum64()
|
||||
m := p.Meta()
|
||||
m.SetMagic(common.Magic)
|
||||
m.SetVersion(common.Version)
|
||||
m.SetPageSize(uint32(db.pageSize))
|
||||
m.SetFreelist(2)
|
||||
m.SetRootBucket(common.NewInBucket(3, 0))
|
||||
m.SetPgid(4)
|
||||
m.SetTxid(common.Txid(i))
|
||||
m.SetChecksum(m.Sum64())
|
||||
}
|
||||
|
||||
// Write an empty freelist at page 3.
|
||||
p := db.pageInBuffer(buf, pgid(2))
|
||||
p.id = pgid(2)
|
||||
p.flags = freelistPageFlag
|
||||
p.count = 0
|
||||
p := db.pageInBuffer(buf, common.Pgid(2))
|
||||
p.SetId(2)
|
||||
p.SetFlags(common.FreelistPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write an empty leaf page at page 4.
|
||||
p = db.pageInBuffer(buf, pgid(3))
|
||||
p.id = pgid(3)
|
||||
p.flags = leafPageFlag
|
||||
p.count = 0
|
||||
p = db.pageInBuffer(buf, common.Pgid(3))
|
||||
p.SetId(3)
|
||||
p.SetFlags(common.LeafPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write the buffer to our data file.
|
||||
if _, err := db.ops.writeAt(buf, 0); err != nil {
|
||||
db.Logger().Errorf("writeAt failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if err := fdatasync(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
db.filesz = len(buf)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -716,13 +740,31 @@ func (db *DB) close() error {
|
||||
//
|
||||
// IMPORTANT: You must close read-only transactions after you are finished or
|
||||
// else the database will not reclaim old pages.
|
||||
func (db *DB) Begin(writable bool) (*Tx, error) {
|
||||
func (db *DB) Begin(writable bool) (t *Tx, err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Starting a new transaction [writable: %t]", writable)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Starting a new transaction [writable: %t] failed: %v", writable, err)
|
||||
} else {
|
||||
lg.Debugf("Starting a new transaction [writable: %t] successfully", writable)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if writable {
|
||||
return db.beginRWTx()
|
||||
}
|
||||
return db.beginTx()
|
||||
}
|
||||
|
||||
func (db *DB) Logger() Logger {
|
||||
if db == nil || db.logger == nil {
|
||||
return getDiscardLogger()
|
||||
}
|
||||
return db.logger
|
||||
}
|
||||
|
||||
func (db *DB) beginTx() (*Tx, error) {
|
||||
// Lock the meta pages while we initialize the transaction. We obtain
|
||||
// the meta lock before the mmap lock because that's the order that the
|
||||
@@ -738,14 +780,14 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
if !db.opened {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
@@ -755,6 +797,9 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
// Keep track of transaction until it closes.
|
||||
db.txs = append(db.txs, t)
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.AddReadonlyTXID(t.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
@@ -771,7 +816,7 @@ func (db *DB) beginTx() (*Tx, error) {
|
||||
func (db *DB) beginRWTx() (*Tx, error) {
|
||||
// If the database was opened with Options.ReadOnly, return an error.
|
||||
if db.readOnly {
|
||||
return nil, ErrDatabaseReadOnly
|
||||
return nil, berrors.ErrDatabaseReadOnly
|
||||
}
|
||||
|
||||
// Obtain writer lock. This is released by the transaction when it closes.
|
||||
@@ -786,49 +831,23 @@ func (db *DB) beginRWTx() (*Tx, error) {
|
||||
// Exit if the database is not open yet.
|
||||
if !db.opened {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
t := &Tx{writable: true}
|
||||
t.init(db)
|
||||
db.rwtx = t
|
||||
db.freePages()
|
||||
db.freelist.ReleasePendingPages()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// freePages releases any pages associated with closed read-only transactions.
|
||||
func (db *DB) freePages() {
|
||||
// Free all pending pages prior to earliest open transaction.
|
||||
sort.Sort(txsById(db.txs))
|
||||
minid := txid(0xFFFFFFFFFFFFFFFF)
|
||||
if len(db.txs) > 0 {
|
||||
minid = db.txs[0].meta.txid
|
||||
}
|
||||
if minid > 0 {
|
||||
db.freelist.release(minid - 1)
|
||||
}
|
||||
// Release unused txid extents.
|
||||
for _, t := range db.txs {
|
||||
db.freelist.releaseRange(minid, t.meta.txid-1)
|
||||
minid = t.meta.txid + 1
|
||||
}
|
||||
db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF))
|
||||
// Any page both allocated and freed in an extent is safe to release.
|
||||
}
|
||||
|
||||
type txsById []*Tx
|
||||
|
||||
func (t txsById) Len() int { return len(t) }
|
||||
func (t txsById) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t txsById) Less(i, j int) bool { return t[i].meta.txid < t[j].meta.txid }
|
||||
|
||||
// removeTx removes a transaction from the database.
|
||||
func (db *DB) removeTx(tx *Tx) {
|
||||
// Release the read lock on the mmap.
|
||||
@@ -848,6 +867,9 @@ func (db *DB) removeTx(tx *Tx) {
|
||||
}
|
||||
}
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
@@ -1056,7 +1078,20 @@ func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
|
||||
//
|
||||
// This is not necessary under normal operation, however, if you use NoSync
|
||||
// then it allows you to force the database file to sync against the disk.
|
||||
func (db *DB) Sync() error { return fdatasync(db) }
|
||||
func (db *DB) Sync() (err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Syncing bbolt db (%s)", db.path)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing bbolt db (%s) failed: %v", runtime.GOOS, runtime.GOARCH, db.path, err)
|
||||
} else {
|
||||
lg.Debugf("Syncing bbolt db (%s) successfully", db.path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return fdatasync(db)
|
||||
}
|
||||
|
||||
// Stats retrieves ongoing performance stats for the database.
|
||||
// This is only updated when a transaction closes.
|
||||
@@ -1069,37 +1104,37 @@ func (db *DB) Stats() Stats {
|
||||
// This is for internal access to the raw data bytes from the C cursor, use
|
||||
// carefully, or not at all.
|
||||
func (db *DB) Info() *Info {
|
||||
_assert(db.data != nil, "database file isn't correctly mapped")
|
||||
common.Assert(db.data != nil, "database file isn't correctly mapped")
|
||||
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
|
||||
}
|
||||
|
||||
// page retrieves a page reference from the mmap based on the current page size.
|
||||
func (db *DB) page(id pgid) *page {
|
||||
pos := id * pgid(db.pageSize)
|
||||
return (*page)(unsafe.Pointer(&db.data[pos]))
|
||||
func (db *DB) page(id common.Pgid) *common.Page {
|
||||
pos := id * common.Pgid(db.pageSize)
|
||||
return (*common.Page)(unsafe.Pointer(&db.data[pos]))
|
||||
}
|
||||
|
||||
// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
|
||||
func (db *DB) pageInBuffer(b []byte, id pgid) *page {
|
||||
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
|
||||
func (db *DB) pageInBuffer(b []byte, id common.Pgid) *common.Page {
|
||||
return (*common.Page)(unsafe.Pointer(&b[id*common.Pgid(db.pageSize)]))
|
||||
}
|
||||
|
||||
// meta retrieves the current meta page reference.
|
||||
func (db *DB) meta() *meta {
|
||||
func (db *DB) meta() *common.Meta {
|
||||
// We have to return the meta with the highest txid which doesn't fail
|
||||
// validation. Otherwise, we can cause errors when in fact the database is
|
||||
// in a consistent state. metaA is the one with the higher txid.
|
||||
metaA := db.meta0
|
||||
metaB := db.meta1
|
||||
if db.meta1.txid > db.meta0.txid {
|
||||
if db.meta1.Txid() > db.meta0.Txid() {
|
||||
metaA = db.meta1
|
||||
metaB = db.meta0
|
||||
}
|
||||
|
||||
// Use higher meta page if valid. Otherwise, fallback to previous, if valid.
|
||||
if err := metaA.validate(); err == nil {
|
||||
if err := metaA.Validate(); err == nil {
|
||||
return metaA
|
||||
} else if err := metaB.validate(); err == nil {
|
||||
} else if err := metaB.Validate(); err == nil {
|
||||
return metaB
|
||||
}
|
||||
|
||||
@@ -1109,7 +1144,7 @@ func (db *DB) meta() *meta {
|
||||
}
|
||||
|
||||
// allocate returns a contiguous block of memory starting at a given page.
|
||||
func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
|
||||
// Allocate a temporary buffer for the page.
|
||||
var buf []byte
|
||||
if count == 1 {
|
||||
@@ -1117,17 +1152,18 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
} else {
|
||||
buf = make([]byte, count*db.pageSize)
|
||||
}
|
||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||
p.overflow = uint32(count - 1)
|
||||
p := (*common.Page)(unsafe.Pointer(&buf[0]))
|
||||
p.SetOverflow(uint32(count - 1))
|
||||
|
||||
// Use pages from the freelist if they are available.
|
||||
if p.id = db.freelist.allocate(txid, count); p.id != 0 {
|
||||
p.SetId(db.freelist.Allocate(txid, count))
|
||||
if p.Id() != 0 {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Resize mmap() if we're at the end.
|
||||
p.id = db.rwtx.meta.pgid
|
||||
var minsz = int((p.id+pgid(count))+1) * db.pageSize
|
||||
p.SetId(db.rwtx.meta.Pgid())
|
||||
var minsz = int((p.Id()+common.Pgid(count))+1) * db.pageSize
|
||||
if minsz >= db.datasz {
|
||||
if err := db.mmap(minsz); err != nil {
|
||||
return nil, fmt.Errorf("mmap allocate error: %s", err)
|
||||
@@ -1135,7 +1171,8 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
}
|
||||
|
||||
// Move the page id high water mark.
|
||||
db.rwtx.meta.pgid += pgid(count)
|
||||
curPgid := db.rwtx.meta.Pgid()
|
||||
db.rwtx.meta.SetPgid(curPgid + common.Pgid(count))
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -1143,7 +1180,13 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
// grow grows the size of the database to the given sz.
|
||||
func (db *DB) grow(sz int) error {
|
||||
// Ignore if the new size is less than available file size.
|
||||
if sz <= db.filesz {
|
||||
lg := db.Logger()
|
||||
fileSize, err := db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if sz <= fileSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1162,21 +1205,22 @@ func (db *DB) grow(sz int) error {
|
||||
// gofail: var resizeFileError string
|
||||
// return errors.New(resizeFileError)
|
||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] truncating file failed, size: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, sz, db.datasz, err)
|
||||
return fmt.Errorf("file resize error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := db.file.Sync(); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing file failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("file sync error: %s", err)
|
||||
}
|
||||
if db.Mlock {
|
||||
// unlock old file and lock new one
|
||||
if err := db.mrelock(db.filesz, sz); err != nil {
|
||||
if err := db.mrelock(fileSize, sz); err != nil {
|
||||
return fmt.Errorf("mlock/munlock error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.filesz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1184,7 +1228,7 @@ func (db *DB) IsReadOnly() bool {
|
||||
return db.readOnly
|
||||
}
|
||||
|
||||
func (db *DB) freepages() []pgid {
|
||||
func (db *DB) freepages() []common.Pgid {
|
||||
tx, err := db.beginTx()
|
||||
defer func() {
|
||||
err = tx.Rollback()
|
||||
@@ -1196,21 +1240,21 @@ func (db *DB) freepages() []pgid {
|
||||
panic("freepages: failed to open read only tx")
|
||||
}
|
||||
|
||||
reachable := make(map[pgid]*page)
|
||||
nofreed := make(map[pgid]bool)
|
||||
reachable := make(map[common.Pgid]*common.Page)
|
||||
nofreed := make(map[common.Pgid]bool)
|
||||
ech := make(chan error)
|
||||
go func() {
|
||||
for e := range ech {
|
||||
panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e))
|
||||
}
|
||||
}()
|
||||
tx.checkBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
close(ech)
|
||||
|
||||
// TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages.
|
||||
|
||||
var fids []pgid
|
||||
for i := pgid(2); i < db.meta().pgid; i++ {
|
||||
var fids []common.Pgid
|
||||
for i := common.Pgid(2); i < db.meta().Pgid(); i++ {
|
||||
if _, ok := reachable[i]; !ok {
|
||||
fids = append(fids, i)
|
||||
}
|
||||
@@ -1218,11 +1262,17 @@ func (db *DB) freepages() []pgid {
|
||||
return fids
|
||||
}
|
||||
|
||||
func newFreelist(freelistType FreelistType) fl.Interface {
|
||||
if freelistType == FreelistMapType {
|
||||
return fl.NewHashMapFreelist()
|
||||
}
|
||||
return fl.NewArrayFreelist()
|
||||
}
|
||||
|
||||
// Options represents the options that can be set when opening a database.
|
||||
type Options struct {
|
||||
// Timeout is the amount of time to wait to obtain a file lock.
|
||||
// When set to zero it will wait indefinitely. This option is only
|
||||
// available on Darwin and Linux.
|
||||
// When set to zero it will wait indefinitely.
|
||||
Timeout time.Duration
|
||||
|
||||
// Sets the DB.NoGrowSync flag before memory mapping the file.
|
||||
@@ -1259,6 +1309,12 @@ type Options struct {
|
||||
// If <=0, the initial map size is 0.
|
||||
// If initialMmapSize is smaller than the previous database size,
|
||||
// it takes no effect.
|
||||
//
|
||||
// Note: On Windows, due to platform limitations, the database file size
|
||||
// will be immediately resized to match `InitialMmapSize` (aligned to page size)
|
||||
// when the DB is opened. On non-Windows platforms, the file size will grow
|
||||
// dynamically based on the actual amount of written data, regardless of `InitialMmapSize`.
|
||||
// Refer to https://github.com/etcd-io/bbolt/issues/378#issuecomment-1378121966.
|
||||
InitialMmapSize int
|
||||
|
||||
// PageSize overrides the default OS page size.
|
||||
@@ -1277,6 +1333,19 @@ type Options struct {
|
||||
// It prevents potential page faults, however
|
||||
// used memory can't be reclaimed. (UNIX only)
|
||||
Mlock bool
|
||||
|
||||
// Logger is the logger used for bbolt.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
func (o *Options) String() string {
|
||||
if o == nil {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{Timeout: %s, NoGrowSync: %t, NoFreelistSync: %t, PreLoadFreelist: %t, FreelistType: %s, ReadOnly: %t, MmapFlags: %x, InitialMmapSize: %d, PageSize: %d, NoSync: %t, OpenFile: %p, Mlock: %t, Logger: %p}",
|
||||
o.Timeout, o.NoGrowSync, o.NoFreelistSync, o.PreLoadFreelist, o.FreelistType, o.ReadOnly, o.MmapFlags, o.InitialMmapSize, o.PageSize, o.NoSync, o.OpenFile, o.Mlock, o.Logger)
|
||||
|
||||
}
|
||||
|
||||
// DefaultOptions represent the options used if nil options are passed into Open().
|
||||
@@ -1327,65 +1396,3 @@ type Info struct {
|
||||
Data uintptr
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
magic uint32
|
||||
version uint32
|
||||
pageSize uint32
|
||||
flags uint32
|
||||
root bucket
|
||||
freelist pgid
|
||||
pgid pgid
|
||||
txid txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *meta) validate() error {
|
||||
if m.magic != magic {
|
||||
return ErrInvalid
|
||||
} else if m.version != version {
|
||||
return ErrVersionMismatch
|
||||
} else if m.checksum != m.sum64() {
|
||||
return ErrChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy copies one meta object to another.
|
||||
func (m *meta) copy(dest *meta) {
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// write writes the meta onto a page.
|
||||
func (m *meta) write(p *page) {
|
||||
if m.root.root >= m.pgid {
|
||||
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||
} else if m.freelist >= m.pgid && m.freelist != pgidNoFreelist {
|
||||
// TODO: reject pgidNoFreeList if !NoFreelistSync
|
||||
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||
}
|
||||
|
||||
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||
p.id = pgid(m.txid % 2)
|
||||
p.flags |= metaPageFlag
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.sum64()
|
||||
|
||||
m.copy(p.meta())
|
||||
}
|
||||
|
||||
// generates the checksum for the meta.
|
||||
func (m *meta) sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
// _assert will panic with a given formatted message if the given condition is false.
|
||||
func _assert(condition bool, msg string, v ...interface{}) {
|
||||
if !condition {
|
||||
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||
}
|
||||
}
|
||||
|
||||
76
vendor/go.etcd.io/bbolt/errors.go
generated
vendored
76
vendor/go.etcd.io/bbolt/errors.go
generated
vendored
@@ -1,78 +1,108 @@
|
||||
package bbolt
|
||||
|
||||
import "errors"
|
||||
import "go.etcd.io/bbolt/errors"
|
||||
|
||||
// These errors can be returned when opening or calling methods on a DB.
|
||||
var (
|
||||
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
|
||||
// is opened or after it is closed.
|
||||
ErrDatabaseNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrDatabaseOpen is returned when opening a database that is
|
||||
// already open.
|
||||
ErrDatabaseOpen = errors.New("database already open")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrDatabaseNotOpen = errors.ErrDatabaseNotOpen
|
||||
|
||||
// ErrInvalid is returned when both meta pages on a database are invalid.
|
||||
// This typically occurs when a file is not a bolt database.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrInvalid = errors.ErrInvalid
|
||||
|
||||
// ErrInvalidMapping is returned when the database file fails to get mapped.
|
||||
ErrInvalidMapping = errors.New("database isn't correctly mapped")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrInvalidMapping = errors.ErrInvalidMapping
|
||||
|
||||
// ErrVersionMismatch is returned when the data file was created with a
|
||||
// different version of Bolt.
|
||||
ErrVersionMismatch = errors.New("version mismatch")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrVersionMismatch = errors.ErrVersionMismatch
|
||||
|
||||
// ErrChecksum is returned when either meta page checksum does not match.
|
||||
ErrChecksum = errors.New("checksum error")
|
||||
// ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrChecksum = errors.ErrChecksum
|
||||
|
||||
// ErrTimeout is returned when a database cannot obtain an exclusive lock
|
||||
// on the data file after the timeout passed to Open().
|
||||
ErrTimeout = errors.New("timeout")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTimeout = errors.ErrTimeout
|
||||
)
|
||||
|
||||
// These errors can occur when beginning or committing a Tx.
|
||||
var (
|
||||
// ErrTxNotWritable is returned when performing a write operation on a
|
||||
// read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTxNotWritable = errors.ErrTxNotWritable
|
||||
|
||||
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||
// that has already been committed or rolled back.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrTxClosed = errors.ErrTxClosed
|
||||
|
||||
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||
// read-only database.
|
||||
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrDatabaseReadOnly = errors.ErrDatabaseReadOnly
|
||||
|
||||
// ErrFreePagesNotLoaded is returned when a readonly transaction without
|
||||
// preloading the free pages is trying to access the free pages.
|
||||
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrFreePagesNotLoaded = errors.ErrFreePagesNotLoaded
|
||||
)
|
||||
|
||||
// These errors can occur when putting or deleting a value or a bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketNotFound = errors.ErrBucketNotFound
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketExists = errors.ErrBucketExists
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrBucketNameRequired = errors.ErrBucketNameRequired
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrKeyRequired = errors.ErrKeyRequired
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrKeyTooLarge = errors.ErrKeyTooLarge
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrValueTooLarge = errors.ErrValueTooLarge
|
||||
|
||||
// ErrIncompatibleValue is returned when trying create or delete a bucket
|
||||
// on an existing non-bucket key or when trying to create or delete a
|
||||
// non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
//
|
||||
// Deprecated: Use the error variables defined in the bbolt/errors package.
|
||||
ErrIncompatibleValue = errors.ErrIncompatibleValue
|
||||
)
|
||||
|
||||
84
vendor/go.etcd.io/bbolt/errors/errors.go
generated
vendored
Normal file
84
vendor/go.etcd.io/bbolt/errors/errors.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// Package errors defines the error variables that may be returned
|
||||
// during bbolt operations.
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// These errors can be returned when opening or calling methods on a DB.
|
||||
var (
|
||||
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
|
||||
// is opened or after it is closed.
|
||||
ErrDatabaseNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrInvalid is returned when both meta pages on a database are invalid.
|
||||
// This typically occurs when a file is not a bolt database.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
|
||||
// ErrInvalidMapping is returned when the database file fails to get mapped.
|
||||
ErrInvalidMapping = errors.New("database isn't correctly mapped")
|
||||
|
||||
// ErrVersionMismatch is returned when the data file was created with a
|
||||
// different version of Bolt.
|
||||
ErrVersionMismatch = errors.New("version mismatch")
|
||||
|
||||
// ErrChecksum is returned when a checksum mismatch occurs on either of the two meta pages.
|
||||
ErrChecksum = errors.New("checksum error")
|
||||
|
||||
// ErrTimeout is returned when a database cannot obtain an exclusive lock
|
||||
// on the data file after the timeout passed to Open().
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
||||
|
||||
// These errors can occur when beginning or committing a Tx.
|
||||
var (
|
||||
// ErrTxNotWritable is returned when performing a write operation on a
|
||||
// read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
|
||||
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||
// that has already been committed or rolled back.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
|
||||
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||
// read-only database.
|
||||
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||
|
||||
// ErrFreePagesNotLoaded is returned when a readonly transaction without
|
||||
// preloading the free pages is trying to access the free pages.
|
||||
ErrFreePagesNotLoaded = errors.New("free pages are not pre-loaded")
|
||||
)
|
||||
|
||||
// These errors can occur when putting or deleting a value or a bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
|
||||
// ErrIncompatibleValue is returned when trying to create or delete a bucket
|
||||
// on an existing non-bucket key or when trying to create or delete a
|
||||
// non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
|
||||
// ErrSameBuckets is returned when trying to move a sub-bucket between
|
||||
// source and target buckets, while source and target buckets are the same.
|
||||
ErrSameBuckets = errors.New("the source and target are the same bucket")
|
||||
|
||||
// ErrDifferentDB is returned when trying to move a sub-bucket between
|
||||
// source and target buckets, while source and target buckets are in different database files.
|
||||
ErrDifferentDB = errors.New("the source and target buckets are in different database files")
|
||||
)
|
||||
410
vendor/go.etcd.io/bbolt/freelist.go
generated
vendored
410
vendor/go.etcd.io/bbolt/freelist.go
generated
vendored
@@ -1,410 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// txPending holds a list of pgids and corresponding allocation txns
|
||||
// that are pending to be freed.
|
||||
type txPending struct {
|
||||
ids []pgid
|
||||
alloctx []txid // txids allocating the ids
|
||||
lastReleaseBegin txid // beginning txid of last matching releaseRange
|
||||
}
|
||||
|
||||
// pidSet holds the set of starting pgids which have the same span size
|
||||
type pidSet map[pgid]struct{}
|
||||
|
||||
// freelist represents a list of all pages that are available for allocation.
|
||||
// It also tracks pages that have been freed but are still in use by open transactions.
|
||||
type freelist struct {
|
||||
freelistType FreelistType // freelist type
|
||||
ids []pgid // all free and available free page ids.
|
||||
allocs map[pgid]txid // mapping of txid that allocated a pgid.
|
||||
pending map[txid]*txPending // mapping of soon-to-be free page ids by tx.
|
||||
cache map[pgid]struct{} // fast lookup of all free and pending page ids.
|
||||
freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
|
||||
forwardMap map[pgid]uint64 // key is start pgid, value is its span size
|
||||
backwardMap map[pgid]uint64 // key is end pgid, value is its span size
|
||||
allocate func(txid txid, n int) pgid // the freelist allocate func
|
||||
free_count func() int // the function which gives you free page number
|
||||
mergeSpans func(ids pgids) // the mergeSpan func
|
||||
getFreePageIDs func() []pgid // get free pgids func
|
||||
readIDs func(pgids []pgid) // readIDs func reads list of pages and init the freelist
|
||||
}
|
||||
|
||||
// newFreelist returns an empty, initialized freelist.
|
||||
func newFreelist(freelistType FreelistType) *freelist {
|
||||
f := &freelist{
|
||||
freelistType: freelistType,
|
||||
allocs: make(map[pgid]txid),
|
||||
pending: make(map[txid]*txPending),
|
||||
cache: make(map[pgid]struct{}),
|
||||
freemaps: make(map[uint64]pidSet),
|
||||
forwardMap: make(map[pgid]uint64),
|
||||
backwardMap: make(map[pgid]uint64),
|
||||
}
|
||||
|
||||
if freelistType == FreelistMapType {
|
||||
f.allocate = f.hashmapAllocate
|
||||
f.free_count = f.hashmapFreeCount
|
||||
f.mergeSpans = f.hashmapMergeSpans
|
||||
f.getFreePageIDs = f.hashmapGetFreePageIDs
|
||||
f.readIDs = f.hashmapReadIDs
|
||||
} else {
|
||||
f.allocate = f.arrayAllocate
|
||||
f.free_count = f.arrayFreeCount
|
||||
f.mergeSpans = f.arrayMergeSpans
|
||||
f.getFreePageIDs = f.arrayGetFreePageIDs
|
||||
f.readIDs = f.arrayReadIDs
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// size returns the size of the page after serialization.
|
||||
func (f *freelist) size() int {
|
||||
n := f.count()
|
||||
if n >= 0xFFFF {
|
||||
// The first element will be used to store the count. See freelist.write.
|
||||
n++
|
||||
}
|
||||
return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n)
|
||||
}
|
||||
|
||||
// count returns count of pages on the freelist
|
||||
func (f *freelist) count() int {
|
||||
return f.free_count() + f.pending_count()
|
||||
}
|
||||
|
||||
// arrayFreeCount returns count of free pages(array version)
|
||||
func (f *freelist) arrayFreeCount() int {
|
||||
return len(f.ids)
|
||||
}
|
||||
|
||||
// pending_count returns count of pending pages
|
||||
func (f *freelist) pending_count() int {
|
||||
var count int
|
||||
for _, txp := range f.pending {
|
||||
count += len(txp.ids)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
func (f *freelist) copyall(dst []pgid) {
|
||||
m := make(pgids, 0, f.pending_count())
|
||||
for _, txp := range f.pending {
|
||||
m = append(m, txp.ids...)
|
||||
}
|
||||
sort.Sort(m)
|
||||
mergepgids(dst, f.getFreePageIDs(), m)
|
||||
}
|
||||
|
||||
// arrayAllocate returns the starting page id of a contiguous list of pages of a given size.
|
||||
// If a contiguous block cannot be found then 0 is returned.
|
||||
func (f *freelist) arrayAllocate(txid txid, n int) pgid {
|
||||
if len(f.ids) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var initial, previd pgid
|
||||
for i, id := range f.ids {
|
||||
if id <= 1 {
|
||||
panic(fmt.Sprintf("invalid page allocation: %d", id))
|
||||
}
|
||||
|
||||
// Reset initial page if this is not contiguous.
|
||||
if previd == 0 || id-previd != 1 {
|
||||
initial = id
|
||||
}
|
||||
|
||||
// If we found a contiguous block then remove it and return it.
|
||||
if (id-initial)+1 == pgid(n) {
|
||||
// If we're allocating off the beginning then take the fast path
|
||||
// and just adjust the existing slice. This will use extra memory
|
||||
// temporarily but the append() in free() will realloc the slice
|
||||
// as is necessary.
|
||||
if (i + 1) == n {
|
||||
f.ids = f.ids[i+1:]
|
||||
} else {
|
||||
copy(f.ids[i-n+1:], f.ids[i+1:])
|
||||
f.ids = f.ids[:len(f.ids)-n]
|
||||
}
|
||||
|
||||
// Remove from the free cache.
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, initial+i)
|
||||
}
|
||||
f.allocs[initial] = txid
|
||||
return initial
|
||||
}
|
||||
|
||||
previd = id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// free releases a page and its overflow for a given transaction id.
|
||||
// If the page is already free then a panic will occur.
|
||||
func (f *freelist) free(txid txid, p *page) {
|
||||
if p.id <= 1 {
|
||||
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
|
||||
}
|
||||
|
||||
// Free page and all its overflow pages.
|
||||
txp := f.pending[txid]
|
||||
if txp == nil {
|
||||
txp = &txPending{}
|
||||
f.pending[txid] = txp
|
||||
}
|
||||
allocTxid, ok := f.allocs[p.id]
|
||||
if ok {
|
||||
delete(f.allocs, p.id)
|
||||
} else if (p.flags & freelistPageFlag) != 0 {
|
||||
// Freelist is always allocated by prior tx.
|
||||
allocTxid = txid - 1
|
||||
}
|
||||
|
||||
for id := p.id; id <= p.id+pgid(p.overflow); id++ {
|
||||
// Verify that page is not already free.
|
||||
if _, ok := f.cache[id]; ok {
|
||||
panic(fmt.Sprintf("page %d already freed", id))
|
||||
}
|
||||
// Add to the freelist and cache.
|
||||
txp.ids = append(txp.ids, id)
|
||||
txp.alloctx = append(txp.alloctx, allocTxid)
|
||||
f.cache[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||
func (f *freelist) release(txid txid) {
|
||||
m := make(pgids, 0)
|
||||
for tid, txp := range f.pending {
|
||||
if tid <= txid {
|
||||
// Move transaction's pending pages to the available freelist.
|
||||
// Don't remove from the cache since the page is still free.
|
||||
m = append(m, txp.ids...)
|
||||
delete(f.pending, tid)
|
||||
}
|
||||
}
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
|
||||
func (f *freelist) releaseRange(begin, end txid) {
|
||||
if begin > end {
|
||||
return
|
||||
}
|
||||
var m pgids
|
||||
for tid, txp := range f.pending {
|
||||
if tid < begin || tid > end {
|
||||
continue
|
||||
}
|
||||
// Don't recompute freed pages if ranges haven't updated.
|
||||
if txp.lastReleaseBegin == begin {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < len(txp.ids); i++ {
|
||||
if atx := txp.alloctx[i]; atx < begin || atx > end {
|
||||
continue
|
||||
}
|
||||
m = append(m, txp.ids[i])
|
||||
txp.ids[i] = txp.ids[len(txp.ids)-1]
|
||||
txp.ids = txp.ids[:len(txp.ids)-1]
|
||||
txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
|
||||
txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
|
||||
i--
|
||||
}
|
||||
txp.lastReleaseBegin = begin
|
||||
if len(txp.ids) == 0 {
|
||||
delete(f.pending, tid)
|
||||
}
|
||||
}
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// rollback removes the pages from a given pending tx.
|
||||
func (f *freelist) rollback(txid txid) {
|
||||
// Remove page ids from cache.
|
||||
txp := f.pending[txid]
|
||||
if txp == nil {
|
||||
return
|
||||
}
|
||||
var m pgids
|
||||
for i, pgid := range txp.ids {
|
||||
delete(f.cache, pgid)
|
||||
tx := txp.alloctx[i]
|
||||
if tx == 0 {
|
||||
continue
|
||||
}
|
||||
if tx != txid {
|
||||
// Pending free aborted; restore page back to alloc list.
|
||||
f.allocs[pgid] = tx
|
||||
} else {
|
||||
// Freed page was allocated by this txn; OK to throw away.
|
||||
m = append(m, pgid)
|
||||
}
|
||||
}
|
||||
// Remove pages from pending list and mark as free if allocated by txid.
|
||||
delete(f.pending, txid)
|
||||
|
||||
// Remove pgids which are allocated by this txid
|
||||
for pgid, tid := range f.allocs {
|
||||
if tid == txid {
|
||||
delete(f.allocs, pgid)
|
||||
}
|
||||
}
|
||||
|
||||
f.mergeSpans(m)
|
||||
}
|
||||
|
||||
// freed returns whether a given page is in the free list.
|
||||
func (f *freelist) freed(pgId pgid) bool {
|
||||
_, ok := f.cache[pgId]
|
||||
return ok
|
||||
}
|
||||
|
||||
// read initializes the freelist from a freelist page.
|
||||
func (f *freelist) read(p *page) {
|
||||
if (p.flags & freelistPageFlag) == 0 {
|
||||
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ()))
|
||||
}
|
||||
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||
// an overflow and the size of the freelist is stored as the first element.
|
||||
var idx, count = 0, int(p.count)
|
||||
if count == 0xFFFF {
|
||||
idx = 1
|
||||
c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
count = int(c)
|
||||
if count < 0 {
|
||||
panic(fmt.Sprintf("leading element count %d overflows int", c))
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the list of page ids from the freelist.
|
||||
if count == 0 {
|
||||
f.ids = nil
|
||||
} else {
|
||||
data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(pgid(0)), idx)
|
||||
ids := unsafe.Slice((*pgid)(data), count)
|
||||
|
||||
// copy the ids, so we don't modify on the freelist page directly
|
||||
idsCopy := make([]pgid, count)
|
||||
copy(idsCopy, ids)
|
||||
// Make sure they're sorted.
|
||||
sort.Sort(pgids(idsCopy))
|
||||
|
||||
f.readIDs(idsCopy)
|
||||
}
|
||||
}
|
||||
|
||||
// arrayReadIDs initializes the freelist from a given list of ids.
|
||||
func (f *freelist) arrayReadIDs(ids []pgid) {
|
||||
f.ids = ids
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *freelist) arrayGetFreePageIDs() []pgid {
|
||||
return f.ids
|
||||
}
|
||||
|
||||
// write writes the page ids onto a freelist page. All free and pending ids are
|
||||
// saved to disk since in the event of a program crash, all pending ids will
|
||||
// become free.
|
||||
func (f *freelist) write(p *page) error {
|
||||
// Combine the old free pgids and pgids waiting on an open transaction.
|
||||
|
||||
// Update the header flag.
|
||||
p.flags |= freelistPageFlag
|
||||
|
||||
// The page.count can only hold up to 64k elements so if we overflow that
|
||||
// number then we handle it by putting the size in the first element.
|
||||
l := f.count()
|
||||
if l == 0 {
|
||||
p.count = uint16(l)
|
||||
} else if l < 0xFFFF {
|
||||
p.count = uint16(l)
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*pgid)(data), l)
|
||||
f.copyall(ids)
|
||||
} else {
|
||||
p.count = 0xFFFF
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*pgid)(data), l+1)
|
||||
ids[0] = pgid(l)
|
||||
f.copyall(ids[1:])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reload reads the freelist from a page and filters out pending items.
|
||||
func (f *freelist) reload(p *page) {
|
||||
f.read(p)
|
||||
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[pgid]bool)
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
var a []pgid
|
||||
for _, id := range f.getFreePageIDs() {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
f.readIDs(a)
|
||||
}
|
||||
|
||||
// noSyncReload reads the freelist from pgids and filters out pending items.
|
||||
func (f *freelist) noSyncReload(pgids []pgid) {
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[pgid]bool)
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
var a []pgid
|
||||
for _, id := range pgids {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
f.readIDs(a)
|
||||
}
|
||||
|
||||
// reindex rebuilds the free cache based on available and pending free lists.
|
||||
func (f *freelist) reindex() {
|
||||
ids := f.getFreePageIDs()
|
||||
f.cache = make(map[pgid]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
f.cache[id] = struct{}{}
|
||||
}
|
||||
for _, txp := range f.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
f.cache[pendingID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array
|
||||
func (f *freelist) arrayMergeSpans(ids pgids) {
|
||||
sort.Sort(ids)
|
||||
f.ids = pgids(f.ids).merge(ids)
|
||||
}
|
||||
178
vendor/go.etcd.io/bbolt/freelist_hmap.go
generated
vendored
178
vendor/go.etcd.io/bbolt/freelist_hmap.go
generated
vendored
@@ -1,178 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import "sort"
|
||||
|
||||
// hashmapFreeCount returns count of free pages(hashmap version)
|
||||
func (f *freelist) hashmapFreeCount() int {
|
||||
// use the forwardMap to get the total count
|
||||
count := 0
|
||||
for _, size := range f.forwardMap {
|
||||
count += int(size)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// hashmapAllocate serves the same purpose as arrayAllocate, but use hashmap as backend
|
||||
func (f *freelist) hashmapAllocate(txid txid, n int) pgid {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if we have a exact size match just return short path
|
||||
if bm, ok := f.freemaps[uint64(n)]; ok {
|
||||
for pid := range bm {
|
||||
// remove the span
|
||||
f.delSpan(pid, uint64(n))
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
// lookup the map to find larger span
|
||||
for size, bm := range f.freemaps {
|
||||
if size < uint64(n) {
|
||||
continue
|
||||
}
|
||||
|
||||
for pid := range bm {
|
||||
// remove the initial
|
||||
f.delSpan(pid, size)
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
remain := size - uint64(n)
|
||||
|
||||
// add remain span
|
||||
f.addSpan(pid+pgid(n), remain)
|
||||
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// hashmapReadIDs reads pgids as input an initial the freelist(hashmap version)
|
||||
func (f *freelist) hashmapReadIDs(pgids []pgid) {
|
||||
f.init(pgids)
|
||||
|
||||
// Rebuild the page cache.
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
// hashmapGetFreePageIDs returns the sorted free page ids
|
||||
func (f *freelist) hashmapGetFreePageIDs() []pgid {
|
||||
count := f.free_count()
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := make([]pgid, 0, count)
|
||||
for start, size := range f.forwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
m = append(m, start+pgid(i))
|
||||
}
|
||||
}
|
||||
sort.Sort(pgids(m))
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// hashmapMergeSpans try to merge list of pages(represented by pgids) with existing spans
|
||||
func (f *freelist) hashmapMergeSpans(ids pgids) {
|
||||
for _, id := range ids {
|
||||
// try to see if we can merge and update
|
||||
f.mergeWithExistingSpan(id)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward
|
||||
func (f *freelist) mergeWithExistingSpan(pid pgid) {
|
||||
prev := pid - 1
|
||||
next := pid + 1
|
||||
|
||||
preSize, mergeWithPrev := f.backwardMap[prev]
|
||||
nextSize, mergeWithNext := f.forwardMap[next]
|
||||
newStart := pid
|
||||
newSize := uint64(1)
|
||||
|
||||
if mergeWithPrev {
|
||||
//merge with previous span
|
||||
start := prev + 1 - pgid(preSize)
|
||||
f.delSpan(start, preSize)
|
||||
|
||||
newStart -= pgid(preSize)
|
||||
newSize += preSize
|
||||
}
|
||||
|
||||
if mergeWithNext {
|
||||
// merge with next span
|
||||
f.delSpan(next, nextSize)
|
||||
newSize += nextSize
|
||||
}
|
||||
|
||||
f.addSpan(newStart, newSize)
|
||||
}
|
||||
|
||||
func (f *freelist) addSpan(start pgid, size uint64) {
|
||||
f.backwardMap[start-1+pgid(size)] = size
|
||||
f.forwardMap[start] = size
|
||||
if _, ok := f.freemaps[size]; !ok {
|
||||
f.freemaps[size] = make(map[pgid]struct{})
|
||||
}
|
||||
|
||||
f.freemaps[size][start] = struct{}{}
|
||||
}
|
||||
|
||||
func (f *freelist) delSpan(start pgid, size uint64) {
|
||||
delete(f.forwardMap, start)
|
||||
delete(f.backwardMap, start+pgid(size-1))
|
||||
delete(f.freemaps[size], start)
|
||||
if len(f.freemaps[size]) == 0 {
|
||||
delete(f.freemaps, size)
|
||||
}
|
||||
}
|
||||
|
||||
// initial from pgids using when use hashmap version
|
||||
// pgids must be sorted
|
||||
func (f *freelist) init(pgids []pgid) {
|
||||
if len(pgids) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
size := uint64(1)
|
||||
start := pgids[0]
|
||||
|
||||
if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) {
|
||||
panic("pgids not sorted")
|
||||
}
|
||||
|
||||
f.freemaps = make(map[uint64]pidSet)
|
||||
f.forwardMap = make(map[pgid]uint64)
|
||||
f.backwardMap = make(map[pgid]uint64)
|
||||
|
||||
for i := 1; i < len(pgids); i++ {
|
||||
// continuous page
|
||||
if pgids[i] == pgids[i-1]+1 {
|
||||
size++
|
||||
} else {
|
||||
f.addSpan(start, size)
|
||||
|
||||
size = 1
|
||||
start = pgids[i]
|
||||
}
|
||||
}
|
||||
|
||||
// init the tail
|
||||
if size != 0 && start != 0 {
|
||||
f.addSpan(start, size)
|
||||
}
|
||||
}
|
||||
7
vendor/go.etcd.io/bbolt/internal/common/bolt_386.go
generated
vendored
Normal file
7
vendor/go.etcd.io/bbolt/internal/common/bolt_386.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0xFFFFFFF
|
||||
7
vendor/go.etcd.io/bbolt/internal/common/bolt_amd64.go
generated
vendored
Normal file
7
vendor/go.etcd.io/bbolt/internal/common/bolt_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
7
vendor/go.etcd.io/bbolt/internal/common/bolt_arm.go
generated
vendored
Normal file
7
vendor/go.etcd.io/bbolt/internal/common/bolt_arm.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0xFFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_arm64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_arm64.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build arm64
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_loong64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_loong64.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build loong64
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mips64x.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mips64x.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build mips64 || mips64le
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0x8000000000 // 512GB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mipsx.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_mipsx.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build mips || mipsle
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0x40000000 // 1GB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0xFFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build ppc
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0xFFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build ppc64
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64le.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_ppc64le.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build ppc64le
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_riscv64.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_riscv64.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build riscv64
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
9
vendor/go.etcd.io/bbolt/internal/common/bolt_s390x.go
generated
vendored
Normal file
9
vendor/go.etcd.io/bbolt/internal/common/bolt_s390x.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build s390x
|
||||
|
||||
package common
|
||||
|
||||
// MaxMapSize represents the largest mmap size supported by Bolt.
|
||||
const MaxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// MaxAllocSize is the size used when creating array pointers.
|
||||
const MaxAllocSize = 0x7FFFFFFF
|
||||
54
vendor/go.etcd.io/bbolt/internal/common/bucket.go
generated
vendored
Normal file
54
vendor/go.etcd.io/bbolt/internal/common/bucket.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const BucketHeaderSize = int(unsafe.Sizeof(InBucket{}))
|
||||
|
||||
// InBucket represents the on-file representation of a bucket.
|
||||
// This is stored as the "value" of a bucket key. If the bucket is small enough,
|
||||
// then its root page can be stored inline in the "value", after the bucket
|
||||
// header. In the case of inline buckets, the "root" will be 0.
|
||||
type InBucket struct {
|
||||
root Pgid // page id of the bucket's root-level page
|
||||
sequence uint64 // monotonically incrementing, used by NextSequence()
|
||||
}
|
||||
|
||||
func NewInBucket(root Pgid, seq uint64) InBucket {
|
||||
return InBucket{
|
||||
root: root,
|
||||
sequence: seq,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *InBucket) RootPage() Pgid {
|
||||
return b.root
|
||||
}
|
||||
|
||||
func (b *InBucket) SetRootPage(id Pgid) {
|
||||
b.root = id
|
||||
}
|
||||
|
||||
// InSequence returns the sequence. The reason why not naming it `Sequence`
|
||||
// is to avoid duplicated name as `(*Bucket) Sequence()`
|
||||
func (b *InBucket) InSequence() uint64 {
|
||||
return b.sequence
|
||||
}
|
||||
|
||||
func (b *InBucket) SetInSequence(v uint64) {
|
||||
b.sequence = v
|
||||
}
|
||||
|
||||
func (b *InBucket) IncSequence() {
|
||||
b.sequence++
|
||||
}
|
||||
|
||||
func (b *InBucket) InlinePage(v []byte) *Page {
|
||||
return (*Page)(unsafe.Pointer(&v[BucketHeaderSize]))
|
||||
}
|
||||
|
||||
func (b *InBucket) String() string {
|
||||
return fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
|
||||
}
|
||||
115
vendor/go.etcd.io/bbolt/internal/common/inode.go
generated
vendored
Normal file
115
vendor/go.etcd.io/bbolt/internal/common/inode.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package common
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Inode represents an internal node inside of a node.
|
||||
// It can be used to point to elements in a page or point
|
||||
// to an element which hasn't been added to a page yet.
|
||||
type Inode struct {
|
||||
flags uint32
|
||||
pgid Pgid
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
type Inodes []Inode
|
||||
|
||||
func (in *Inode) Flags() uint32 {
|
||||
return in.flags
|
||||
}
|
||||
|
||||
func (in *Inode) SetFlags(flags uint32) {
|
||||
in.flags = flags
|
||||
}
|
||||
|
||||
func (in *Inode) Pgid() Pgid {
|
||||
return in.pgid
|
||||
}
|
||||
|
||||
func (in *Inode) SetPgid(id Pgid) {
|
||||
in.pgid = id
|
||||
}
|
||||
|
||||
func (in *Inode) Key() []byte {
|
||||
return in.key
|
||||
}
|
||||
|
||||
func (in *Inode) SetKey(key []byte) {
|
||||
in.key = key
|
||||
}
|
||||
|
||||
func (in *Inode) Value() []byte {
|
||||
return in.value
|
||||
}
|
||||
|
||||
func (in *Inode) SetValue(value []byte) {
|
||||
in.value = value
|
||||
}
|
||||
|
||||
func ReadInodeFromPage(p *Page) Inodes {
|
||||
inodes := make(Inodes, int(p.Count()))
|
||||
isLeaf := p.IsLeafPage()
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
inode := &inodes[i]
|
||||
if isLeaf {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
inode.SetFlags(elem.Flags())
|
||||
inode.SetKey(elem.Key())
|
||||
inode.SetValue(elem.Value())
|
||||
} else {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
inode.SetPgid(elem.Pgid())
|
||||
inode.SetKey(elem.Key())
|
||||
}
|
||||
Assert(len(inode.Key()) > 0, "read: zero-length inode key")
|
||||
}
|
||||
|
||||
return inodes
|
||||
}
|
||||
|
||||
func WriteInodeToPage(inodes Inodes, p *Page) uint32 {
|
||||
// Loop over each item and write it to the page.
|
||||
// off tracks the offset into p of the start of the next data.
|
||||
off := unsafe.Sizeof(*p) + p.PageElementSize()*uintptr(len(inodes))
|
||||
isLeaf := p.IsLeafPage()
|
||||
for i, item := range inodes {
|
||||
Assert(len(item.Key()) > 0, "write: zero-length inode key")
|
||||
|
||||
// Create a slice to write into of needed size and advance
|
||||
// byte pointer for next iteration.
|
||||
sz := len(item.Key()) + len(item.Value())
|
||||
b := UnsafeByteSlice(unsafe.Pointer(p), off, 0, sz)
|
||||
off += uintptr(sz)
|
||||
|
||||
// Write the page element.
|
||||
if isLeaf {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
elem.SetPos(uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))))
|
||||
elem.SetFlags(item.Flags())
|
||||
elem.SetKsize(uint32(len(item.Key())))
|
||||
elem.SetVsize(uint32(len(item.Value())))
|
||||
} else {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
elem.SetPos(uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem))))
|
||||
elem.SetKsize(uint32(len(item.Key())))
|
||||
elem.SetPgid(item.Pgid())
|
||||
Assert(elem.Pgid() != p.Id(), "write: circular dependency occurred")
|
||||
}
|
||||
|
||||
// Write data for the element to the end of the page.
|
||||
l := copy(b, item.Key())
|
||||
copy(b[l:], item.Value())
|
||||
}
|
||||
|
||||
return uint32(off)
|
||||
}
|
||||
|
||||
func UsedSpaceInPage(inodes Inodes, p *Page) uint32 {
|
||||
off := unsafe.Sizeof(*p) + p.PageElementSize()*uintptr(len(inodes))
|
||||
for _, item := range inodes {
|
||||
sz := len(item.Key()) + len(item.Value())
|
||||
off += uintptr(sz)
|
||||
}
|
||||
|
||||
return uint32(off)
|
||||
}
|
||||
161
vendor/go.etcd.io/bbolt/internal/common/meta.go
generated
vendored
Normal file
161
vendor/go.etcd.io/bbolt/internal/common/meta.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/errors"
|
||||
)
|
||||
|
||||
type Meta struct {
|
||||
magic uint32
|
||||
version uint32
|
||||
pageSize uint32
|
||||
flags uint32
|
||||
root InBucket
|
||||
freelist Pgid
|
||||
pgid Pgid
|
||||
txid Txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// Validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *Meta) Validate() error {
|
||||
if m.magic != Magic {
|
||||
return errors.ErrInvalid
|
||||
} else if m.version != Version {
|
||||
return errors.ErrVersionMismatch
|
||||
} else if m.checksum != m.Sum64() {
|
||||
return errors.ErrChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy copies one meta object to another.
|
||||
func (m *Meta) Copy(dest *Meta) {
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// Write writes the meta onto a page.
|
||||
func (m *Meta) Write(p *Page) {
|
||||
if m.root.root >= m.pgid {
|
||||
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||
} else if m.freelist >= m.pgid && m.freelist != PgidNoFreelist {
|
||||
// TODO: reject pgidNoFreeList if !NoFreelistSync
|
||||
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||
}
|
||||
|
||||
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||
p.id = Pgid(m.txid % 2)
|
||||
p.SetFlags(MetaPageFlag)
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.Sum64()
|
||||
|
||||
m.Copy(p.Meta())
|
||||
}
|
||||
|
||||
// Sum64 generates the checksum for the meta.
|
||||
func (m *Meta) Sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(Meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func (m *Meta) Magic() uint32 {
|
||||
return m.magic
|
||||
}
|
||||
|
||||
func (m *Meta) SetMagic(v uint32) {
|
||||
m.magic = v
|
||||
}
|
||||
|
||||
func (m *Meta) Version() uint32 {
|
||||
return m.version
|
||||
}
|
||||
|
||||
func (m *Meta) SetVersion(v uint32) {
|
||||
m.version = v
|
||||
}
|
||||
|
||||
func (m *Meta) PageSize() uint32 {
|
||||
return m.pageSize
|
||||
}
|
||||
|
||||
func (m *Meta) SetPageSize(v uint32) {
|
||||
m.pageSize = v
|
||||
}
|
||||
|
||||
func (m *Meta) Flags() uint32 {
|
||||
return m.flags
|
||||
}
|
||||
|
||||
func (m *Meta) SetFlags(v uint32) {
|
||||
m.flags = v
|
||||
}
|
||||
|
||||
func (m *Meta) SetRootBucket(b InBucket) {
|
||||
m.root = b
|
||||
}
|
||||
|
||||
func (m *Meta) RootBucket() *InBucket {
|
||||
return &m.root
|
||||
}
|
||||
|
||||
func (m *Meta) Freelist() Pgid {
|
||||
return m.freelist
|
||||
}
|
||||
|
||||
func (m *Meta) SetFreelist(v Pgid) {
|
||||
m.freelist = v
|
||||
}
|
||||
|
||||
func (m *Meta) IsFreelistPersisted() bool {
|
||||
return m.freelist != PgidNoFreelist
|
||||
}
|
||||
|
||||
func (m *Meta) Pgid() Pgid {
|
||||
return m.pgid
|
||||
}
|
||||
|
||||
func (m *Meta) SetPgid(id Pgid) {
|
||||
m.pgid = id
|
||||
}
|
||||
|
||||
func (m *Meta) Txid() Txid {
|
||||
return m.txid
|
||||
}
|
||||
|
||||
func (m *Meta) SetTxid(id Txid) {
|
||||
m.txid = id
|
||||
}
|
||||
|
||||
func (m *Meta) IncTxid() {
|
||||
m.txid += 1
|
||||
}
|
||||
|
||||
func (m *Meta) DecTxid() {
|
||||
m.txid -= 1
|
||||
}
|
||||
|
||||
func (m *Meta) Checksum() uint64 {
|
||||
return m.checksum
|
||||
}
|
||||
|
||||
func (m *Meta) SetChecksum(v uint64) {
|
||||
m.checksum = v
|
||||
}
|
||||
|
||||
func (m *Meta) Print(w io.Writer) {
|
||||
fmt.Fprintf(w, "Version: %d\n", m.version)
|
||||
fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize)
|
||||
fmt.Fprintf(w, "Flags: %08x\n", m.flags)
|
||||
fmt.Fprintf(w, "Root: <pgid=%d>\n", m.root.root)
|
||||
fmt.Fprintf(w, "Freelist: <pgid=%d>\n", m.freelist)
|
||||
fmt.Fprintf(w, "HWM: <pgid=%d>\n", m.pgid)
|
||||
fmt.Fprintf(w, "Txn ID: %d\n", m.txid)
|
||||
fmt.Fprintf(w, "Checksum: %016x\n", m.checksum)
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
391
vendor/go.etcd.io/bbolt/internal/common/page.go
generated
vendored
Normal file
391
vendor/go.etcd.io/bbolt/internal/common/page.go
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const PageHeaderSize = unsafe.Sizeof(Page{})
|
||||
|
||||
const MinKeysPerPage = 2
|
||||
|
||||
const BranchPageElementSize = unsafe.Sizeof(branchPageElement{})
|
||||
const LeafPageElementSize = unsafe.Sizeof(leafPageElement{})
|
||||
const pgidSize = unsafe.Sizeof(Pgid(0))
|
||||
|
||||
const (
|
||||
BranchPageFlag = 0x01
|
||||
LeafPageFlag = 0x02
|
||||
MetaPageFlag = 0x04
|
||||
FreelistPageFlag = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
BucketLeafFlag = 0x01
|
||||
)
|
||||
|
||||
type Pgid uint64
|
||||
|
||||
type Page struct {
|
||||
id Pgid
|
||||
flags uint16
|
||||
count uint16
|
||||
overflow uint32
|
||||
}
|
||||
|
||||
func NewPage(id Pgid, flags, count uint16, overflow uint32) *Page {
|
||||
return &Page{
|
||||
id: id,
|
||||
flags: flags,
|
||||
count: count,
|
||||
overflow: overflow,
|
||||
}
|
||||
}
|
||||
|
||||
// Typ returns a human-readable page type string used for debugging.
|
||||
func (p *Page) Typ() string {
|
||||
if p.IsBranchPage() {
|
||||
return "branch"
|
||||
} else if p.IsLeafPage() {
|
||||
return "leaf"
|
||||
} else if p.IsMetaPage() {
|
||||
return "meta"
|
||||
} else if p.IsFreelistPage() {
|
||||
return "freelist"
|
||||
}
|
||||
return fmt.Sprintf("unknown<%02x>", p.flags)
|
||||
}
|
||||
|
||||
func (p *Page) IsBranchPage() bool {
|
||||
return p.flags == BranchPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsLeafPage() bool {
|
||||
return p.flags == LeafPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsMetaPage() bool {
|
||||
return p.flags == MetaPageFlag
|
||||
}
|
||||
|
||||
func (p *Page) IsFreelistPage() bool {
|
||||
return p.flags == FreelistPageFlag
|
||||
}
|
||||
|
||||
// Meta returns a pointer to the metadata section of the page.
|
||||
func (p *Page) Meta() *Meta {
|
||||
return (*Meta)(UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
}
|
||||
|
||||
func (p *Page) FastCheck(id Pgid) {
|
||||
Assert(p.id == id, "Page expected to be: %v, but self identifies as %v", id, p.id)
|
||||
// Only one flag of page-type can be set.
|
||||
Assert(p.IsBranchPage() ||
|
||||
p.IsLeafPage() ||
|
||||
p.IsMetaPage() ||
|
||||
p.IsFreelistPage(),
|
||||
"page %v: has unexpected type/flags: %x", p.id, p.flags)
|
||||
}
|
||||
|
||||
// LeafPageElement retrieves the leaf node by index
|
||||
func (p *Page) LeafPageElement(index uint16) *leafPageElement {
|
||||
return (*leafPageElement)(UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
LeafPageElementSize, int(index)))
|
||||
}
|
||||
|
||||
// LeafPageElements retrieves a list of leaf nodes.
|
||||
func (p *Page) LeafPageElements() []leafPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*leafPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// BranchPageElement retrieves the branch node by index
|
||||
func (p *Page) BranchPageElement(index uint16) *branchPageElement {
|
||||
return (*branchPageElement)(UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
unsafe.Sizeof(branchPageElement{}), int(index)))
|
||||
}
|
||||
|
||||
// BranchPageElements retrieves a list of branch nodes.
|
||||
func (p *Page) BranchPageElements() []branchPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*branchPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
func (p *Page) FreelistPageCount() (int, int) {
|
||||
Assert(p.IsFreelistPage(), fmt.Sprintf("can't get freelist page count from a non-freelist page: %2x", p.flags))
|
||||
|
||||
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||
// an overflow and the size of the freelist is stored as the first element.
|
||||
var idx, count = 0, int(p.count)
|
||||
if count == 0xFFFF {
|
||||
idx = 1
|
||||
c := *(*Pgid)(UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
count = int(c)
|
||||
if count < 0 {
|
||||
panic(fmt.Sprintf("leading element count %d overflows int", c))
|
||||
}
|
||||
}
|
||||
|
||||
return idx, count
|
||||
}
|
||||
|
||||
func (p *Page) FreelistPageIds() []Pgid {
|
||||
Assert(p.IsFreelistPage(), fmt.Sprintf("can't get freelist page IDs from a non-freelist page: %2x", p.flags))
|
||||
|
||||
idx, count := p.FreelistPageCount()
|
||||
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := UnsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), pgidSize, idx)
|
||||
ids := unsafe.Slice((*Pgid)(data), count)
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
// dump writes n bytes of the page to STDERR as hex output.
|
||||
func (p *Page) hexdump(n int) {
|
||||
buf := UnsafeByteSlice(unsafe.Pointer(p), 0, 0, n)
|
||||
fmt.Fprintf(os.Stderr, "%x\n", buf)
|
||||
}
|
||||
|
||||
func (p *Page) PageElementSize() uintptr {
|
||||
if p.IsLeafPage() {
|
||||
return LeafPageElementSize
|
||||
}
|
||||
return BranchPageElementSize
|
||||
}
|
||||
|
||||
func (p *Page) Id() Pgid {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *Page) SetId(target Pgid) {
|
||||
p.id = target
|
||||
}
|
||||
|
||||
func (p *Page) Flags() uint16 {
|
||||
return p.flags
|
||||
}
|
||||
|
||||
func (p *Page) SetFlags(v uint16) {
|
||||
p.flags = v
|
||||
}
|
||||
|
||||
func (p *Page) Count() uint16 {
|
||||
return p.count
|
||||
}
|
||||
|
||||
func (p *Page) SetCount(target uint16) {
|
||||
p.count = target
|
||||
}
|
||||
|
||||
func (p *Page) Overflow() uint32 {
|
||||
return p.overflow
|
||||
}
|
||||
|
||||
func (p *Page) SetOverflow(target uint32) {
|
||||
p.overflow = target
|
||||
}
|
||||
|
||||
func (p *Page) String() string {
|
||||
return fmt.Sprintf("ID: %d, Type: %s, count: %d, overflow: %d", p.id, p.Typ(), p.count, p.overflow)
|
||||
}
|
||||
|
||||
type Pages []*Page
|
||||
|
||||
func (s Pages) Len() int { return len(s) }
|
||||
func (s Pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s Pages) Less(i, j int) bool { return s[i].id < s[j].id }
|
||||
|
||||
// branchPageElement represents a node on a branch page.
|
||||
type branchPageElement struct {
|
||||
pos uint32
|
||||
ksize uint32
|
||||
pgid Pgid
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Pos() uint32 {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetPos(v uint32) {
|
||||
n.pos = v
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Ksize() uint32 {
|
||||
return n.ksize
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetKsize(v uint32) {
|
||||
n.ksize = v
|
||||
}
|
||||
|
||||
func (n *branchPageElement) Pgid() Pgid {
|
||||
return n.pgid
|
||||
}
|
||||
|
||||
func (n *branchPageElement) SetPgid(v Pgid) {
|
||||
n.pgid = v
|
||||
}
|
||||
|
||||
// Key returns a byte slice of the node key.
|
||||
func (n *branchPageElement) Key() []byte {
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize))
|
||||
}
|
||||
|
||||
// leafPageElement represents a node on a leaf page.
|
||||
type leafPageElement struct {
|
||||
flags uint32
|
||||
pos uint32
|
||||
ksize uint32
|
||||
vsize uint32
|
||||
}
|
||||
|
||||
func NewLeafPageElement(flags, pos, ksize, vsize uint32) *leafPageElement {
|
||||
return &leafPageElement{
|
||||
flags: flags,
|
||||
pos: pos,
|
||||
ksize: ksize,
|
||||
vsize: vsize,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Flags() uint32 {
|
||||
return n.flags
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetFlags(v uint32) {
|
||||
n.flags = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Pos() uint32 {
|
||||
return n.pos
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetPos(v uint32) {
|
||||
n.pos = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Ksize() uint32 {
|
||||
return n.ksize
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetKsize(v uint32) {
|
||||
n.ksize = v
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Vsize() uint32 {
|
||||
return n.vsize
|
||||
}
|
||||
|
||||
func (n *leafPageElement) SetVsize(v uint32) {
|
||||
n.vsize = v
|
||||
}
|
||||
|
||||
// Key returns a byte slice of the node key.
|
||||
func (n *leafPageElement) Key() []byte {
|
||||
i := int(n.pos)
|
||||
j := i + int(n.ksize)
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// Value returns a byte slice of the node value.
|
||||
func (n *leafPageElement) Value() []byte {
|
||||
i := int(n.pos) + int(n.ksize)
|
||||
j := i + int(n.vsize)
|
||||
return UnsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
func (n *leafPageElement) IsBucketEntry() bool {
|
||||
return n.flags&uint32(BucketLeafFlag) != 0
|
||||
}
|
||||
|
||||
func (n *leafPageElement) Bucket() *InBucket {
|
||||
if n.IsBucketEntry() {
|
||||
return LoadBucket(n.Value())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// PageInfo represents human readable information about a page.
|
||||
type PageInfo struct {
|
||||
ID int
|
||||
Type string
|
||||
Count int
|
||||
OverflowCount int
|
||||
}
|
||||
|
||||
type Pgids []Pgid
|
||||
|
||||
func (s Pgids) Len() int { return len(s) }
|
||||
func (s Pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s Pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
|
||||
// Merge returns the sorted union of a and b.
|
||||
func (a Pgids) Merge(b Pgids) Pgids {
|
||||
// Return the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
merged := make(Pgids, len(a)+len(b))
|
||||
Mergepgids(merged, a, b)
|
||||
return merged
|
||||
}
|
||||
|
||||
// Mergepgids copies the sorted union of a and b into dst.
|
||||
// If dst is too small, it panics.
|
||||
func Mergepgids(dst, a, b Pgids) {
|
||||
if len(dst) < len(a)+len(b) {
|
||||
panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
|
||||
}
|
||||
// Copy in the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
copy(dst, b)
|
||||
return
|
||||
}
|
||||
if len(b) == 0 {
|
||||
copy(dst, a)
|
||||
return
|
||||
}
|
||||
|
||||
// Merged will hold all elements from both lists.
|
||||
merged := dst[:0]
|
||||
|
||||
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||
lead, follow := a, b
|
||||
if b[0] < a[0] {
|
||||
lead, follow = b, a
|
||||
}
|
||||
|
||||
// Continue while there are elements in the lead.
|
||||
for len(lead) > 0 {
|
||||
// Merge largest prefix of lead that is ahead of follow[0].
|
||||
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||
merged = append(merged, lead[:n]...)
|
||||
if n >= len(lead) {
|
||||
break
|
||||
}
|
||||
|
||||
// Swap lead and follow.
|
||||
lead, follow = follow, lead[n:]
|
||||
}
|
||||
|
||||
// Append what's left in follow.
|
||||
_ = append(merged, follow...)
|
||||
}
|
||||
37
vendor/go.etcd.io/bbolt/internal/common/types.go
generated
vendored
Normal file
37
vendor/go.etcd.io/bbolt/internal/common/types.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MaxMmapStep is the largest step that can be taken when remapping the mmap.
|
||||
const MaxMmapStep = 1 << 30 // 1GB
|
||||
|
||||
// Version represents the data file format version.
|
||||
const Version uint32 = 2
|
||||
|
||||
// Magic represents a marker value to indicate that a file is a Bolt DB.
|
||||
const Magic uint32 = 0xED0CDAED
|
||||
|
||||
const PgidNoFreelist Pgid = 0xffffffffffffffff
|
||||
|
||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||
// syncing changes to a file. This is required as some operating systems,
|
||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||
// must be synchronized using the msync(2) syscall.
|
||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||
|
||||
// Default values if not set in a DB instance.
|
||||
const (
|
||||
DefaultMaxBatchSize int = 1000
|
||||
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||
DefaultAllocSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
// DefaultPageSize is the default page size for db which is set to the OS page size.
|
||||
var DefaultPageSize = os.Getpagesize()
|
||||
|
||||
// Txid represents the internal transaction identifier.
|
||||
type Txid uint64
|
||||
10
vendor/go.etcd.io/bbolt/unsafe.go → vendor/go.etcd.io/bbolt/internal/common/unsafe.go
generated
vendored
10
vendor/go.etcd.io/bbolt/unsafe.go → vendor/go.etcd.io/bbolt/internal/common/unsafe.go
generated
vendored
@@ -1,18 +1,18 @@
|
||||
package bbolt
|
||||
package common
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func unsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer {
|
||||
func UnsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(base) + offset)
|
||||
}
|
||||
|
||||
func unsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer {
|
||||
func UnsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(base) + offset + uintptr(n)*elemsz)
|
||||
}
|
||||
|
||||
func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
func UnsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
//
|
||||
// This memory is not allocated from C, but it is unmanaged by Go's
|
||||
@@ -23,5 +23,5 @@ func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
|
||||
// index 0. However, the wiki never says that the address must be to
|
||||
// the beginning of a C allocation (or even that malloc was used at
|
||||
// all), so this is believed to be correct.
|
||||
return (*[maxAllocSize]byte)(unsafeAdd(base, offset))[i:j:j]
|
||||
return (*[MaxAllocSize]byte)(UnsafeAdd(base, offset))[i:j:j]
|
||||
}
|
||||
64
vendor/go.etcd.io/bbolt/internal/common/utils.go
generated
vendored
Normal file
64
vendor/go.etcd.io/bbolt/internal/common/utils.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func LoadBucket(buf []byte) *InBucket {
|
||||
return (*InBucket)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func LoadPage(buf []byte) *Page {
|
||||
return (*Page)(unsafe.Pointer(&buf[0]))
|
||||
}
|
||||
|
||||
func LoadPageMeta(buf []byte) *Meta {
|
||||
return (*Meta)(unsafe.Pointer(&buf[PageHeaderSize]))
|
||||
}
|
||||
|
||||
func CopyFile(srcPath, dstPath string) error {
|
||||
// Ensure source file exists.
|
||||
_, err := os.Stat(srcPath)
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("source file %q not found", srcPath)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure output file not exist.
|
||||
_, err = os.Stat(dstPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("output file %q already exists", dstPath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
srcDB, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file %q: %w", srcPath, err)
|
||||
}
|
||||
defer srcDB.Close()
|
||||
dstDB, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file %q: %w", dstPath, err)
|
||||
}
|
||||
defer dstDB.Close()
|
||||
written, err := io.Copy(dstDB, srcDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy database file from %q to %q: %w", srcPath, dstPath, err)
|
||||
}
|
||||
|
||||
srcFi, err := srcDB.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get source file info %q: %w", srcPath, err)
|
||||
}
|
||||
initialSize := srcFi.Size()
|
||||
if initialSize != written {
|
||||
return fmt.Errorf("the byte copied (%q: %d) isn't equal to the initial db size (%q: %d)", dstPath, written, srcPath, initialSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
67
vendor/go.etcd.io/bbolt/internal/common/verify.go
generated
vendored
Normal file
67
vendor/go.etcd.io/bbolt/internal/common/verify.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copied from https://github.com/etcd-io/etcd/blob/main/client/pkg/verify/verify.go
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ENV_VERIFY = "BBOLT_VERIFY"
|
||||
|
||||
type VerificationType string
|
||||
|
||||
const (
|
||||
ENV_VERIFY_VALUE_ALL VerificationType = "all"
|
||||
ENV_VERIFY_VALUE_ASSERT VerificationType = "assert"
|
||||
)
|
||||
|
||||
func getEnvVerify() string {
|
||||
return strings.ToLower(os.Getenv(ENV_VERIFY))
|
||||
}
|
||||
|
||||
func IsVerificationEnabled(verification VerificationType) bool {
|
||||
env := getEnvVerify()
|
||||
return env == string(ENV_VERIFY_VALUE_ALL) || env == strings.ToLower(string(verification))
|
||||
}
|
||||
|
||||
// EnableVerifications sets `ENV_VERIFY` and returns a function that
|
||||
// can be used to bring the original settings.
|
||||
func EnableVerifications(verification VerificationType) func() {
|
||||
previousEnv := getEnvVerify()
|
||||
os.Setenv(ENV_VERIFY, string(verification))
|
||||
return func() {
|
||||
os.Setenv(ENV_VERIFY, previousEnv)
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAllVerifications enables verification and returns a function
|
||||
// that can be used to bring the original settings.
|
||||
func EnableAllVerifications() func() {
|
||||
return EnableVerifications(ENV_VERIFY_VALUE_ALL)
|
||||
}
|
||||
|
||||
// DisableVerifications unsets `ENV_VERIFY` and returns a function that
|
||||
// can be used to bring the original settings.
|
||||
func DisableVerifications() func() {
|
||||
previousEnv := getEnvVerify()
|
||||
os.Unsetenv(ENV_VERIFY)
|
||||
return func() {
|
||||
os.Setenv(ENV_VERIFY, previousEnv)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify performs verification if the assertions are enabled.
|
||||
// In the default setup running in tests and skipped in the production code.
|
||||
func Verify(f func()) {
|
||||
if IsVerificationEnabled(ENV_VERIFY_VALUE_ASSERT) {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
// Assert will panic with a given formatted message if the given condition is false.
|
||||
func Assert(condition bool, msg string, v ...any) {
|
||||
if !condition {
|
||||
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||
}
|
||||
}
|
||||
108
vendor/go.etcd.io/bbolt/internal/freelist/array.go
generated
vendored
Normal file
108
vendor/go.etcd.io/bbolt/internal/freelist/array.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type array struct {
|
||||
*shared
|
||||
|
||||
ids []common.Pgid // all free and available free page ids.
|
||||
}
|
||||
|
||||
func (f *array) Init(ids common.Pgids) {
|
||||
f.ids = ids
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *array) Allocate(txid common.Txid, n int) common.Pgid {
|
||||
if len(f.ids) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var initial, previd common.Pgid
|
||||
for i, id := range f.ids {
|
||||
if id <= 1 {
|
||||
panic(fmt.Sprintf("invalid page allocation: %d", id))
|
||||
}
|
||||
|
||||
// Reset initial page if this is not contiguous.
|
||||
if previd == 0 || id-previd != 1 {
|
||||
initial = id
|
||||
}
|
||||
|
||||
// If we found a contiguous block then remove it and return it.
|
||||
if (id-initial)+1 == common.Pgid(n) {
|
||||
// If we're allocating off the beginning then take the fast path
|
||||
// and just adjust the existing slice. This will use extra memory
|
||||
// temporarily but the append() in free() will realloc the slice
|
||||
// as is necessary.
|
||||
if (i + 1) == n {
|
||||
f.ids = f.ids[i+1:]
|
||||
} else {
|
||||
copy(f.ids[i-n+1:], f.ids[i+1:])
|
||||
f.ids = f.ids[:len(f.ids)-n]
|
||||
}
|
||||
|
||||
// Remove from the free cache.
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, initial+i)
|
||||
}
|
||||
f.allocs[initial] = txid
|
||||
return initial
|
||||
}
|
||||
|
||||
previd = id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *array) FreeCount() int {
|
||||
return len(f.ids)
|
||||
}
|
||||
|
||||
func (f *array) freePageIds() common.Pgids {
|
||||
return f.ids
|
||||
}
|
||||
|
||||
func (f *array) mergeSpans(ids common.Pgids) {
|
||||
sort.Sort(ids)
|
||||
common.Verify(func() {
|
||||
idsIdx := make(map[common.Pgid]struct{})
|
||||
for _, id := range f.ids {
|
||||
// The existing f.ids shouldn't have duplicated free ID.
|
||||
if _, ok := idsIdx[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in existing f.ids: %v", id, f.ids))
|
||||
}
|
||||
idsIdx[id] = struct{}{}
|
||||
}
|
||||
|
||||
prev := common.Pgid(0)
|
||||
for _, id := range ids {
|
||||
// The ids shouldn't have duplicated free ID. Note page 0 and 1
|
||||
// are reserved for meta pages, so they can never be free page IDs.
|
||||
if prev == id {
|
||||
panic(fmt.Sprintf("detected duplicated free ID: %d in ids: %v", id, ids))
|
||||
}
|
||||
prev = id
|
||||
|
||||
// The ids shouldn't have any overlap with the existing f.ids.
|
||||
if _, ok := idsIdx[id]; ok {
|
||||
panic(fmt.Sprintf("detected overlapped free page ID: %d between ids: %v and existing f.ids: %v", id, ids, f.ids))
|
||||
}
|
||||
}
|
||||
})
|
||||
f.ids = common.Pgids(f.ids).Merge(ids)
|
||||
}
|
||||
|
||||
func NewArrayFreelist() Interface {
|
||||
a := &array{
|
||||
shared: newShared(),
|
||||
ids: []common.Pgid{},
|
||||
}
|
||||
a.Interface = a
|
||||
return a
|
||||
}
|
||||
82
vendor/go.etcd.io/bbolt/internal/freelist/freelist.go
generated
vendored
Normal file
82
vendor/go.etcd.io/bbolt/internal/freelist/freelist.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type ReadWriter interface {
|
||||
// Read calls Init with the page ids stored in the given page.
|
||||
Read(page *common.Page)
|
||||
|
||||
// Write writes the freelist into the given page.
|
||||
Write(page *common.Page)
|
||||
|
||||
// EstimatedWritePageSize returns the size in bytes of the freelist after serialization in Write.
|
||||
// This should never underestimate the size.
|
||||
EstimatedWritePageSize() int
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
ReadWriter
|
||||
|
||||
// Init initializes this freelist with the given list of pages.
|
||||
Init(ids common.Pgids)
|
||||
|
||||
// Allocate tries to allocate the given number of contiguous pages
|
||||
// from the free list pages. It returns the starting page ID if
|
||||
// available; otherwise, it returns 0.
|
||||
Allocate(txid common.Txid, numPages int) common.Pgid
|
||||
|
||||
// Count returns the number of free and pending pages.
|
||||
Count() int
|
||||
|
||||
// FreeCount returns the number of free pages.
|
||||
FreeCount() int
|
||||
|
||||
// PendingCount returns the number of pending pages.
|
||||
PendingCount() int
|
||||
|
||||
// AddReadonlyTXID adds a given read-only transaction id for pending page tracking.
|
||||
AddReadonlyTXID(txid common.Txid)
|
||||
|
||||
// RemoveReadonlyTXID removes a given read-only transaction id for pending page tracking.
|
||||
RemoveReadonlyTXID(txid common.Txid)
|
||||
|
||||
// ReleasePendingPages releases any pages associated with closed read-only transactions.
|
||||
ReleasePendingPages()
|
||||
|
||||
// Free releases a page and its overflow for a given transaction id.
|
||||
// If the page is already free or is one of the meta pages, then a panic will occur.
|
||||
Free(txId common.Txid, p *common.Page)
|
||||
|
||||
// Freed returns whether a given page is in the free list.
|
||||
Freed(pgId common.Pgid) bool
|
||||
|
||||
// Rollback removes the pages from a given pending tx.
|
||||
Rollback(txId common.Txid)
|
||||
|
||||
// Copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
Copyall(dst []common.Pgid)
|
||||
|
||||
// Reload reads the freelist from a page and filters out pending items.
|
||||
Reload(p *common.Page)
|
||||
|
||||
// NoSyncReload reads the freelist from Pgids and filters out pending items.
|
||||
NoSyncReload(pgIds common.Pgids)
|
||||
|
||||
// freePageIds returns the IDs of all free pages. Returns an empty slice if no free pages are available.
|
||||
freePageIds() common.Pgids
|
||||
|
||||
// pendingPageIds returns all pending pages by transaction id.
|
||||
pendingPageIds() map[common.Txid]*txPending
|
||||
|
||||
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||
release(txId common.Txid)
|
||||
|
||||
// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
|
||||
releaseRange(begin, end common.Txid)
|
||||
|
||||
// mergeSpans is merging the given pages into the freelist
|
||||
mergeSpans(ids common.Pgids)
|
||||
}
|
||||
292
vendor/go.etcd.io/bbolt/internal/freelist/hashmap.go
generated
vendored
Normal file
292
vendor/go.etcd.io/bbolt/internal/freelist/hashmap.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// pidSet holds the set of starting pgids which have the same span size
|
||||
type pidSet map[common.Pgid]struct{}
|
||||
|
||||
type hashMap struct {
|
||||
*shared
|
||||
|
||||
freePagesCount uint64 // count of free pages(hashmap version)
|
||||
freemaps map[uint64]pidSet // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
|
||||
forwardMap map[common.Pgid]uint64 // key is start pgid, value is its span size
|
||||
backwardMap map[common.Pgid]uint64 // key is end pgid, value is its span size
|
||||
}
|
||||
|
||||
func (f *hashMap) Init(pgids common.Pgids) {
|
||||
// reset the counter when freelist init
|
||||
f.freePagesCount = 0
|
||||
f.freemaps = make(map[uint64]pidSet)
|
||||
f.forwardMap = make(map[common.Pgid]uint64)
|
||||
f.backwardMap = make(map[common.Pgid]uint64)
|
||||
|
||||
if len(pgids) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !sort.SliceIsSorted([]common.Pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) {
|
||||
panic("pgids not sorted")
|
||||
}
|
||||
|
||||
size := uint64(1)
|
||||
start := pgids[0]
|
||||
|
||||
for i := 1; i < len(pgids); i++ {
|
||||
// continuous page
|
||||
if pgids[i] == pgids[i-1]+1 {
|
||||
size++
|
||||
} else {
|
||||
f.addSpan(start, size)
|
||||
|
||||
size = 1
|
||||
start = pgids[i]
|
||||
}
|
||||
}
|
||||
|
||||
// init the tail
|
||||
if size != 0 && start != 0 {
|
||||
f.addSpan(start, size)
|
||||
}
|
||||
|
||||
f.reindex()
|
||||
}
|
||||
|
||||
func (f *hashMap) Allocate(txid common.Txid, n int) common.Pgid {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if we have a exact size match just return short path
|
||||
if bm, ok := f.freemaps[uint64(n)]; ok {
|
||||
for pid := range bm {
|
||||
// remove the span
|
||||
f.delSpan(pid, uint64(n))
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
// lookup the map to find larger span
|
||||
for size, bm := range f.freemaps {
|
||||
if size < uint64(n) {
|
||||
continue
|
||||
}
|
||||
|
||||
for pid := range bm {
|
||||
// remove the initial
|
||||
f.delSpan(pid, size)
|
||||
|
||||
f.allocs[pid] = txid
|
||||
|
||||
remain := size - uint64(n)
|
||||
|
||||
// add remain span
|
||||
f.addSpan(pid+common.Pgid(n), remain)
|
||||
|
||||
for i := common.Pgid(0); i < common.Pgid(n); i++ {
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *hashMap) FreeCount() int {
|
||||
common.Verify(func() {
|
||||
expectedFreePageCount := f.hashmapFreeCountSlow()
|
||||
common.Assert(int(f.freePagesCount) == expectedFreePageCount,
|
||||
"freePagesCount (%d) is out of sync with free pages map (%d)", f.freePagesCount, expectedFreePageCount)
|
||||
})
|
||||
return int(f.freePagesCount)
|
||||
}
|
||||
|
||||
func (f *hashMap) freePageIds() common.Pgids {
|
||||
count := f.FreeCount()
|
||||
if count == 0 {
|
||||
return common.Pgids{}
|
||||
}
|
||||
|
||||
m := make([]common.Pgid, 0, count)
|
||||
|
||||
startPageIds := make([]common.Pgid, 0, len(f.forwardMap))
|
||||
for k := range f.forwardMap {
|
||||
startPageIds = append(startPageIds, k)
|
||||
}
|
||||
sort.Sort(common.Pgids(startPageIds))
|
||||
|
||||
for _, start := range startPageIds {
|
||||
if size, ok := f.forwardMap[start]; ok {
|
||||
for i := 0; i < int(size); i++ {
|
||||
m = append(m, start+common.Pgid(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *hashMap) hashmapFreeCountSlow() int {
|
||||
count := 0
|
||||
for _, size := range f.forwardMap {
|
||||
count += int(size)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (f *hashMap) addSpan(start common.Pgid, size uint64) {
|
||||
f.backwardMap[start-1+common.Pgid(size)] = size
|
||||
f.forwardMap[start] = size
|
||||
if _, ok := f.freemaps[size]; !ok {
|
||||
f.freemaps[size] = make(map[common.Pgid]struct{})
|
||||
}
|
||||
|
||||
f.freemaps[size][start] = struct{}{}
|
||||
f.freePagesCount += size
|
||||
}
|
||||
|
||||
func (f *hashMap) delSpan(start common.Pgid, size uint64) {
|
||||
delete(f.forwardMap, start)
|
||||
delete(f.backwardMap, start+common.Pgid(size-1))
|
||||
delete(f.freemaps[size], start)
|
||||
if len(f.freemaps[size]) == 0 {
|
||||
delete(f.freemaps, size)
|
||||
}
|
||||
f.freePagesCount -= size
|
||||
}
|
||||
|
||||
func (f *hashMap) mergeSpans(ids common.Pgids) {
|
||||
common.Verify(func() {
|
||||
ids1Freemap := f.idsFromFreemaps()
|
||||
ids2Forward := f.idsFromForwardMap()
|
||||
ids3Backward := f.idsFromBackwardMap()
|
||||
|
||||
if !reflect.DeepEqual(ids1Freemap, ids2Forward) {
|
||||
panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.forwardMap: %v", f.freemaps, f.forwardMap))
|
||||
}
|
||||
if !reflect.DeepEqual(ids1Freemap, ids3Backward) {
|
||||
panic(fmt.Sprintf("Detected mismatch, f.freemaps: %v, f.backwardMap: %v", f.freemaps, f.backwardMap))
|
||||
}
|
||||
|
||||
sort.Sort(ids)
|
||||
prev := common.Pgid(0)
|
||||
for _, id := range ids {
|
||||
// The ids shouldn't have duplicated free ID.
|
||||
if prev == id {
|
||||
panic(fmt.Sprintf("detected duplicated free ID: %d in ids: %v", id, ids))
|
||||
}
|
||||
prev = id
|
||||
|
||||
// The ids shouldn't have any overlap with the existing f.freemaps.
|
||||
if _, ok := ids1Freemap[id]; ok {
|
||||
panic(fmt.Sprintf("detected overlapped free page ID: %d between ids: %v and existing f.freemaps: %v", id, ids, f.freemaps))
|
||||
}
|
||||
}
|
||||
})
|
||||
for _, id := range ids {
|
||||
// try to see if we can merge and update
|
||||
f.mergeWithExistingSpan(id)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward
|
||||
func (f *hashMap) mergeWithExistingSpan(pid common.Pgid) {
|
||||
prev := pid - 1
|
||||
next := pid + 1
|
||||
|
||||
preSize, mergeWithPrev := f.backwardMap[prev]
|
||||
nextSize, mergeWithNext := f.forwardMap[next]
|
||||
newStart := pid
|
||||
newSize := uint64(1)
|
||||
|
||||
if mergeWithPrev {
|
||||
//merge with previous span
|
||||
start := prev + 1 - common.Pgid(preSize)
|
||||
f.delSpan(start, preSize)
|
||||
|
||||
newStart -= common.Pgid(preSize)
|
||||
newSize += preSize
|
||||
}
|
||||
|
||||
if mergeWithNext {
|
||||
// merge with next span
|
||||
f.delSpan(next, nextSize)
|
||||
newSize += nextSize
|
||||
}
|
||||
|
||||
f.addSpan(newStart, newSize)
|
||||
}
|
||||
|
||||
// idsFromFreemaps get all free page IDs from f.freemaps.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromFreemaps() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for size, idSet := range f.freemaps {
|
||||
for start := range idSet {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := start + common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.freemaps: %v", id, f.freemaps))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// idsFromForwardMap get all free page IDs from f.forwardMap.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromForwardMap() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for start, size := range f.forwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := start + common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.forwardMap: %v", id, f.forwardMap))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// idsFromBackwardMap get all free page IDs from f.backwardMap.
|
||||
// used by test only.
|
||||
func (f *hashMap) idsFromBackwardMap() map[common.Pgid]struct{} {
|
||||
ids := make(map[common.Pgid]struct{})
|
||||
for end, size := range f.backwardMap {
|
||||
for i := 0; i < int(size); i++ {
|
||||
id := end - common.Pgid(i)
|
||||
if _, ok := ids[id]; ok {
|
||||
panic(fmt.Sprintf("detected duplicated free page ID: %d in f.backwardMap: %v", id, f.backwardMap))
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func NewHashMapFreelist() Interface {
|
||||
hm := &hashMap{
|
||||
shared: newShared(),
|
||||
freemaps: make(map[uint64]pidSet),
|
||||
forwardMap: make(map[common.Pgid]uint64),
|
||||
backwardMap: make(map[common.Pgid]uint64),
|
||||
}
|
||||
hm.Interface = hm
|
||||
return hm
|
||||
}
|
||||
310
vendor/go.etcd.io/bbolt/internal/freelist/shared.go
generated
vendored
Normal file
310
vendor/go.etcd.io/bbolt/internal/freelist/shared.go
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
package freelist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
type txPending struct {
|
||||
ids []common.Pgid
|
||||
alloctx []common.Txid // txids allocating the ids
|
||||
lastReleaseBegin common.Txid // beginning txid of last matching releaseRange
|
||||
}
|
||||
|
||||
type shared struct {
|
||||
Interface
|
||||
|
||||
readonlyTXIDs []common.Txid // all readonly transaction IDs.
|
||||
allocs map[common.Pgid]common.Txid // mapping of Txid that allocated a pgid.
|
||||
cache map[common.Pgid]struct{} // fast lookup of all free and pending page ids.
|
||||
pending map[common.Txid]*txPending // mapping of soon-to-be free page ids by tx.
|
||||
}
|
||||
|
||||
func newShared() *shared {
|
||||
return &shared{
|
||||
pending: make(map[common.Txid]*txPending),
|
||||
allocs: make(map[common.Pgid]common.Txid),
|
||||
cache: make(map[common.Pgid]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) pendingPageIds() map[common.Txid]*txPending {
|
||||
return t.pending
|
||||
}
|
||||
|
||||
func (t *shared) PendingCount() int {
|
||||
var count int
|
||||
for _, txp := range t.pending {
|
||||
count += len(txp.ids)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (t *shared) Count() int {
|
||||
return t.FreeCount() + t.PendingCount()
|
||||
}
|
||||
|
||||
func (t *shared) Freed(pgId common.Pgid) bool {
|
||||
_, ok := t.cache[pgId]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t *shared) Free(txid common.Txid, p *common.Page) {
|
||||
if p.Id() <= 1 {
|
||||
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.Id()))
|
||||
}
|
||||
|
||||
// Free page and all its overflow pages.
|
||||
txp := t.pending[txid]
|
||||
if txp == nil {
|
||||
txp = &txPending{}
|
||||
t.pending[txid] = txp
|
||||
}
|
||||
allocTxid, ok := t.allocs[p.Id()]
|
||||
common.Verify(func() {
|
||||
if allocTxid == txid {
|
||||
panic(fmt.Sprintf("free: freed page (%d) was allocated by the same transaction (%d)", p.Id(), txid))
|
||||
}
|
||||
})
|
||||
if ok {
|
||||
delete(t.allocs, p.Id())
|
||||
}
|
||||
|
||||
for id := p.Id(); id <= p.Id()+common.Pgid(p.Overflow()); id++ {
|
||||
// Verify that page is not already free.
|
||||
if _, ok := t.cache[id]; ok {
|
||||
panic(fmt.Sprintf("page %d already freed", id))
|
||||
}
|
||||
// Add to the freelist and cache.
|
||||
txp.ids = append(txp.ids, id)
|
||||
txp.alloctx = append(txp.alloctx, allocTxid)
|
||||
t.cache[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) Rollback(txid common.Txid) {
|
||||
// Remove page ids from cache.
|
||||
txp := t.pending[txid]
|
||||
if txp == nil {
|
||||
return
|
||||
}
|
||||
for i, pgid := range txp.ids {
|
||||
delete(t.cache, pgid)
|
||||
tx := txp.alloctx[i]
|
||||
if tx == 0 {
|
||||
continue
|
||||
}
|
||||
if tx != txid {
|
||||
// Pending free aborted; restore page back to alloc list.
|
||||
t.allocs[pgid] = tx
|
||||
} else {
|
||||
// A writing TXN should never free a page which was allocated by itself.
|
||||
panic(fmt.Sprintf("rollback: freed page (%d) was allocated by the same transaction (%d)", pgid, txid))
|
||||
}
|
||||
}
|
||||
// Remove pages from pending list and mark as free if allocated by txid.
|
||||
delete(t.pending, txid)
|
||||
|
||||
// Remove pgids which are allocated by this txid
|
||||
for pgid, tid := range t.allocs {
|
||||
if tid == txid {
|
||||
delete(t.allocs, pgid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) AddReadonlyTXID(tid common.Txid) {
|
||||
t.readonlyTXIDs = append(t.readonlyTXIDs, tid)
|
||||
}
|
||||
|
||||
func (t *shared) RemoveReadonlyTXID(tid common.Txid) {
|
||||
for i := range t.readonlyTXIDs {
|
||||
if t.readonlyTXIDs[i] == tid {
|
||||
last := len(t.readonlyTXIDs) - 1
|
||||
t.readonlyTXIDs[i] = t.readonlyTXIDs[last]
|
||||
t.readonlyTXIDs = t.readonlyTXIDs[:last]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type txIDx []common.Txid
|
||||
|
||||
func (t txIDx) Len() int { return len(t) }
|
||||
func (t txIDx) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t txIDx) Less(i, j int) bool { return t[i] < t[j] }
|
||||
|
||||
func (t *shared) ReleasePendingPages() {
|
||||
// Free all pending pages prior to the earliest open transaction.
|
||||
sort.Sort(txIDx(t.readonlyTXIDs))
|
||||
minid := common.Txid(math.MaxUint64)
|
||||
if len(t.readonlyTXIDs) > 0 {
|
||||
minid = t.readonlyTXIDs[0]
|
||||
}
|
||||
if minid > 0 {
|
||||
t.release(minid - 1)
|
||||
}
|
||||
// Release unused txid extents.
|
||||
for _, tid := range t.readonlyTXIDs {
|
||||
t.releaseRange(minid, tid-1)
|
||||
minid = tid + 1
|
||||
}
|
||||
t.releaseRange(minid, common.Txid(math.MaxUint64))
|
||||
// Any page both allocated and freed in an extent is safe to release.
|
||||
}
|
||||
|
||||
func (t *shared) release(txid common.Txid) {
|
||||
m := make(common.Pgids, 0)
|
||||
for tid, txp := range t.pending {
|
||||
if tid <= txid {
|
||||
// Move transaction's pending pages to the available freelist.
|
||||
// Don't remove from the cache since the page is still free.
|
||||
m = append(m, txp.ids...)
|
||||
delete(t.pending, tid)
|
||||
}
|
||||
}
|
||||
t.mergeSpans(m)
|
||||
}
|
||||
|
||||
func (t *shared) releaseRange(begin, end common.Txid) {
|
||||
if begin > end {
|
||||
return
|
||||
}
|
||||
m := common.Pgids{}
|
||||
for tid, txp := range t.pending {
|
||||
if tid < begin || tid > end {
|
||||
continue
|
||||
}
|
||||
// Don't recompute freed pages if ranges haven't updated.
|
||||
if txp.lastReleaseBegin == begin {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < len(txp.ids); i++ {
|
||||
if atx := txp.alloctx[i]; atx < begin || atx > end {
|
||||
continue
|
||||
}
|
||||
m = append(m, txp.ids[i])
|
||||
txp.ids[i] = txp.ids[len(txp.ids)-1]
|
||||
txp.ids = txp.ids[:len(txp.ids)-1]
|
||||
txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
|
||||
txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
|
||||
i--
|
||||
}
|
||||
txp.lastReleaseBegin = begin
|
||||
if len(txp.ids) == 0 {
|
||||
delete(t.pending, tid)
|
||||
}
|
||||
}
|
||||
t.mergeSpans(m)
|
||||
}
|
||||
|
||||
// Copyall copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
func (t *shared) Copyall(dst []common.Pgid) {
|
||||
m := make(common.Pgids, 0, t.PendingCount())
|
||||
for _, txp := range t.pendingPageIds() {
|
||||
m = append(m, txp.ids...)
|
||||
}
|
||||
sort.Sort(m)
|
||||
common.Mergepgids(dst, t.freePageIds(), m)
|
||||
}
|
||||
|
||||
func (t *shared) Reload(p *common.Page) {
|
||||
t.Read(p)
|
||||
t.NoSyncReload(t.freePageIds())
|
||||
}
|
||||
|
||||
func (t *shared) NoSyncReload(pgIds common.Pgids) {
|
||||
// Build a cache of only pending pages.
|
||||
pcache := make(map[common.Pgid]bool)
|
||||
for _, txp := range t.pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
pcache[pendingID] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check each page in the freelist and build a new available freelist
|
||||
// with any pages not in the pending lists.
|
||||
a := []common.Pgid{}
|
||||
for _, id := range pgIds {
|
||||
if !pcache[id] {
|
||||
a = append(a, id)
|
||||
}
|
||||
}
|
||||
|
||||
t.Init(a)
|
||||
}
|
||||
|
||||
// reindex rebuilds the free cache based on available and pending free lists.
|
||||
func (t *shared) reindex() {
|
||||
free := t.freePageIds()
|
||||
pending := t.pendingPageIds()
|
||||
t.cache = make(map[common.Pgid]struct{}, len(free))
|
||||
for _, id := range free {
|
||||
t.cache[id] = struct{}{}
|
||||
}
|
||||
for _, txp := range pending {
|
||||
for _, pendingID := range txp.ids {
|
||||
t.cache[pendingID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) Read(p *common.Page) {
|
||||
if !p.IsFreelistPage() {
|
||||
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.Id(), p.Typ()))
|
||||
}
|
||||
|
||||
ids := p.FreelistPageIds()
|
||||
|
||||
// Copy the list of page ids from the freelist.
|
||||
if len(ids) == 0 {
|
||||
t.Init([]common.Pgid{})
|
||||
} else {
|
||||
// copy the ids, so we don't modify on the freelist page directly
|
||||
idsCopy := make([]common.Pgid, len(ids))
|
||||
copy(idsCopy, ids)
|
||||
// Make sure they're sorted.
|
||||
sort.Sort(common.Pgids(idsCopy))
|
||||
|
||||
t.Init(idsCopy)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *shared) EstimatedWritePageSize() int {
|
||||
n := t.Count()
|
||||
if n >= 0xFFFF {
|
||||
// The first element will be used to store the count. See freelist.write.
|
||||
n++
|
||||
}
|
||||
return int(common.PageHeaderSize) + (int(unsafe.Sizeof(common.Pgid(0))) * n)
|
||||
}
|
||||
|
||||
func (t *shared) Write(p *common.Page) {
|
||||
// Combine the old free pgids and pgids waiting on an open transaction.
|
||||
|
||||
// Update the header flag.
|
||||
p.SetFlags(common.FreelistPageFlag)
|
||||
|
||||
// The page.count can only hold up to 64k elements so if we overflow that
|
||||
// number then we handle it by putting the size in the first element.
|
||||
l := t.Count()
|
||||
if l == 0 {
|
||||
p.SetCount(uint16(l))
|
||||
} else if l < 0xFFFF {
|
||||
p.SetCount(uint16(l))
|
||||
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*common.Pgid)(data), l)
|
||||
t.Copyall(ids)
|
||||
} else {
|
||||
p.SetCount(0xFFFF)
|
||||
data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
ids := unsafe.Slice((*common.Pgid)(data), l+1)
|
||||
ids[0] = common.Pgid(l)
|
||||
t.Copyall(ids[1:])
|
||||
}
|
||||
}
|
||||
113
vendor/go.etcd.io/bbolt/logger.go
generated
vendored
Normal file
113
vendor/go.etcd.io/bbolt/logger.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
package bbolt
|
||||
|
||||
// See https://github.com/etcd-io/raft/blob/main/logger.go
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Debug(v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
|
||||
Error(v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
|
||||
Info(v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
|
||||
Warning(v ...interface{})
|
||||
Warningf(format string, v ...interface{})
|
||||
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
|
||||
Panic(v ...interface{})
|
||||
Panicf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func getDiscardLogger() Logger {
|
||||
return discardLogger
|
||||
}
|
||||
|
||||
var (
|
||||
discardLogger = &DefaultLogger{Logger: log.New(io.Discard, "", 0)}
|
||||
)
|
||||
|
||||
const (
|
||||
calldepth = 2
|
||||
)
|
||||
|
||||
// DefaultLogger is a default implementation of the Logger interface.
|
||||
type DefaultLogger struct {
|
||||
*log.Logger
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableTimestamps() {
|
||||
l.SetFlags(l.Flags() | log.Ldate | log.Ltime)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableDebug() {
|
||||
l.debug = true
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debug(v ...interface{}) {
|
||||
if l.debug {
|
||||
_ = l.Output(calldepth, header("DEBUG", fmt.Sprint(v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debugf(format string, v ...interface{}) {
|
||||
if l.debug {
|
||||
_ = l.Output(calldepth, header("DEBUG", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Info(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("INFO", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Infof(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("INFO", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Error(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("ERROR", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Errorf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("ERROR", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warning(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("WARN", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warningf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("WARN", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatal(v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("FATAL", fmt.Sprint(v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
_ = l.Output(calldepth, header("FATAL", fmt.Sprintf(format, v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panic(v ...interface{}) {
|
||||
l.Logger.Panic(v...)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panicf(format string, v ...interface{}) {
|
||||
l.Logger.Panicf(format, v...)
|
||||
}
|
||||
|
||||
func header(lvl, msg string) string {
|
||||
return fmt.Sprintf("%s: %s", lvl, msg)
|
||||
}
|
||||
1
vendor/go.etcd.io/bbolt/mlock_unix.go
generated
vendored
1
vendor/go.etcd.io/bbolt/mlock_unix.go
generated
vendored
@@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package bbolt
|
||||
|
||||
|
||||
244
vendor/go.etcd.io/bbolt/node.go
generated
vendored
244
vendor/go.etcd.io/bbolt/node.go
generated
vendored
@@ -4,7 +4,8 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// node represents an in-memory, deserialized page.
|
||||
@@ -14,10 +15,10 @@ type node struct {
|
||||
unbalanced bool
|
||||
spilled bool
|
||||
key []byte
|
||||
pgid pgid
|
||||
pgid common.Pgid
|
||||
parent *node
|
||||
children nodes
|
||||
inodes inodes
|
||||
inodes common.Inodes
|
||||
}
|
||||
|
||||
// root returns the top-level node this node is attached to.
|
||||
@@ -38,10 +39,10 @@ func (n *node) minKeys() int {
|
||||
|
||||
// size returns the size of the node after serialization.
|
||||
func (n *node) size() int {
|
||||
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||
sz, elsz := common.PageHeaderSize, n.pageElementSize()
|
||||
for i := 0; i < len(n.inodes); i++ {
|
||||
item := &n.inodes[i]
|
||||
sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value))
|
||||
sz += elsz + uintptr(len(item.Key())) + uintptr(len(item.Value()))
|
||||
}
|
||||
return int(sz)
|
||||
}
|
||||
@@ -50,10 +51,10 @@ func (n *node) size() int {
|
||||
// This is an optimization to avoid calculating a large node when we only need
|
||||
// to know if it fits inside a certain page size.
|
||||
func (n *node) sizeLessThan(v uintptr) bool {
|
||||
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||
sz, elsz := common.PageHeaderSize, n.pageElementSize()
|
||||
for i := 0; i < len(n.inodes); i++ {
|
||||
item := &n.inodes[i]
|
||||
sz += elsz + uintptr(len(item.key)) + uintptr(len(item.value))
|
||||
sz += elsz + uintptr(len(item.Key())) + uintptr(len(item.Value()))
|
||||
if sz >= v {
|
||||
return false
|
||||
}
|
||||
@@ -64,9 +65,9 @@ func (n *node) sizeLessThan(v uintptr) bool {
|
||||
// pageElementSize returns the size of each page element based on the type of node.
|
||||
func (n *node) pageElementSize() uintptr {
|
||||
if n.isLeaf {
|
||||
return leafPageElementSize
|
||||
return common.LeafPageElementSize
|
||||
}
|
||||
return branchPageElementSize
|
||||
return common.BranchPageElementSize
|
||||
}
|
||||
|
||||
// childAt returns the child node at a given index.
|
||||
@@ -74,12 +75,12 @@ func (n *node) childAt(index int) *node {
|
||||
if n.isLeaf {
|
||||
panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index))
|
||||
}
|
||||
return n.bucket.node(n.inodes[index].pgid, n)
|
||||
return n.bucket.node(n.inodes[index].Pgid(), n)
|
||||
}
|
||||
|
||||
// childIndex returns the index of a given child node.
|
||||
func (n *node) childIndex(child *node) int {
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), child.key) != -1 })
|
||||
return index
|
||||
}
|
||||
|
||||
@@ -113,9 +114,9 @@ func (n *node) prevSibling() *node {
|
||||
}
|
||||
|
||||
// put inserts a key/value.
|
||||
func (n *node) put(oldKey, newKey, value []byte, pgId pgid, flags uint32) {
|
||||
if pgId >= n.bucket.tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgId (%d) above high water mark (%d)", pgId, n.bucket.tx.meta.pgid))
|
||||
func (n *node) put(oldKey, newKey, value []byte, pgId common.Pgid, flags uint32) {
|
||||
if pgId >= n.bucket.tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgId (%d) above high water mark (%d)", pgId, n.bucket.tx.meta.Pgid()))
|
||||
} else if len(oldKey) <= 0 {
|
||||
panic("put: zero-length old key")
|
||||
} else if len(newKey) <= 0 {
|
||||
@@ -123,30 +124,30 @@ func (n *node) put(oldKey, newKey, value []byte, pgId pgid, flags uint32) {
|
||||
}
|
||||
|
||||
// Find insertion index.
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), oldKey) != -1 })
|
||||
|
||||
// Add capacity and shift nodes if we don't have an exact match and need to insert.
|
||||
exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey))
|
||||
exact := len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].Key(), oldKey)
|
||||
if !exact {
|
||||
n.inodes = append(n.inodes, inode{})
|
||||
n.inodes = append(n.inodes, common.Inode{})
|
||||
copy(n.inodes[index+1:], n.inodes[index:])
|
||||
}
|
||||
|
||||
inode := &n.inodes[index]
|
||||
inode.flags = flags
|
||||
inode.key = newKey
|
||||
inode.value = value
|
||||
inode.pgid = pgId
|
||||
_assert(len(inode.key) > 0, "put: zero-length inode key")
|
||||
inode.SetFlags(flags)
|
||||
inode.SetKey(newKey)
|
||||
inode.SetValue(value)
|
||||
inode.SetPgid(pgId)
|
||||
common.Assert(len(inode.Key()) > 0, "put: zero-length inode key")
|
||||
}
|
||||
|
||||
// del removes a key from the node.
|
||||
func (n *node) del(key []byte) {
|
||||
// Find index of key.
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 })
|
||||
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].Key(), key) != -1 })
|
||||
|
||||
// Exit if the key isn't found.
|
||||
if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) {
|
||||
if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].Key(), key) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -158,30 +159,15 @@ func (n *node) del(key []byte) {
|
||||
}
|
||||
|
||||
// read initializes the node from a page.
|
||||
func (n *node) read(p *page) {
|
||||
n.pgid = p.id
|
||||
n.isLeaf = ((p.flags & leafPageFlag) != 0)
|
||||
n.inodes = make(inodes, int(p.count))
|
||||
func (n *node) read(p *common.Page) {
|
||||
n.pgid = p.Id()
|
||||
n.isLeaf = p.IsLeafPage()
|
||||
n.inodes = common.ReadInodeFromPage(p)
|
||||
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
inode := &n.inodes[i]
|
||||
if n.isLeaf {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
inode.flags = elem.flags
|
||||
inode.key = elem.key()
|
||||
inode.value = elem.value()
|
||||
} else {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
inode.pgid = elem.pgid
|
||||
inode.key = elem.key()
|
||||
}
|
||||
_assert(len(inode.key) > 0, "read: zero-length inode key")
|
||||
}
|
||||
|
||||
// Save first key so we can find the node in the parent when we spill.
|
||||
// Save first key, so we can find the node in the parent when we spill.
|
||||
if len(n.inodes) > 0 {
|
||||
n.key = n.inodes[0].key
|
||||
_assert(len(n.key) > 0, "read: zero-length node key")
|
||||
n.key = n.inodes[0].Key()
|
||||
common.Assert(len(n.key) > 0, "read: zero-length node key")
|
||||
} else {
|
||||
n.key = nil
|
||||
}
|
||||
@@ -190,57 +176,27 @@ func (n *node) read(p *page) {
|
||||
// write writes the items onto one or more pages.
|
||||
// The page should have p.id (might be 0 for meta or bucket-inline page) and p.overflow set
|
||||
// and the rest should be zeroed.
|
||||
func (n *node) write(p *page) {
|
||||
_assert(p.count == 0 && p.flags == 0, "node cannot be written into a not empty page")
|
||||
func (n *node) write(p *common.Page) {
|
||||
common.Assert(p.Count() == 0 && p.Flags() == 0, "node cannot be written into a not empty page")
|
||||
|
||||
// Initialize page.
|
||||
if n.isLeaf {
|
||||
p.flags = leafPageFlag
|
||||
p.SetFlags(common.LeafPageFlag)
|
||||
} else {
|
||||
p.flags = branchPageFlag
|
||||
p.SetFlags(common.BranchPageFlag)
|
||||
}
|
||||
|
||||
if len(n.inodes) >= 0xFFFF {
|
||||
panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id))
|
||||
panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.Id()))
|
||||
}
|
||||
p.count = uint16(len(n.inodes))
|
||||
p.SetCount(uint16(len(n.inodes)))
|
||||
|
||||
// Stop here if there are no items to write.
|
||||
if p.count == 0 {
|
||||
if p.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop over each item and write it to the page.
|
||||
// off tracks the offset into p of the start of the next data.
|
||||
off := unsafe.Sizeof(*p) + n.pageElementSize()*uintptr(len(n.inodes))
|
||||
for i, item := range n.inodes {
|
||||
_assert(len(item.key) > 0, "write: zero-length inode key")
|
||||
|
||||
// Create a slice to write into of needed size and advance
|
||||
// byte pointer for next iteration.
|
||||
sz := len(item.key) + len(item.value)
|
||||
b := unsafeByteSlice(unsafe.Pointer(p), off, 0, sz)
|
||||
off += uintptr(sz)
|
||||
|
||||
// Write the page element.
|
||||
if n.isLeaf {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||
elem.flags = item.flags
|
||||
elem.ksize = uint32(len(item.key))
|
||||
elem.vsize = uint32(len(item.value))
|
||||
} else {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||
elem.ksize = uint32(len(item.key))
|
||||
elem.pgid = item.pgid
|
||||
_assert(elem.pgid != p.id, "write: circular dependency occurred")
|
||||
}
|
||||
|
||||
// Write data for the element to the end of the page.
|
||||
l := copy(b, item.key)
|
||||
copy(b[l:], item.value)
|
||||
}
|
||||
common.WriteInodeToPage(n.inodes, p)
|
||||
|
||||
// DEBUG ONLY: n.dump()
|
||||
}
|
||||
@@ -273,7 +229,7 @@ func (n *node) split(pageSize uintptr) []*node {
|
||||
func (n *node) splitTwo(pageSize uintptr) (*node, *node) {
|
||||
// Ignore the split if the page doesn't have at least enough nodes for
|
||||
// two pages or if the nodes can fit in a single page.
|
||||
if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) {
|
||||
if len(n.inodes) <= (common.MinKeysPerPage*2) || n.sizeLessThan(pageSize) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -313,17 +269,17 @@ func (n *node) splitTwo(pageSize uintptr) (*node, *node) {
|
||||
// It returns the index as well as the size of the first page.
|
||||
// This is only be called from split().
|
||||
func (n *node) splitIndex(threshold int) (index, sz uintptr) {
|
||||
sz = pageHeaderSize
|
||||
sz = common.PageHeaderSize
|
||||
|
||||
// Loop until we only have the minimum number of keys required for the second page.
|
||||
for i := 0; i < len(n.inodes)-minKeysPerPage; i++ {
|
||||
for i := 0; i < len(n.inodes)-common.MinKeysPerPage; i++ {
|
||||
index = uintptr(i)
|
||||
inode := n.inodes[i]
|
||||
elsize := n.pageElementSize() + uintptr(len(inode.key)) + uintptr(len(inode.value))
|
||||
elsize := n.pageElementSize() + uintptr(len(inode.Key())) + uintptr(len(inode.Value()))
|
||||
|
||||
// If we have at least the minimum number of keys and adding another
|
||||
// node would put us over the threshold then exit and return.
|
||||
if index >= minKeysPerPage && sz+elsize > uintptr(threshold) {
|
||||
if index >= common.MinKeysPerPage && sz+elsize > uintptr(threshold) {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -360,7 +316,7 @@ func (n *node) spill() error {
|
||||
for _, node := range nodes {
|
||||
// Add node's page to the freelist if it's not new.
|
||||
if node.pgid > 0 {
|
||||
tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid))
|
||||
tx.db.freelist.Free(tx.meta.Txid(), tx.page(node.pgid))
|
||||
node.pgid = 0
|
||||
}
|
||||
|
||||
@@ -371,10 +327,10 @@ func (n *node) spill() error {
|
||||
}
|
||||
|
||||
// Write the node.
|
||||
if p.id >= tx.meta.pgid {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid))
|
||||
if p.Id() >= tx.meta.Pgid() {
|
||||
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.Id(), tx.meta.Pgid()))
|
||||
}
|
||||
node.pgid = p.id
|
||||
node.pgid = p.Id()
|
||||
node.write(p)
|
||||
node.spilled = true
|
||||
|
||||
@@ -382,12 +338,12 @@ func (n *node) spill() error {
|
||||
if node.parent != nil {
|
||||
var key = node.key
|
||||
if key == nil {
|
||||
key = node.inodes[0].key
|
||||
key = node.inodes[0].Key()
|
||||
}
|
||||
|
||||
node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0)
|
||||
node.key = node.inodes[0].key
|
||||
_assert(len(node.key) > 0, "spill: zero-length node key")
|
||||
node.parent.put(key, node.inodes[0].Key(), nil, node.pgid, 0)
|
||||
node.key = node.inodes[0].Key()
|
||||
common.Assert(len(node.key) > 0, "spill: zero-length node key")
|
||||
}
|
||||
|
||||
// Update the statistics.
|
||||
@@ -415,8 +371,8 @@ func (n *node) rebalance() {
|
||||
// Update statistics.
|
||||
n.bucket.tx.stats.IncRebalance(1)
|
||||
|
||||
// Ignore if node is above threshold (25%) and has enough keys.
|
||||
var threshold = n.bucket.tx.db.pageSize / 4
|
||||
// Ignore if node is above threshold (25% when FillPercent is set to DefaultFillPercent) and has enough keys.
|
||||
var threshold = int(float64(n.bucket.tx.db.pageSize)*n.bucket.FillPercent) / 2
|
||||
if n.size() > threshold && len(n.inodes) > n.minKeys() {
|
||||
return
|
||||
}
|
||||
@@ -426,14 +382,14 @@ func (n *node) rebalance() {
|
||||
// If root node is a branch and only has one node then collapse it.
|
||||
if !n.isLeaf && len(n.inodes) == 1 {
|
||||
// Move root's child up.
|
||||
child := n.bucket.node(n.inodes[0].pgid, n)
|
||||
child := n.bucket.node(n.inodes[0].Pgid(), n)
|
||||
n.isLeaf = child.isLeaf
|
||||
n.inodes = child.inodes[:]
|
||||
n.children = child.children
|
||||
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range n.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
if child, ok := n.bucket.nodes[inode.Pgid()]; ok {
|
||||
child.parent = n
|
||||
}
|
||||
}
|
||||
@@ -457,53 +413,37 @@ func (n *node) rebalance() {
|
||||
return
|
||||
}
|
||||
|
||||
_assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
|
||||
common.Assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
|
||||
|
||||
// Destination node is right sibling if idx == 0, otherwise left sibling.
|
||||
var target *node
|
||||
var useNextSibling = (n.parent.childIndex(n) == 0)
|
||||
// Merge with right sibling if idx == 0, otherwise left sibling.
|
||||
var leftNode, rightNode *node
|
||||
var useNextSibling = n.parent.childIndex(n) == 0
|
||||
if useNextSibling {
|
||||
target = n.nextSibling()
|
||||
leftNode = n
|
||||
rightNode = n.nextSibling()
|
||||
} else {
|
||||
target = n.prevSibling()
|
||||
leftNode = n.prevSibling()
|
||||
rightNode = n
|
||||
}
|
||||
|
||||
// If both this node and the target node are too small then merge them.
|
||||
if useNextSibling {
|
||||
// If both nodes are too small then merge them.
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range target.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
for _, inode := range rightNode.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.Pgid()]; ok {
|
||||
child.parent.removeChild(child)
|
||||
child.parent = n
|
||||
child.parent = leftNode
|
||||
child.parent.children = append(child.parent.children, child)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over inodes from target and remove target.
|
||||
n.inodes = append(n.inodes, target.inodes...)
|
||||
n.parent.del(target.key)
|
||||
n.parent.removeChild(target)
|
||||
delete(n.bucket.nodes, target.pgid)
|
||||
target.free()
|
||||
} else {
|
||||
// Reparent all child nodes being moved.
|
||||
for _, inode := range n.inodes {
|
||||
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||
child.parent.removeChild(child)
|
||||
child.parent = target
|
||||
child.parent.children = append(child.parent.children, child)
|
||||
}
|
||||
}
|
||||
// Copy over inodes from right node to left node and remove right node.
|
||||
leftNode.inodes = append(leftNode.inodes, rightNode.inodes...)
|
||||
n.parent.del(rightNode.key)
|
||||
n.parent.removeChild(rightNode)
|
||||
delete(n.bucket.nodes, rightNode.pgid)
|
||||
rightNode.free()
|
||||
|
||||
// Copy over inodes to target and remove node.
|
||||
target.inodes = append(target.inodes, n.inodes...)
|
||||
n.parent.del(n.key)
|
||||
n.parent.removeChild(n)
|
||||
delete(n.bucket.nodes, n.pgid)
|
||||
n.free()
|
||||
}
|
||||
|
||||
// Either this node or the target node was deleted from the parent so rebalance it.
|
||||
// Either this node or the sibling node was deleted from the parent so rebalance it.
|
||||
n.parent.rebalance()
|
||||
}
|
||||
|
||||
@@ -525,20 +465,20 @@ func (n *node) dereference() {
|
||||
key := make([]byte, len(n.key))
|
||||
copy(key, n.key)
|
||||
n.key = key
|
||||
_assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
|
||||
common.Assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
|
||||
}
|
||||
|
||||
for i := range n.inodes {
|
||||
inode := &n.inodes[i]
|
||||
|
||||
key := make([]byte, len(inode.key))
|
||||
copy(key, inode.key)
|
||||
inode.key = key
|
||||
_assert(len(inode.key) > 0, "dereference: zero-length inode key")
|
||||
key := make([]byte, len(inode.Key()))
|
||||
copy(key, inode.Key())
|
||||
inode.SetKey(key)
|
||||
common.Assert(len(inode.Key()) > 0, "dereference: zero-length inode key")
|
||||
|
||||
value := make([]byte, len(inode.value))
|
||||
copy(value, inode.value)
|
||||
inode.value = value
|
||||
value := make([]byte, len(inode.Value()))
|
||||
copy(value, inode.Value())
|
||||
inode.SetValue(value)
|
||||
}
|
||||
|
||||
// Recursively dereference children.
|
||||
@@ -553,7 +493,7 @@ func (n *node) dereference() {
|
||||
// free adds the node's underlying page to the freelist.
|
||||
func (n *node) free() {
|
||||
if n.pgid != 0 {
|
||||
n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid))
|
||||
n.bucket.tx.db.freelist.Free(n.bucket.tx.meta.Txid(), n.bucket.tx.page(n.pgid))
|
||||
n.pgid = 0
|
||||
}
|
||||
}
|
||||
@@ -594,17 +534,5 @@ type nodes []*node
|
||||
func (s nodes) Len() int { return len(s) }
|
||||
func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s nodes) Less(i, j int) bool {
|
||||
return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1
|
||||
return bytes.Compare(s[i].inodes[0].Key(), s[j].inodes[0].Key()) == -1
|
||||
}
|
||||
|
||||
// inode represents an internal node inside of a node.
|
||||
// It can be used to point to elements in a page or point
|
||||
// to an element which hasn't been added to a page yet.
|
||||
type inode struct {
|
||||
flags uint32
|
||||
pgid pgid
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
type inodes []inode
|
||||
|
||||
212
vendor/go.etcd.io/bbolt/page.go
generated
vendored
212
vendor/go.etcd.io/bbolt/page.go
generated
vendored
@@ -1,212 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const pageHeaderSize = unsafe.Sizeof(page{})
|
||||
|
||||
const minKeysPerPage = 2
|
||||
|
||||
const branchPageElementSize = unsafe.Sizeof(branchPageElement{})
|
||||
const leafPageElementSize = unsafe.Sizeof(leafPageElement{})
|
||||
|
||||
const (
|
||||
branchPageFlag = 0x01
|
||||
leafPageFlag = 0x02
|
||||
metaPageFlag = 0x04
|
||||
freelistPageFlag = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
bucketLeafFlag = 0x01
|
||||
)
|
||||
|
||||
type pgid uint64
|
||||
|
||||
type page struct {
|
||||
id pgid
|
||||
flags uint16
|
||||
count uint16
|
||||
overflow uint32
|
||||
}
|
||||
|
||||
// typ returns a human readable page type string used for debugging.
|
||||
func (p *page) typ() string {
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
return "branch"
|
||||
} else if (p.flags & leafPageFlag) != 0 {
|
||||
return "leaf"
|
||||
} else if (p.flags & metaPageFlag) != 0 {
|
||||
return "meta"
|
||||
} else if (p.flags & freelistPageFlag) != 0 {
|
||||
return "freelist"
|
||||
}
|
||||
return fmt.Sprintf("unknown<%02x>", p.flags)
|
||||
}
|
||||
|
||||
// meta returns a pointer to the metadata section of the page.
|
||||
func (p *page) meta() *meta {
|
||||
return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
|
||||
}
|
||||
|
||||
func (p *page) fastCheck(id pgid) {
|
||||
_assert(p.id == id, "Page expected to be: %v, but self identifies as %v", id, p.id)
|
||||
// Only one flag of page-type can be set.
|
||||
_assert(p.flags == branchPageFlag ||
|
||||
p.flags == leafPageFlag ||
|
||||
p.flags == metaPageFlag ||
|
||||
p.flags == freelistPageFlag,
|
||||
"page %v: has unexpected type/flags: %x", p.id, p.flags)
|
||||
}
|
||||
|
||||
// leafPageElement retrieves the leaf node by index
|
||||
func (p *page) leafPageElement(index uint16) *leafPageElement {
|
||||
return (*leafPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
leafPageElementSize, int(index)))
|
||||
}
|
||||
|
||||
// leafPageElements retrieves a list of leaf nodes.
|
||||
func (p *page) leafPageElements() []leafPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*leafPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// branchPageElement retrieves the branch node by index
|
||||
func (p *page) branchPageElement(index uint16) *branchPageElement {
|
||||
return (*branchPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
|
||||
unsafe.Sizeof(branchPageElement{}), int(index)))
|
||||
}
|
||||
|
||||
// branchPageElements retrieves a list of branch nodes.
|
||||
func (p *page) branchPageElements() []branchPageElement {
|
||||
if p.count == 0 {
|
||||
return nil
|
||||
}
|
||||
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
|
||||
elems := unsafe.Slice((*branchPageElement)(data), int(p.count))
|
||||
return elems
|
||||
}
|
||||
|
||||
// dump writes n bytes of the page to STDERR as hex output.
|
||||
func (p *page) hexdump(n int) {
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, n)
|
||||
fmt.Fprintf(os.Stderr, "%x\n", buf)
|
||||
}
|
||||
|
||||
type pages []*page
|
||||
|
||||
func (s pages) Len() int { return len(s) }
|
||||
func (s pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s pages) Less(i, j int) bool { return s[i].id < s[j].id }
|
||||
|
||||
// branchPageElement represents a node on a branch page.
|
||||
type branchPageElement struct {
|
||||
pos uint32
|
||||
ksize uint32
|
||||
pgid pgid
|
||||
}
|
||||
|
||||
// key returns a byte slice of the node key.
|
||||
func (n *branchPageElement) key() []byte {
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize))
|
||||
}
|
||||
|
||||
// leafPageElement represents a node on a leaf page.
|
||||
type leafPageElement struct {
|
||||
flags uint32
|
||||
pos uint32
|
||||
ksize uint32
|
||||
vsize uint32
|
||||
}
|
||||
|
||||
// key returns a byte slice of the node key.
|
||||
func (n *leafPageElement) key() []byte {
|
||||
i := int(n.pos)
|
||||
j := i + int(n.ksize)
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// value returns a byte slice of the node value.
|
||||
func (n *leafPageElement) value() []byte {
|
||||
i := int(n.pos) + int(n.ksize)
|
||||
j := i + int(n.vsize)
|
||||
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
|
||||
}
|
||||
|
||||
// PageInfo represents human readable information about a page.
|
||||
type PageInfo struct {
|
||||
ID int
|
||||
Type string
|
||||
Count int
|
||||
OverflowCount int
|
||||
}
|
||||
|
||||
type pgids []pgid
|
||||
|
||||
func (s pgids) Len() int { return len(s) }
|
||||
func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||
|
||||
// merge returns the sorted union of a and b.
|
||||
func (a pgids) merge(b pgids) pgids {
|
||||
// Return the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
return b
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return a
|
||||
}
|
||||
merged := make(pgids, len(a)+len(b))
|
||||
mergepgids(merged, a, b)
|
||||
return merged
|
||||
}
|
||||
|
||||
// mergepgids copies the sorted union of a and b into dst.
|
||||
// If dst is too small, it panics.
|
||||
func mergepgids(dst, a, b pgids) {
|
||||
if len(dst) < len(a)+len(b) {
|
||||
panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
|
||||
}
|
||||
// Copy in the opposite slice if one is nil.
|
||||
if len(a) == 0 {
|
||||
copy(dst, b)
|
||||
return
|
||||
}
|
||||
if len(b) == 0 {
|
||||
copy(dst, a)
|
||||
return
|
||||
}
|
||||
|
||||
// Merged will hold all elements from both lists.
|
||||
merged := dst[:0]
|
||||
|
||||
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||
lead, follow := a, b
|
||||
if b[0] < a[0] {
|
||||
lead, follow = b, a
|
||||
}
|
||||
|
||||
// Continue while there are elements in the lead.
|
||||
for len(lead) > 0 {
|
||||
// Merge largest prefix of lead that is ahead of follow[0].
|
||||
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||
merged = append(merged, lead[:n]...)
|
||||
if n >= len(lead) {
|
||||
break
|
||||
}
|
||||
|
||||
// Swap lead and follow.
|
||||
lead, follow = follow, lead[n:]
|
||||
}
|
||||
|
||||
// Append what's left in follow.
|
||||
_ = append(merged, follow...)
|
||||
}
|
||||
238
vendor/go.etcd.io/bbolt/tx.go
generated
vendored
238
vendor/go.etcd.io/bbolt/tx.go
generated
vendored
@@ -5,15 +5,16 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// txid represents the internal transaction identifier.
|
||||
type txid uint64
|
||||
berrors "go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Tx represents a read-only or read/write transaction on the database.
|
||||
// Read-only transactions can be used for retrieving values for keys and creating cursors.
|
||||
@@ -27,9 +28,9 @@ type Tx struct {
|
||||
writable bool
|
||||
managed bool
|
||||
db *DB
|
||||
meta *meta
|
||||
meta *common.Meta
|
||||
root Bucket
|
||||
pages map[pgid]*page
|
||||
pages map[common.Pgid]*common.Page
|
||||
stats TxStats
|
||||
commitHandlers []func()
|
||||
|
||||
@@ -48,24 +49,27 @@ func (tx *Tx) init(db *DB) {
|
||||
tx.pages = nil
|
||||
|
||||
// Copy the meta page since it can be changed by the writer.
|
||||
tx.meta = &meta{}
|
||||
db.meta().copy(tx.meta)
|
||||
tx.meta = &common.Meta{}
|
||||
db.meta().Copy(tx.meta)
|
||||
|
||||
// Copy over the root bucket.
|
||||
tx.root = newBucket(tx)
|
||||
tx.root.bucket = &bucket{}
|
||||
*tx.root.bucket = tx.meta.root
|
||||
tx.root.InBucket = &common.InBucket{}
|
||||
*tx.root.InBucket = *(tx.meta.RootBucket())
|
||||
|
||||
// Increment the transaction id and add a page cache for writable transactions.
|
||||
if tx.writable {
|
||||
tx.pages = make(map[pgid]*page)
|
||||
tx.meta.txid += txid(1)
|
||||
tx.pages = make(map[common.Pgid]*common.Page)
|
||||
tx.meta.IncTxid()
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the transaction id.
|
||||
func (tx *Tx) ID() int {
|
||||
return int(tx.meta.txid)
|
||||
if tx == nil || tx.meta == nil {
|
||||
return -1
|
||||
}
|
||||
return int(tx.meta.Txid())
|
||||
}
|
||||
|
||||
// DB returns a reference to the database that created the transaction.
|
||||
@@ -75,7 +79,7 @@ func (tx *Tx) DB() *DB {
|
||||
|
||||
// Size returns current database size in bytes as seen by this transaction.
|
||||
func (tx *Tx) Size() int64 {
|
||||
return int64(tx.meta.pgid) * int64(tx.db.pageSize)
|
||||
return int64(tx.meta.Pgid()) * int64(tx.db.pageSize)
|
||||
}
|
||||
|
||||
// Writable returns whether the transaction can perform write operations.
|
||||
@@ -96,6 +100,11 @@ func (tx *Tx) Stats() TxStats {
|
||||
return tx.stats
|
||||
}
|
||||
|
||||
// Inspect returns the structure of the database.
|
||||
func (tx *Tx) Inspect() BucketStructure {
|
||||
return tx.root.Inspect()
|
||||
}
|
||||
|
||||
// Bucket retrieves a bucket by name.
|
||||
// Returns nil if the bucket does not exist.
|
||||
// The bucket instance is only valid for the lifetime of the transaction.
|
||||
@@ -123,6 +132,24 @@ func (tx *Tx) DeleteBucket(name []byte) error {
|
||||
return tx.root.DeleteBucket(name)
|
||||
}
|
||||
|
||||
// MoveBucket moves a sub-bucket from the source bucket to the destination bucket.
|
||||
// Returns an error if
|
||||
// 1. the sub-bucket cannot be found in the source bucket;
|
||||
// 2. or the key already exists in the destination bucket;
|
||||
// 3. the key represents a non-bucket value.
|
||||
//
|
||||
// If src is nil, it means moving a top level bucket into the target bucket.
|
||||
// If dst is nil, it means converting the child bucket into a top level bucket.
|
||||
func (tx *Tx) MoveBucket(child []byte, src *Bucket, dst *Bucket) error {
|
||||
if src == nil {
|
||||
src = &tx.root
|
||||
}
|
||||
if dst == nil {
|
||||
dst = &tx.root
|
||||
}
|
||||
return src.MoveBucket(child, dst)
|
||||
}
|
||||
|
||||
// ForEach executes a function for each bucket in the root.
|
||||
// If the provided function returns an error then the iteration is stopped and
|
||||
// the error is returned to the caller.
|
||||
@@ -137,15 +164,28 @@ func (tx *Tx) OnCommit(fn func()) {
|
||||
tx.commitHandlers = append(tx.commitHandlers, fn)
|
||||
}
|
||||
|
||||
// Commit writes all changes to disk and updates the meta page.
|
||||
// Commit writes all changes to disk, updates the meta page and closes the transaction.
|
||||
// Returns an error if a disk write error occurs, or if Commit is
|
||||
// called on a read-only transaction.
|
||||
func (tx *Tx) Commit() error {
|
||||
_assert(!tx.managed, "managed tx commit not allowed")
|
||||
func (tx *Tx) Commit() (err error) {
|
||||
txId := tx.ID()
|
||||
lg := tx.db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Debugf("Committing transaction %d", txId)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Committing transaction failed: %v", err)
|
||||
} else {
|
||||
lg.Debugf("Committing transaction %d successfully", txId)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
common.Assert(!tx.managed, "managed tx commit not allowed")
|
||||
if tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return berrors.ErrTxClosed
|
||||
} else if !tx.writable {
|
||||
return ErrTxNotWritable
|
||||
return berrors.ErrTxNotWritable
|
||||
}
|
||||
|
||||
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
|
||||
@@ -157,40 +197,43 @@ func (tx *Tx) Commit() error {
|
||||
tx.stats.IncRebalanceTime(time.Since(startTime))
|
||||
}
|
||||
|
||||
opgid := tx.meta.pgid
|
||||
opgid := tx.meta.Pgid()
|
||||
|
||||
// spill data onto dirty pages.
|
||||
startTime = time.Now()
|
||||
if err := tx.root.spill(); err != nil {
|
||||
if err = tx.root.spill(); err != nil {
|
||||
lg.Errorf("spilling data onto dirty pages failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
tx.stats.IncSpillTime(time.Since(startTime))
|
||||
|
||||
// Free the old root bucket.
|
||||
tx.meta.root.root = tx.root.root
|
||||
tx.meta.RootBucket().SetRootPage(tx.root.RootPage())
|
||||
|
||||
// Free the old freelist because commit writes out a fresh freelist.
|
||||
if tx.meta.freelist != pgidNoFreelist {
|
||||
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
|
||||
if tx.meta.Freelist() != common.PgidNoFreelist {
|
||||
tx.db.freelist.Free(tx.meta.Txid(), tx.db.page(tx.meta.Freelist()))
|
||||
}
|
||||
|
||||
if !tx.db.NoFreelistSync {
|
||||
err := tx.commitFreelist()
|
||||
err = tx.commitFreelist()
|
||||
if err != nil {
|
||||
lg.Errorf("committing freelist failed: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
tx.meta.freelist = pgidNoFreelist
|
||||
tx.meta.SetFreelist(common.PgidNoFreelist)
|
||||
}
|
||||
|
||||
// If the high water mark has moved up then attempt to grow the database.
|
||||
if tx.meta.pgid > opgid {
|
||||
if tx.meta.Pgid() > opgid {
|
||||
_ = errors.New("")
|
||||
// gofail: var lackOfDiskSpace string
|
||||
// tx.rollback()
|
||||
// return errors.New(lackOfDiskSpace)
|
||||
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
|
||||
if err = tx.db.grow(int(tx.meta.Pgid()+1) * tx.db.pageSize); err != nil {
|
||||
lg.Errorf("growing db size failed, pgid: %d, pagesize: %d, error: %v", tx.meta.Pgid(), tx.db.pageSize, err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -198,7 +241,8 @@ func (tx *Tx) Commit() error {
|
||||
|
||||
// Write dirty pages to disk.
|
||||
startTime = time.Now()
|
||||
if err := tx.write(); err != nil {
|
||||
if err = tx.write(); err != nil {
|
||||
lg.Errorf("writing data failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -208,11 +252,11 @@ func (tx *Tx) Commit() error {
|
||||
ch := tx.Check()
|
||||
var errs []string
|
||||
for {
|
||||
err, ok := <-ch
|
||||
chkErr, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
errs = append(errs, chkErr.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
panic("check fail: " + strings.Join(errs, "\n"))
|
||||
@@ -220,7 +264,8 @@ func (tx *Tx) Commit() error {
|
||||
}
|
||||
|
||||
// Write meta to disk.
|
||||
if err := tx.writeMeta(); err != nil {
|
||||
if err = tx.writeMeta(); err != nil {
|
||||
lg.Errorf("writeMeta failed: %v", err)
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
@@ -240,16 +285,14 @@ func (tx *Tx) Commit() error {
|
||||
func (tx *Tx) commitFreelist() error {
|
||||
// Allocate new pages for the new free list. This will overestimate
|
||||
// the size of the freelist but not underestimate the size (which would be bad).
|
||||
p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1)
|
||||
p, err := tx.allocate((tx.db.freelist.EstimatedWritePageSize() / tx.db.pageSize) + 1)
|
||||
if err != nil {
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
if err := tx.db.freelist.write(p); err != nil {
|
||||
tx.rollback()
|
||||
return err
|
||||
}
|
||||
tx.meta.freelist = p.id
|
||||
|
||||
tx.db.freelist.Write(p)
|
||||
tx.meta.SetFreelist(p.Id())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -257,9 +300,9 @@ func (tx *Tx) commitFreelist() error {
|
||||
// Rollback closes the transaction and ignores all previous updates. Read-only
|
||||
// transactions must be rolled back and not committed.
|
||||
func (tx *Tx) Rollback() error {
|
||||
_assert(!tx.managed, "managed tx rollback not allowed")
|
||||
common.Assert(!tx.managed, "managed tx rollback not allowed")
|
||||
if tx.db == nil {
|
||||
return ErrTxClosed
|
||||
return berrors.ErrTxClosed
|
||||
}
|
||||
tx.nonPhysicalRollback()
|
||||
return nil
|
||||
@@ -271,7 +314,7 @@ func (tx *Tx) nonPhysicalRollback() {
|
||||
return
|
||||
}
|
||||
if tx.writable {
|
||||
tx.db.freelist.rollback(tx.meta.txid)
|
||||
tx.db.freelist.Rollback(tx.meta.Txid())
|
||||
}
|
||||
tx.close()
|
||||
}
|
||||
@@ -282,17 +325,17 @@ func (tx *Tx) rollback() {
|
||||
return
|
||||
}
|
||||
if tx.writable {
|
||||
tx.db.freelist.rollback(tx.meta.txid)
|
||||
tx.db.freelist.Rollback(tx.meta.Txid())
|
||||
// When mmap fails, the `data`, `dataref` and `datasz` may be reset to
|
||||
// zero values, and there is no way to reload free page IDs in this case.
|
||||
if tx.db.data != nil {
|
||||
if !tx.db.hasSyncedFreelist() {
|
||||
// Reconstruct free page list by scanning the DB to get the whole free page list.
|
||||
// Note: scaning the whole db is heavy if your db size is large in NoSyncFreeList mode.
|
||||
tx.db.freelist.noSyncReload(tx.db.freepages())
|
||||
// Note: scanning the whole db is heavy if your db size is large in NoSyncFreeList mode.
|
||||
tx.db.freelist.NoSyncReload(tx.db.freepages())
|
||||
} else {
|
||||
// Read free page list from freelist page.
|
||||
tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist))
|
||||
tx.db.freelist.Reload(tx.db.page(tx.db.meta().Freelist()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,9 +348,9 @@ func (tx *Tx) close() {
|
||||
}
|
||||
if tx.writable {
|
||||
// Grab freelist stats.
|
||||
var freelistFreeN = tx.db.freelist.free_count()
|
||||
var freelistPendingN = tx.db.freelist.pending_count()
|
||||
var freelistAlloc = tx.db.freelist.size()
|
||||
var freelistFreeN = tx.db.freelist.FreeCount()
|
||||
var freelistPendingN = tx.db.freelist.PendingCount()
|
||||
var freelistAlloc = tx.db.freelist.EstimatedWritePageSize()
|
||||
|
||||
// Remove transaction ref & writer lock.
|
||||
tx.db.rwtx = nil
|
||||
@@ -335,7 +378,7 @@ func (tx *Tx) close() {
|
||||
// Copy writes the entire database to a writer.
|
||||
// This function exists for backwards compatibility.
|
||||
//
|
||||
// Deprecated; Use WriteTo() instead.
|
||||
// Deprecated: Use WriteTo() instead.
|
||||
func (tx *Tx) Copy(w io.Writer) error {
|
||||
_, err := tx.WriteTo(w)
|
||||
return err
|
||||
@@ -357,13 +400,13 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
// Generate a meta page. We use the same page data for both meta pages.
|
||||
buf := make([]byte, tx.db.pageSize)
|
||||
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||
page.flags = metaPageFlag
|
||||
*page.meta() = *tx.meta
|
||||
page := (*common.Page)(unsafe.Pointer(&buf[0]))
|
||||
page.SetFlags(common.MetaPageFlag)
|
||||
*page.Meta() = *tx.meta
|
||||
|
||||
// Write meta 0.
|
||||
page.id = 0
|
||||
page.meta().checksum = page.meta().sum64()
|
||||
page.SetId(0)
|
||||
page.Meta().SetChecksum(page.Meta().Sum64())
|
||||
nn, err := w.Write(buf)
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
@@ -371,9 +414,9 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}
|
||||
|
||||
// Write meta 1 with a lower transaction id.
|
||||
page.id = 1
|
||||
page.meta().txid -= 1
|
||||
page.meta().checksum = page.meta().sum64()
|
||||
page.SetId(1)
|
||||
page.Meta().DecTxid()
|
||||
page.Meta().SetChecksum(page.Meta().Sum64())
|
||||
nn, err = w.Write(buf)
|
||||
n += int64(nn)
|
||||
if err != nil {
|
||||
@@ -413,14 +456,16 @@ func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
|
||||
}
|
||||
|
||||
// allocate returns a contiguous block of memory starting at a given page.
|
||||
func (tx *Tx) allocate(count int) (*page, error) {
|
||||
p, err := tx.db.allocate(tx.meta.txid, count)
|
||||
func (tx *Tx) allocate(count int) (*common.Page, error) {
|
||||
lg := tx.db.Logger()
|
||||
p, err := tx.db.allocate(tx.meta.Txid(), count)
|
||||
if err != nil {
|
||||
lg.Errorf("allocating failed, txid: %d, count: %d, error: %v", tx.meta.Txid(), count, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save to our page cache.
|
||||
tx.pages[p.id] = p
|
||||
tx.pages[p.Id()] = p
|
||||
|
||||
// Update statistics.
|
||||
tx.stats.IncPageCount(int64(count))
|
||||
@@ -432,29 +477,31 @@ func (tx *Tx) allocate(count int) (*page, error) {
|
||||
// write writes any dirty pages to disk.
|
||||
func (tx *Tx) write() error {
|
||||
// Sort pages by id.
|
||||
pages := make(pages, 0, len(tx.pages))
|
||||
lg := tx.db.Logger()
|
||||
pages := make(common.Pages, 0, len(tx.pages))
|
||||
for _, p := range tx.pages {
|
||||
pages = append(pages, p)
|
||||
}
|
||||
// Clear out page cache early.
|
||||
tx.pages = make(map[pgid]*page)
|
||||
tx.pages = make(map[common.Pgid]*common.Page)
|
||||
sort.Sort(pages)
|
||||
|
||||
// Write pages to disk in order.
|
||||
for _, p := range pages {
|
||||
rem := (uint64(p.overflow) + 1) * uint64(tx.db.pageSize)
|
||||
offset := int64(p.id) * int64(tx.db.pageSize)
|
||||
rem := (uint64(p.Overflow()) + 1) * uint64(tx.db.pageSize)
|
||||
offset := int64(p.Id()) * int64(tx.db.pageSize)
|
||||
var written uintptr
|
||||
|
||||
// Write out page in "max allocation" sized chunks.
|
||||
for {
|
||||
sz := rem
|
||||
if sz > maxAllocSize-1 {
|
||||
sz = maxAllocSize - 1
|
||||
if sz > common.MaxAllocSize-1 {
|
||||
sz = common.MaxAllocSize - 1
|
||||
}
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz))
|
||||
buf := common.UnsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz))
|
||||
|
||||
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
|
||||
lg.Errorf("writeAt failed, offset: %d: %w", offset, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -474,9 +521,10 @@ func (tx *Tx) write() error {
|
||||
}
|
||||
|
||||
// Ignore file sync if flag is set on DB.
|
||||
if !tx.db.NoSync || IgnoreNoSync {
|
||||
if !tx.db.NoSync || common.IgnoreNoSync {
|
||||
// gofail: var beforeSyncDataPages struct{}
|
||||
if err := fdatasync(tx.db); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -485,11 +533,11 @@ func (tx *Tx) write() error {
|
||||
for _, p := range pages {
|
||||
// Ignore page sizes over 1 page.
|
||||
// These are allocated using make() instead of the page pool.
|
||||
if int(p.overflow) != 0 {
|
||||
if int(p.Overflow()) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize)
|
||||
buf := common.UnsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize)
|
||||
|
||||
// See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1
|
||||
for i := range buf {
|
||||
@@ -503,18 +551,24 @@ func (tx *Tx) write() error {
|
||||
|
||||
// writeMeta writes the meta to the disk.
|
||||
func (tx *Tx) writeMeta() error {
|
||||
// gofail: var beforeWriteMetaError string
|
||||
// return errors.New(beforeWriteMetaError)
|
||||
|
||||
// Create a temporary buffer for the meta page.
|
||||
lg := tx.db.Logger()
|
||||
buf := make([]byte, tx.db.pageSize)
|
||||
p := tx.db.pageInBuffer(buf, 0)
|
||||
tx.meta.write(p)
|
||||
tx.meta.Write(p)
|
||||
|
||||
// Write the meta page to file.
|
||||
if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil {
|
||||
if _, err := tx.db.ops.writeAt(buf, int64(p.Id())*int64(tx.db.pageSize)); err != nil {
|
||||
lg.Errorf("writeAt failed, pgid: %d, pageSize: %d, error: %v", p.Id(), tx.db.pageSize, err)
|
||||
return err
|
||||
}
|
||||
if !tx.db.NoSync || IgnoreNoSync {
|
||||
if !tx.db.NoSync || common.IgnoreNoSync {
|
||||
// gofail: var beforeSyncMetaPage struct{}
|
||||
if err := fdatasync(tx.db); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -527,69 +581,69 @@ func (tx *Tx) writeMeta() error {
|
||||
|
||||
// page returns a reference to the page with a given id.
|
||||
// If page has been written to then a temporary buffered page is returned.
|
||||
func (tx *Tx) page(id pgid) *page {
|
||||
func (tx *Tx) page(id common.Pgid) *common.Page {
|
||||
// Check the dirty pages first.
|
||||
if tx.pages != nil {
|
||||
if p, ok := tx.pages[id]; ok {
|
||||
p.fastCheck(id)
|
||||
p.FastCheck(id)
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return directly from the mmap.
|
||||
p := tx.db.page(id)
|
||||
p.fastCheck(id)
|
||||
p.FastCheck(id)
|
||||
return p
|
||||
}
|
||||
|
||||
// forEachPage iterates over every page within a given page and executes a function.
|
||||
func (tx *Tx) forEachPage(pgidnum pgid, fn func(*page, int, []pgid)) {
|
||||
stack := make([]pgid, 10)
|
||||
func (tx *Tx) forEachPage(pgidnum common.Pgid, fn func(*common.Page, int, []common.Pgid)) {
|
||||
stack := make([]common.Pgid, 10)
|
||||
stack[0] = pgidnum
|
||||
tx.forEachPageInternal(stack[:1], fn)
|
||||
}
|
||||
|
||||
func (tx *Tx) forEachPageInternal(pgidstack []pgid, fn func(*page, int, []pgid)) {
|
||||
func (tx *Tx) forEachPageInternal(pgidstack []common.Pgid, fn func(*common.Page, int, []common.Pgid)) {
|
||||
p := tx.page(pgidstack[len(pgidstack)-1])
|
||||
|
||||
// Execute function.
|
||||
fn(p, len(pgidstack)-1, pgidstack)
|
||||
|
||||
// Recursively loop over children.
|
||||
if (p.flags & branchPageFlag) != 0 {
|
||||
for i := 0; i < int(p.count); i++ {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
tx.forEachPageInternal(append(pgidstack, elem.pgid), fn)
|
||||
if p.IsBranchPage() {
|
||||
for i := 0; i < int(p.Count()); i++ {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
tx.forEachPageInternal(append(pgidstack, elem.Pgid()), fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page returns page information for a given page number.
|
||||
// This is only safe for concurrent use when used by a writable transaction.
|
||||
func (tx *Tx) Page(id int) (*PageInfo, error) {
|
||||
func (tx *Tx) Page(id int) (*common.PageInfo, error) {
|
||||
if tx.db == nil {
|
||||
return nil, ErrTxClosed
|
||||
} else if pgid(id) >= tx.meta.pgid {
|
||||
return nil, berrors.ErrTxClosed
|
||||
} else if common.Pgid(id) >= tx.meta.Pgid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if tx.db.freelist == nil {
|
||||
return nil, ErrFreePagesNotLoaded
|
||||
return nil, berrors.ErrFreePagesNotLoaded
|
||||
}
|
||||
|
||||
// Build the page info.
|
||||
p := tx.db.page(pgid(id))
|
||||
info := &PageInfo{
|
||||
p := tx.db.page(common.Pgid(id))
|
||||
info := &common.PageInfo{
|
||||
ID: id,
|
||||
Count: int(p.count),
|
||||
OverflowCount: int(p.overflow),
|
||||
Count: int(p.Count()),
|
||||
OverflowCount: int(p.Overflow()),
|
||||
}
|
||||
|
||||
// Determine the type (or if it's free).
|
||||
if tx.db.freelist.freed(pgid(id)) {
|
||||
if tx.db.freelist.Freed(common.Pgid(id)) {
|
||||
info.Type = "free"
|
||||
} else {
|
||||
info.Type = p.typ()
|
||||
info.Type = p.Typ()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
|
||||
204
vendor/go.etcd.io/bbolt/tx_check.go
generated
vendored
204
vendor/go.etcd.io/bbolt/tx_check.go
generated
vendored
@@ -3,6 +3,8 @@ package bbolt
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
)
|
||||
|
||||
// Check performs several consistency checks on the database for this transaction.
|
||||
@@ -13,13 +15,10 @@ import (
|
||||
// because of caching. This overhead can be removed if running on a read-only
|
||||
// transaction, however, it is not safe to execute other writer transactions at
|
||||
// the same time.
|
||||
func (tx *Tx) Check() <-chan error {
|
||||
return tx.CheckWithOptions()
|
||||
}
|
||||
|
||||
// CheckWithOptions allows users to provide a customized `KVStringer` implementation,
|
||||
//
|
||||
// It also allows users to provide a customized `KVStringer` implementation,
|
||||
// so that bolt can generate human-readable diagnostic messages.
|
||||
func (tx *Tx) CheckWithOptions(options ...CheckOption) <-chan error {
|
||||
func (tx *Tx) Check(options ...CheckOption) <-chan error {
|
||||
chkConfig := checkConfig{
|
||||
kvStringer: HexKVStringer(),
|
||||
}
|
||||
@@ -28,18 +27,22 @@ func (tx *Tx) CheckWithOptions(options ...CheckOption) <-chan error {
|
||||
}
|
||||
|
||||
ch := make(chan error)
|
||||
go tx.check(chkConfig.kvStringer, ch)
|
||||
go func() {
|
||||
// Close the channel to signal completion.
|
||||
defer close(ch)
|
||||
tx.check(chkConfig, ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (tx *Tx) check(kvStringer KVStringer, ch chan error) {
|
||||
func (tx *Tx) check(cfg checkConfig, ch chan error) {
|
||||
// Force loading free list if opened in ReadOnly mode.
|
||||
tx.db.loadFreelist()
|
||||
|
||||
// Check if any pages are double freed.
|
||||
freed := make(map[pgid]bool)
|
||||
all := make([]pgid, tx.db.freelist.count())
|
||||
tx.db.freelist.copyall(all)
|
||||
freed := make(map[common.Pgid]bool)
|
||||
all := make([]common.Pgid, tx.db.freelist.Count())
|
||||
tx.db.freelist.Copyall(all)
|
||||
for _, id := range all {
|
||||
if freed[id] {
|
||||
ch <- fmt.Errorf("page %d: already freed", id)
|
||||
@@ -48,46 +51,110 @@ func (tx *Tx) check(kvStringer KVStringer, ch chan error) {
|
||||
}
|
||||
|
||||
// Track every reachable page.
|
||||
reachable := make(map[pgid]*page)
|
||||
reachable := make(map[common.Pgid]*common.Page)
|
||||
reachable[0] = tx.page(0) // meta0
|
||||
reachable[1] = tx.page(1) // meta1
|
||||
if tx.meta.freelist != pgidNoFreelist {
|
||||
for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ {
|
||||
reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist)
|
||||
if tx.meta.Freelist() != common.PgidNoFreelist {
|
||||
for i := uint32(0); i <= tx.page(tx.meta.Freelist()).Overflow(); i++ {
|
||||
reachable[tx.meta.Freelist()+common.Pgid(i)] = tx.page(tx.meta.Freelist())
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check buckets.
|
||||
tx.checkBucket(&tx.root, reachable, freed, kvStringer, ch)
|
||||
if cfg.pageId == 0 {
|
||||
// Check the whole db file, starting from the root bucket and
|
||||
// recursively check all child buckets.
|
||||
tx.recursivelyCheckBucket(&tx.root, reachable, freed, cfg.kvStringer, ch)
|
||||
|
||||
// Ensure all pages below high water mark are either reachable or freed.
|
||||
for i := pgid(0); i < tx.meta.pgid; i++ {
|
||||
for i := common.Pgid(0); i < tx.meta.Pgid(); i++ {
|
||||
_, isReachable := reachable[i]
|
||||
if !isReachable && !freed[i] {
|
||||
ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
|
||||
}
|
||||
}
|
||||
|
||||
// Close the channel to signal completion.
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
// Ignore inline buckets.
|
||||
if b.root == 0 {
|
||||
} else {
|
||||
// Check the db file starting from a specified pageId.
|
||||
if cfg.pageId < 2 || cfg.pageId >= uint64(tx.meta.Pgid()) {
|
||||
ch <- fmt.Errorf("page ID (%d) out of range [%d, %d)", cfg.pageId, 2, tx.meta.Pgid())
|
||||
return
|
||||
}
|
||||
|
||||
// Check every page used by this bucket.
|
||||
b.tx.forEachPage(b.root, func(p *page, _ int, stack []pgid) {
|
||||
if p.id > tx.meta.pgid {
|
||||
ch <- fmt.Errorf("page %d: out of bounds: %d (stack: %v)", int(p.id), int(b.tx.meta.pgid), stack)
|
||||
tx.recursivelyCheckPage(common.Pgid(cfg.pageId), reachable, freed, cfg.kvStringer, ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) recursivelyCheckPage(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
tx.checkInvariantProperties(pageId, reachable, freed, kvStringer, ch)
|
||||
tx.recursivelyCheckBucketInPage(pageId, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
|
||||
func (tx *Tx) recursivelyCheckBucketInPage(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
p := tx.page(pageId)
|
||||
|
||||
switch {
|
||||
case p.IsBranchPage():
|
||||
for i := range p.BranchPageElements() {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
tx.recursivelyCheckBucketInPage(elem.Pgid(), reachable, freed, kvStringer, ch)
|
||||
}
|
||||
case p.IsLeafPage():
|
||||
for i := range p.LeafPageElements() {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
if elem.IsBucketEntry() {
|
||||
inBkt := common.NewInBucket(pageId, 0)
|
||||
tmpBucket := Bucket{
|
||||
InBucket: &inBkt,
|
||||
rootNode: &node{isLeaf: p.IsLeafPage()},
|
||||
FillPercent: DefaultFillPercent,
|
||||
tx: tx,
|
||||
}
|
||||
if child := tmpBucket.Bucket(elem.Key()); child != nil {
|
||||
tx.recursivelyCheckBucket(child, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
ch <- fmt.Errorf("unexpected page type (flags: %x) for pgId:%d", p.Flags(), pageId)
|
||||
}
|
||||
}
|
||||
|
||||
func (tx *Tx) recursivelyCheckBucket(b *Bucket, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
// Ignore inline buckets.
|
||||
if b.RootPage() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tx.checkInvariantProperties(b.RootPage(), reachable, freed, kvStringer, ch)
|
||||
|
||||
// Check each bucket within this bucket.
|
||||
_ = b.ForEachBucket(func(k []byte) error {
|
||||
if child := b.Bucket(k); child != nil {
|
||||
tx.recursivelyCheckBucket(child, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (tx *Tx) checkInvariantProperties(pageId common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool,
|
||||
kvStringer KVStringer, ch chan error) {
|
||||
tx.forEachPage(pageId, func(p *common.Page, _ int, stack []common.Pgid) {
|
||||
verifyPageReachable(p, tx.meta.Pgid(), stack, reachable, freed, ch)
|
||||
})
|
||||
|
||||
tx.recursivelyCheckPageKeyOrder(pageId, kvStringer.KeyToString, ch)
|
||||
}
|
||||
|
||||
func verifyPageReachable(p *common.Page, hwm common.Pgid, stack []common.Pgid, reachable map[common.Pgid]*common.Page, freed map[common.Pgid]bool, ch chan error) {
|
||||
if p.Id() > hwm {
|
||||
ch <- fmt.Errorf("page %d: out of bounds: %d (stack: %v)", int(p.Id()), int(hwm), stack)
|
||||
}
|
||||
|
||||
// Ensure each page is only referenced once.
|
||||
for i := pgid(0); i <= pgid(p.overflow); i++ {
|
||||
var id = p.id + i
|
||||
for i := common.Pgid(0); i <= common.Pgid(p.Overflow()); i++ {
|
||||
var id = p.Id() + i
|
||||
if _, ok := reachable[id]; ok {
|
||||
ch <- fmt.Errorf("page %d: multiple references (stack: %v)", int(id), stack)
|
||||
}
|
||||
@@ -95,71 +162,60 @@ func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bo
|
||||
}
|
||||
|
||||
// We should only encounter un-freed leaf and branch pages.
|
||||
if freed[p.id] {
|
||||
ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
|
||||
} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
|
||||
ch <- fmt.Errorf("page %d: invalid type: %s (stack: %v)", int(p.id), p.typ(), stack)
|
||||
if freed[p.Id()] {
|
||||
ch <- fmt.Errorf("page %d: reachable freed", int(p.Id()))
|
||||
} else if !p.IsBranchPage() && !p.IsLeafPage() {
|
||||
ch <- fmt.Errorf("page %d: invalid type: %s (stack: %v)", int(p.Id()), p.Typ(), stack)
|
||||
}
|
||||
})
|
||||
|
||||
tx.recursivelyCheckPages(b.root, kvStringer.KeyToString, ch)
|
||||
|
||||
// Check each bucket within this bucket.
|
||||
_ = b.ForEachBucket(func(k []byte) error {
|
||||
if child := b.Bucket(k); child != nil {
|
||||
tx.checkBucket(child, reachable, freed, kvStringer, ch)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// recursivelyCheckPages confirms database consistency with respect to b-tree
|
||||
// recursivelyCheckPageKeyOrder verifies database consistency with respect to b-tree
|
||||
// key order constraints:
|
||||
// - keys on pages must be sorted
|
||||
// - keys on children pages are between 2 consecutive keys on the parent's branch page).
|
||||
func (tx *Tx) recursivelyCheckPages(pgId pgid, keyToString func([]byte) string, ch chan error) {
|
||||
tx.recursivelyCheckPagesInternal(pgId, nil, nil, nil, keyToString, ch)
|
||||
func (tx *Tx) recursivelyCheckPageKeyOrder(pgId common.Pgid, keyToString func([]byte) string, ch chan error) {
|
||||
tx.recursivelyCheckPageKeyOrderInternal(pgId, nil, nil, nil, keyToString, ch)
|
||||
}
|
||||
|
||||
// recursivelyCheckPagesInternal verifies that all keys in the subtree rooted at `pgid` are:
|
||||
// recursivelyCheckPageKeyOrderInternal verifies that all keys in the subtree rooted at `pgid` are:
|
||||
// - >=`minKeyClosed` (can be nil)
|
||||
// - <`maxKeyOpen` (can be nil)
|
||||
// - Are in right ordering relationship to their parents.
|
||||
// `pagesStack` is expected to contain IDs of pages from the tree root to `pgid` for the clean debugging message.
|
||||
func (tx *Tx) recursivelyCheckPagesInternal(
|
||||
pgId pgid, minKeyClosed, maxKeyOpen []byte, pagesStack []pgid,
|
||||
func (tx *Tx) recursivelyCheckPageKeyOrderInternal(
|
||||
pgId common.Pgid, minKeyClosed, maxKeyOpen []byte, pagesStack []common.Pgid,
|
||||
keyToString func([]byte) string, ch chan error) (maxKeyInSubtree []byte) {
|
||||
|
||||
p := tx.page(pgId)
|
||||
pagesStack = append(pagesStack, pgId)
|
||||
switch {
|
||||
case p.flags&branchPageFlag != 0:
|
||||
case p.IsBranchPage():
|
||||
// For branch page we navigate ranges of all subpages.
|
||||
runningMin := minKeyClosed
|
||||
for i := range p.branchPageElements() {
|
||||
elem := p.branchPageElement(uint16(i))
|
||||
verifyKeyOrder(elem.pgid, "branch", i, elem.key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
for i := range p.BranchPageElements() {
|
||||
elem := p.BranchPageElement(uint16(i))
|
||||
verifyKeyOrder(elem.Pgid(), "branch", i, elem.Key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
|
||||
maxKey := maxKeyOpen
|
||||
if i < len(p.branchPageElements())-1 {
|
||||
maxKey = p.branchPageElement(uint16(i + 1)).key()
|
||||
if i < len(p.BranchPageElements())-1 {
|
||||
maxKey = p.BranchPageElement(uint16(i + 1)).Key()
|
||||
}
|
||||
maxKeyInSubtree = tx.recursivelyCheckPagesInternal(elem.pgid, elem.key(), maxKey, pagesStack, keyToString, ch)
|
||||
maxKeyInSubtree = tx.recursivelyCheckPageKeyOrderInternal(elem.Pgid(), elem.Key(), maxKey, pagesStack, keyToString, ch)
|
||||
runningMin = maxKeyInSubtree
|
||||
}
|
||||
return maxKeyInSubtree
|
||||
case p.flags&leafPageFlag != 0:
|
||||
case p.IsLeafPage():
|
||||
runningMin := minKeyClosed
|
||||
for i := range p.leafPageElements() {
|
||||
elem := p.leafPageElement(uint16(i))
|
||||
verifyKeyOrder(pgId, "leaf", i, elem.key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
runningMin = elem.key()
|
||||
for i := range p.LeafPageElements() {
|
||||
elem := p.LeafPageElement(uint16(i))
|
||||
verifyKeyOrder(pgId, "leaf", i, elem.Key(), runningMin, maxKeyOpen, ch, keyToString, pagesStack)
|
||||
runningMin = elem.Key()
|
||||
}
|
||||
if p.count > 0 {
|
||||
return p.leafPageElement(p.count - 1).key()
|
||||
if p.Count() > 0 {
|
||||
return p.LeafPageElement(p.Count() - 1).Key()
|
||||
}
|
||||
default:
|
||||
ch <- fmt.Errorf("unexpected page type for pgId:%d", pgId)
|
||||
ch <- fmt.Errorf("unexpected page type (flags: %x) for pgId:%d", p.Flags(), pgId)
|
||||
}
|
||||
return maxKeyInSubtree
|
||||
}
|
||||
@@ -168,7 +224,7 @@ func (tx *Tx) recursivelyCheckPagesInternal(
|
||||
* verifyKeyOrder checks whether an entry with given #index on pgId (pageType: "branch|leaf") that has given "key",
|
||||
* is within range determined by (previousKey..maxKeyOpen) and reports found violations to the channel (ch).
|
||||
*/
|
||||
func verifyKeyOrder(pgId pgid, pageType string, index int, key []byte, previousKey []byte, maxKeyOpen []byte, ch chan error, keyToString func([]byte) string, pagesStack []pgid) {
|
||||
func verifyKeyOrder(pgId common.Pgid, pageType string, index int, key []byte, previousKey []byte, maxKeyOpen []byte, ch chan error, keyToString func([]byte) string, pagesStack []common.Pgid) {
|
||||
if index == 0 && previousKey != nil && compareKeys(previousKey, key) > 0 {
|
||||
ch <- fmt.Errorf("the first key[%d]=(hex)%s on %s page(%d) needs to be >= the key in the ancestor (%s). Stack: %v",
|
||||
index, keyToString(key), pageType, pgId, keyToString(previousKey), pagesStack)
|
||||
@@ -194,6 +250,7 @@ func verifyKeyOrder(pgId pgid, pageType string, index int, key []byte, previousK
|
||||
|
||||
type checkConfig struct {
|
||||
kvStringer KVStringer
|
||||
pageId uint64
|
||||
}
|
||||
|
||||
type CheckOption func(options *checkConfig)
|
||||
@@ -204,6 +261,13 @@ func WithKVStringer(kvStringer KVStringer) CheckOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPageId sets a page ID from which the check command starts to check
|
||||
func WithPageId(pageId uint64) CheckOption {
|
||||
return func(c *checkConfig) {
|
||||
c.pageId = pageId
|
||||
}
|
||||
}
|
||||
|
||||
// KVStringer allows to prepare human-readable diagnostic messages.
|
||||
type KVStringer interface {
|
||||
KeyToString([]byte) string
|
||||
|
||||
9
vendor/modules.txt
vendored
9
vendor/modules.txt
vendored
@@ -442,7 +442,7 @@ github.com/russross/blackfriday/v2
|
||||
# github.com/sirupsen/logrus v1.9.3
|
||||
## explicit; go 1.13
|
||||
github.com/sirupsen/logrus
|
||||
# github.com/spf13/pflag v1.0.5
|
||||
# github.com/spf13/pflag v1.0.6
|
||||
## explicit; go 1.12
|
||||
github.com/spf13/pflag
|
||||
# github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf
|
||||
@@ -468,9 +468,12 @@ github.com/vanng822/go-premailer/premailer
|
||||
# github.com/x448/float16 v0.8.4
|
||||
## explicit; go 1.11
|
||||
github.com/x448/float16
|
||||
# go.etcd.io/bbolt v1.3.11
|
||||
## explicit; go 1.22
|
||||
# go.etcd.io/bbolt v1.4.1
|
||||
## explicit; go 1.23
|
||||
go.etcd.io/bbolt
|
||||
go.etcd.io/bbolt/errors
|
||||
go.etcd.io/bbolt/internal/common
|
||||
go.etcd.io/bbolt/internal/freelist
|
||||
# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0
|
||||
## explicit; go 1.21
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
|
||||
|
||||
Reference in New Issue
Block a user