Merge pull request #2634 from jhowardmsft/boltdb

Move to supported boltdb
This commit is contained in:
Michael Crosby 2018-09-13 20:42:00 -04:00 committed by GitHub
commit c95bb88fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 610 additions and 327 deletions

View File

@ -19,8 +19,8 @@ package metadata
import ( import (
"context" "context"
"github.com/boltdb/bolt"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type transactionKey struct{} type transactionKey struct{}

View File

@ -19,8 +19,8 @@ package boltutil
import ( import (
"time" "time"
"github.com/boltdb/bolt"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
var ( var (

View File

@ -17,8 +17,8 @@
package metadata package metadata
import ( import (
"github.com/boltdb/bolt"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
bolt "go.etcd.io/bbolt"
) )
// The layout where a "/" delineates a bucket is described in the following // The layout where a "/" delineates a bucket is described in the following

View File

@ -21,7 +21,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
@ -32,6 +31,7 @@ import (
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types" "github.com/gogo/protobuf/types"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type containerStore struct { type containerStore struct {

View File

@ -27,7 +27,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
@ -36,6 +35,7 @@ import (
"github.com/gogo/protobuf/types" "github.com/gogo/protobuf/types"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
func init() { func init() {

View File

@ -23,7 +23,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
@ -34,6 +33,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type contentStore struct { type contentStore struct {

View File

@ -24,7 +24,6 @@ import (
"sync/atomic" "sync/atomic"
"testing" "testing"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local" "github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/content/testsuite" "github.com/containerd/containerd/content/testsuite"
@ -34,6 +33,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
func createContentStore(ctx context.Context, root string) (context.Context, content.Store, func() error, error) { func createContentStore(ctx context.Context, root string) (context.Context, content.Store, func() error, error) {

View File

@ -23,12 +23,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/gc" "github.com/containerd/containerd/gc"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
const ( const (
@ -43,7 +43,7 @@ const (
// dbVersion represents updates to the schema // dbVersion represents updates to the schema
// version which are additions and compatible with // version which are additions and compatible with
// prior version of the same schema. // prior version of the same schema.
dbVersion = 2 dbVersion = 3
) )
// DB represents a metadata database backed by a bolt // DB represents a metadata database backed by a bolt

View File

@ -30,7 +30,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local" "github.com/containerd/containerd/content/local"
@ -44,6 +43,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
func testDB(t *testing.T) (context.Context, *DB, func()) { func testDB(t *testing.T) (context.Context, *DB, func()) {
@ -265,6 +265,16 @@ func TestMigrations(t *testing.T) {
return nil return nil
}, },
}, },
{
name: "NoOp",
init: func(tx *bolt.Tx) error {
return nil
},
check: func(tx *bolt.Tx) error {
return nil
},
},
} }
if len(migrationTests) != len(migrations) { if len(migrationTests) != len(migrations) {

View File

@ -23,10 +23,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/gc" "github.com/containerd/containerd/gc"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
const ( const (

View File

@ -27,10 +27,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/gc" "github.com/containerd/containerd/gc"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
bolt "go.etcd.io/bbolt"
) )
func TestGCRoots(t *testing.T) { func TestGCRoots(t *testing.T) {

View File

@ -23,7 +23,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
@ -33,6 +32,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type imageStore struct { type imageStore struct {

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
@ -28,6 +27,7 @@ import (
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
// LeaseManager manages the create/delete lifecyle of leases // LeaseManager manages the create/delete lifecyle of leases

View File

@ -19,10 +19,10 @@ package metadata
import ( import (
"testing" "testing"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
func TestLeases(t *testing.T) { func TestLeases(t *testing.T) {

View File

@ -16,7 +16,7 @@
package metadata package metadata
import "github.com/boltdb/bolt" import bolt "go.etcd.io/bbolt"
type migration struct { type migration struct {
schema string schema string
@ -45,6 +45,11 @@ var migrations = []migration{
version: 2, version: 2,
migrate: migrateIngests, migrate: migrateIngests,
}, },
{
schema: "v1",
version: 3,
migrate: noOpMigration,
},
} }
// addChildLinks Adds children key to the snapshotters to enforce snapshot // addChildLinks Adds children key to the snapshotters to enforce snapshot
@ -154,3 +159,10 @@ func migrateIngests(tx *bolt.Tx) error {
return nil return nil
} }
// noOpMigration was for a database change from boltdb/bolt which is no
// longer being supported, to go.etcd.io/bbolt which is the currently
// maintained repo for boltdb.
func noOpMigration(tx *bolt.Tx) error {
return nil
}

View File

@ -19,11 +19,11 @@ package metadata
import ( import (
"context" "context"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
l "github.com/containerd/containerd/labels" l "github.com/containerd/containerd/labels"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type namespaceStore struct { type namespaceStore struct {

View File

@ -23,7 +23,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/labels" "github.com/containerd/containerd/labels"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
@ -32,6 +31,7 @@ import (
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
type snapshotter struct { type snapshotter struct {

View File

@ -23,11 +23,11 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/containerd/containerd/snapshots/native" "github.com/containerd/containerd/snapshots/native"
"github.com/containerd/containerd/snapshots/testsuite" "github.com/containerd/containerd/snapshots/testsuite"
bolt "go.etcd.io/bbolt"
) )
func newTestSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { func newTestSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {

View File

@ -26,7 +26,6 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/boltdb/bolt"
eventstypes "github.com/containerd/containerd/api/events" eventstypes "github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/api/types" "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
@ -49,6 +48,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )

View File

@ -23,7 +23,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/events/exchange" "github.com/containerd/containerd/events/exchange"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
@ -32,6 +31,7 @@ import (
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/runtime" "github.com/containerd/containerd/runtime"
bolt "go.etcd.io/bbolt"
) )
func init() { func init() {

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"io" "io"
"github.com/boltdb/bolt"
eventstypes "github.com/containerd/containerd/api/events" eventstypes "github.com/containerd/containerd/api/events"
api "github.com/containerd/containerd/api/services/containers/v1" api "github.com/containerd/containerd/api/services/containers/v1"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
@ -30,6 +29,7 @@ import (
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
grpcm "google.golang.org/grpc/metadata" grpcm "google.golang.org/grpc/metadata"

View File

@ -19,12 +19,12 @@ package leases
import ( import (
"context" "context"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/gc" "github.com/containerd/containerd/gc"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
"github.com/containerd/containerd/metadata" "github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
bolt "go.etcd.io/bbolt"
) )
func init() { func init() {

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"strings" "strings"
"github.com/boltdb/bolt"
eventstypes "github.com/containerd/containerd/api/events" eventstypes "github.com/containerd/containerd/api/events"
api "github.com/containerd/containerd/api/services/namespaces/v1" api "github.com/containerd/containerd/api/services/namespaces/v1"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
@ -30,6 +29,7 @@ import (
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services" "github.com/containerd/containerd/services"
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"

View File

@ -29,7 +29,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/boltdb/bolt"
csapi "github.com/containerd/containerd/api/services/content/v1" csapi "github.com/containerd/containerd/api/services/content/v1"
ssapi "github.com/containerd/containerd/api/services/snapshots/v1" ssapi "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@ -46,6 +45,7 @@ import (
metrics "github.com/docker/go-metrics" metrics "github.com/docker/go-metrics"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc" "google.golang.org/grpc"
) )

View File

@ -26,7 +26,6 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/boltdb/bolt"
api "github.com/containerd/containerd/api/services/tasks/v1" api "github.com/containerd/containerd/api/services/tasks/v1"
"github.com/containerd/containerd/api/types" "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/api/types/task" "github.com/containerd/containerd/api/types/task"
@ -48,6 +47,7 @@ import (
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"

View File

@ -23,11 +23,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/metadata/boltutil" "github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
var ( var (

View File

@ -25,9 +25,9 @@ import (
"context" "context"
"sync" "sync"
"github.com/boltdb/bolt"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/pkg/errors" "github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
) )
// Transactor is used to finalize an active transaction. // Transactor is used to finalize an active transaction.

View File

@ -34,13 +34,13 @@ github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio v0.4.10 github.com/Microsoft/go-winio v0.4.10
github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55 github.com/Microsoft/hcsshim 44c060121b68e8bdc40b411beba551f3b4ee9e55
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
gotest.tools v2.1.0 gotest.tools v2.1.0
github.com/google/go-cmp v0.1.0 github.com/google/go-cmp v0.1.0
go.etcd.io/bbolt v1.3.1-etcd.8
# cri dependencies # cri dependencies
github.com/containerd/cri v1.11.1 github.com/containerd/cri v1.11.1

View File

@ -1,5 +1,18 @@
Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.2.1-green.svg) bbolt
==== =====
[![Go Report Card](https://goreportcard.com/badge/github.com/etcd-io/bbolt?style=flat-square)](https://goreportcard.com/report/github.com/etcd-io/bbolt)
[![Coverage](https://codecov.io/gh/etcd-io/bbolt/branch/master/graph/badge.svg)](https://codecov.io/gh/etcd-io/bbolt)
[![Build Status Travis](https://img.shields.io/travis/etcd-io/bboltlabs.svg?style=flat-square&&branch=master)](https://travis-ci.com/etcd-io/bbolt)
[![Godoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/etcd-io/bbolt)
[![Releases](https://img.shields.io/github/release/etcd-io/bbolt/all.svg?style=flat-square)](https://github.com/etcd-io/bbolt/releases)
[![LICENSE](https://img.shields.io/github/license/etcd-io/bbolt.svg?style=flat-square)](https://github.com/etcd-io/bbolt/blob/master/LICENSE)
bbolt is a fork of [Ben Johnson's][gh_ben] [Bolt][bolt] key/value
store. The purpose of this fork is to provide the Go community with an active
maintenance and development target for Bolt; the goal is improved reliability
and stability. bbolt includes bug fixes, performance enhancements, and features
not found in Bolt while preserving backwards compatibility with the Bolt API.
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
[LMDB project][lmdb]. The goal of the project is to provide a simple, [LMDB project][lmdb]. The goal of the project is to provide a simple,
@ -10,6 +23,8 @@ Since Bolt is meant to be used as such a low-level piece of functionality,
simplicity is key. The API will be small and only focus on getting values simplicity is key. The API will be small and only focus on getting values
and setting values. That's it. and setting values. That's it.
[gh_ben]: https://github.com/benbjohnson
[bolt]: https://github.com/boltdb/bolt
[hyc_symas]: https://twitter.com/hyc_symas [hyc_symas]: https://twitter.com/hyc_symas
[lmdb]: http://symas.com/mdb/ [lmdb]: http://symas.com/mdb/
@ -21,36 +36,42 @@ consistency and thread safety. Bolt is currently used in high-load production
environments serving databases as large as 1TB. Many companies such as environments serving databases as large as 1TB. Many companies such as
Shopify and Heroku use Bolt-backed services every day. Shopify and Heroku use Bolt-backed services every day.
## Project versioning
bbolt uses [semantic versioning](http://semver.org).
API should not change between patch and minor releases.
New minor versions may add additional features to the API.
## Table of Contents ## Table of Contents
- [Getting Started](#getting-started) - [Getting Started](#getting-started)
- [Installing](#installing) - [Installing](#installing)
- [Opening a database](#opening-a-database) - [Opening a database](#opening-a-database)
- [Transactions](#transactions) - [Transactions](#transactions)
- [Read-write transactions](#read-write-transactions) - [Read-write transactions](#read-write-transactions)
- [Read-only transactions](#read-only-transactions) - [Read-only transactions](#read-only-transactions)
- [Batch read-write transactions](#batch-read-write-transactions) - [Batch read-write transactions](#batch-read-write-transactions)
- [Managing transactions manually](#managing-transactions-manually) - [Managing transactions manually](#managing-transactions-manually)
- [Using buckets](#using-buckets) - [Using buckets](#using-buckets)
- [Using key/value pairs](#using-keyvalue-pairs) - [Using key/value pairs](#using-keyvalue-pairs)
- [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket) - [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket)
- [Iterating over keys](#iterating-over-keys) - [Iterating over keys](#iterating-over-keys)
- [Prefix scans](#prefix-scans) - [Prefix scans](#prefix-scans)
- [Range scans](#range-scans) - [Range scans](#range-scans)
- [ForEach()](#foreach) - [ForEach()](#foreach)
- [Nested buckets](#nested-buckets) - [Nested buckets](#nested-buckets)
- [Database backups](#database-backups) - [Database backups](#database-backups)
- [Statistics](#statistics) - [Statistics](#statistics)
- [Read-Only Mode](#read-only-mode) - [Read-Only Mode](#read-only-mode)
- [Mobile Use (iOS/Android)](#mobile-use-iosandroid) - [Mobile Use (iOS/Android)](#mobile-use-iosandroid)
- [Resources](#resources) - [Resources](#resources)
- [Comparison with other databases](#comparison-with-other-databases) - [Comparison with other databases](#comparison-with-other-databases)
- [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases) - [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases)
- [LevelDB, RocksDB](#leveldb-rocksdb) - [LevelDB, RocksDB](#leveldb-rocksdb)
- [LMDB](#lmdb) - [LMDB](#lmdb)
- [Caveats & Limitations](#caveats--limitations) - [Caveats & Limitations](#caveats--limitations)
- [Reading the Source](#reading-the-source) - [Reading the Source](#reading-the-source)
- [Other Projects Using Bolt](#other-projects-using-bolt) - [Other Projects Using Bolt](#other-projects-using-bolt)
## Getting Started ## Getting Started
@ -59,13 +80,28 @@ Shopify and Heroku use Bolt-backed services every day.
To start using Bolt, install Go and run `go get`: To start using Bolt, install Go and run `go get`:
```sh ```sh
$ go get github.com/boltdb/bolt/... $ go get go.etcd.io/bbolt/...
``` ```
This will retrieve the library and install the `bolt` command line utility into This will retrieve the library and install the `bolt` command line utility into
your `$GOBIN` path. your `$GOBIN` path.
### Importing bbolt
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)
if err != nil {
return err
}
defer db.Close()
```
### Opening a database ### Opening a database
The top-level object in Bolt is a `DB`. It is represented as a single file on The top-level object in Bolt is a `DB`. It is represented as a single file on
@ -79,7 +115,7 @@ package main
import ( import (
"log" "log"
"github.com/boltdb/bolt" bolt "go.etcd.io/bbolt"
) )
func main() { func main() {
@ -522,7 +558,7 @@ this from a read-only transaction, it will perform a hot backup and not block
your other database reads and writes. your other database reads and writes.
By default, it will use a regular file handle which will utilize the operating By default, it will use a regular file handle which will utilize the operating
system's page cache. See the [`Tx`](https://godoc.org/github.com/boltdb/bolt#Tx) system's page cache. See the [`Tx`](https://godoc.org/go.etcd.io/bbolt#Tx)
documentation for information about optimizing for larger-than-RAM datasets. documentation for information about optimizing for larger-than-RAM datasets.
One common use case is to backup over HTTP so you can use tools like `cURL` to One common use case is to backup over HTTP so you can use tools like `cURL` to
@ -811,7 +847,7 @@ Here are a few things to note when evaluating and using Bolt:
## Reading the Source ## Reading the Source
Bolt is a relatively small code base (<3KLOC) for an embedded, serializable, Bolt is a relatively small code base (<5KLOC) for an embedded, serializable,
transactional key/value database so it can be a good starting point for people transactional key/value database so it can be a good starting point for people
interested in how databases work. interested in how databases work.
@ -863,53 +899,55 @@ them via pull request.
Below is a list of public, open source projects that use Bolt: 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. * [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside. * [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside.
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. * [bolter](https://github.com/hasit/bolter) - Command-line app for viewing BoltDB file in your terminal.
* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics. * [boltcli](https://github.com/spacewander/boltcli) - the redis-cli for boltdb with Lua script support.
* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects. * [BoltHold](https://github.com/timshannon/bolthold) - An embeddable NoSQL store for Go types built on BoltDB
* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt. * [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt.
* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site. * [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage. * [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. * [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. * [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
* [stow](https://github.com/djherbis/stow) - a persistence manager for objects
backed by boltdb.
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining * [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
simple tx and key scans. simple tx and key scans.
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets. * [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service * [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. * [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. * [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
* [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB.
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings.
* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development.
* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains
* [bolter](https://github.com/hasit/bolter) - Command-line app for viewing BoltDB file in your terminal.
* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency. * [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.
* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies * [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
* [BoltHold](https://github.com/timshannon/bolthold) - An embeddable NoSQL store for Go types built on BoltDB * [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
* [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.
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
* [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.
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
* [stow](https://github.com/djherbis/stow) - a persistence manager for objects
backed by boltdb.
* [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB.
* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings.
* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics.
* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects.
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development.
* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
If you are using Bolt in a project please send a pull request to add it to the list. If you are using Bolt in a project please send a pull request to add it to the list.

View File

@ -1,4 +1,4 @@
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB const maxMapSize = 0x7FFFFFFF // 2GB

View File

@ -1,4 +1,4 @@
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB const maxMapSize = 0xFFFFFFFFFFFF // 256TB

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import "unsafe" import "unsafe"

View File

@ -1,6 +1,6 @@
// +build arm64 // +build arm64
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB const maxMapSize = 0xFFFFFFFFFFFF // 256TB

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"syscall" "syscall"

12
vendor/go.etcd.io/bbolt/bolt_mips64x.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// +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
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

12
vendor/go.etcd.io/bbolt/bolt_mipsx.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// +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
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"syscall" "syscall"

View File

@ -1,9 +1,12 @@
// +build ppc // +build ppc
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers. // maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF const maxAllocSize = 0xFFFFFFF
// Are unaligned load/stores broken on this arch?
var brokenUnaligned = false

View File

@ -1,6 +1,6 @@
// +build ppc64 // +build ppc64
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB const maxMapSize = 0xFFFFFFFFFFFF // 256TB

View File

@ -1,6 +1,6 @@
// +build ppc64le // +build ppc64le
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB const maxMapSize = 0xFFFFFFFFFFFF // 256TB

View File

@ -1,6 +1,6 @@
// +build s390x // +build s390x
package bolt package bbolt
// maxMapSize represents the largest mmap size supported by Bolt. // maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB const maxMapSize = 0xFFFFFFFFFFFF // 256TB

View File

@ -1,41 +1,43 @@
// +build !windows,!plan9,!solaris // +build !windows,!plan9,!solaris
package bolt package bbolt
import ( import (
"fmt" "fmt"
"os"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { func flock(db *DB, exclusive bool, timeout time.Duration) error {
var t time.Time var t time.Time
if timeout != 0 {
t = time.Now()
}
fd := db.file.Fd()
flag := syscall.LOCK_NB
if exclusive {
flag |= syscall.LOCK_EX
} else {
flag |= syscall.LOCK_SH
}
for { for {
// If we're beyond our timeout then return an error. // Attempt to obtain an exclusive lock.
// This can only occur after we've attempted a flock once. err := syscall.Flock(int(fd), flag)
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
flag := syscall.LOCK_SH
if exclusive {
flag = syscall.LOCK_EX
}
// Otherwise attempt to obtain an exclusive lock.
err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
if err == nil { if err == nil {
return nil return nil
} else if err != syscall.EWOULDBLOCK { } else if err != syscall.EWOULDBLOCK {
return err 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. // Wait for a bit and try again.
time.Sleep(50 * time.Millisecond) time.Sleep(flockRetryTimeout)
} }
} }
@ -53,7 +55,9 @@ func mmap(db *DB, sz int) error {
} }
// Advise the kernel that the mmap is accessed randomly. // Advise the kernel that the mmap is accessed randomly.
if err := madvise(b, syscall.MADV_RANDOM); err != nil { err = 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) return fmt.Errorf("madvise: %s", err)
} }

View File

@ -1,8 +1,7 @@
package bolt package bbolt
import ( import (
"fmt" "fmt"
"os"
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
@ -11,36 +10,35 @@ import (
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { func flock(db *DB, exclusive bool, timeout time.Duration) error {
var t time.Time 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 { for {
// If we're beyond our timeout then return an error. // Attempt to obtain an exclusive lock.
// This can only occur after we've attempted a flock once. lock := syscall.Flock_t{Type: lockType}
if t.IsZero() { err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Pid = 0
lock.Whence = 0
lock.Pid = 0
if exclusive {
lock.Type = syscall.F_WRLCK
} else {
lock.Type = syscall.F_RDLCK
}
err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
if err == nil { if err == nil {
return nil return nil
} else if err != syscall.EAGAIN { } else if err != syscall.EAGAIN {
return err 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. // Wait for a bit and try again.
time.Sleep(50 * time.Millisecond) time.Sleep(flockRetryTimeout)
} }
} }

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"fmt" "fmt"
@ -16,8 +16,6 @@ var (
) )
const ( const (
lockExt = ".lock"
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
flagLockExclusive = 2 flagLockExclusive = 2
flagLockFailImmediately = 1 flagLockFailImmediately = 1
@ -48,48 +46,47 @@ func fdatasync(db *DB) error {
} }
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error { func flock(db *DB, exclusive bool, timeout time.Duration) error {
// Create a separate lock file on windows because a process
// cannot share an exclusive lock on the same file. This is
// needed during Tx.WriteTo().
f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
if err != nil {
return err
}
db.lockfile = f
var t time.Time var t time.Time
if timeout != 0 {
t = time.Now()
}
var flag uint32 = flagLockFailImmediately
if exclusive {
flag |= flagLockExclusive
}
for { for {
// If we're beyond our timeout then return an error. // Fix for https://github.com/etcd-io/bbolt/issues/121. Use byte-range
// This can only occur after we've attempted a flock once. // -1..0 as the lock on the database file.
if t.IsZero() { var m1 uint32 = (1 << 32) - 1 // -1 in a uint32
t = time.Now() err := lockFileEx(syscall.Handle(db.file.Fd()), flag, 0, 1, 0, &syscall.Overlapped{
} else if timeout > 0 && time.Since(t) > timeout { Offset: m1,
return ErrTimeout OffsetHigh: m1,
} })
var flag uint32 = flagLockFailImmediately
if exclusive {
flag |= flagLockExclusive
}
err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
if err == nil { if err == nil {
return nil return nil
} else if err != errLockViolation { } else if err != errLockViolation {
return err return err
} }
// If we timed oumercit then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout
}
// Wait for a bit and try again. // Wait for a bit and try again.
time.Sleep(50 * time.Millisecond) time.Sleep(flockRetryTimeout)
} }
} }
// funlock releases an advisory lock on a file descriptor. // funlock releases an advisory lock on a file descriptor.
func funlock(db *DB) error { func funlock(db *DB) error {
err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{}) var m1 uint32 = (1 << 32) - 1 // -1 in a uint32
db.lockfile.Close() err := unlockFileEx(syscall.Handle(db.file.Fd()), 0, 1, 0, &syscall.Overlapped{
os.Remove(db.path + lockExt) Offset: m1,
OffsetHigh: m1,
})
return err return err
} }

View File

@ -1,6 +1,6 @@
// +build !windows,!plan9,!linux,!openbsd // +build !windows,!plan9,!linux,!openbsd
package bolt package bbolt
// fdatasync flushes written data to a file descriptor. // fdatasync flushes written data to a file descriptor.
func fdatasync(db *DB) error { func fdatasync(db *DB) error {

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"bytes" "bytes"
@ -14,13 +14,6 @@ const (
MaxValueSize = (1 << 31) - 2 MaxValueSize = (1 << 31) - 2
) )
const (
maxUint = ^uint(0)
minUint = 0
maxInt = int(^uint(0) >> 1)
minInt = -maxInt - 1
)
const bucketHeaderSize = int(unsafe.Sizeof(bucket{})) const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
const ( const (
@ -323,7 +316,12 @@ func (b *Bucket) Delete(key []byte) error {
// Move cursor to correct position. // Move cursor to correct position.
c := b.Cursor() c := b.Cursor()
_, _, flags := c.seek(key) k, _, flags := c.seek(key)
// Return nil if the key doesn't exist.
if !bytes.Equal(key, k) {
return nil
}
// Return an error if there is already existing bucket value. // Return an error if there is already existing bucket value.
if (flags & bucketLeafFlag) != 0 { if (flags & bucketLeafFlag) != 0 {

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"bytes" "bytes"
@ -157,12 +157,6 @@ func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
// Start from root page/node and traverse to correct page. // Start from root page/node and traverse to correct page.
c.stack = c.stack[:0] c.stack = c.stack[:0]
c.search(seek, c.bucket.root) c.search(seek, c.bucket.root)
ref := &c.stack[len(c.stack)-1]
// If the cursor is pointing to the end of page/node then return nil.
if ref.index >= ref.count() {
return nil, nil, 0
}
// If this is a bucket then return a nil value. // If this is a bucket then return a nil value.
return c.keyValue() return c.keyValue()
@ -339,6 +333,8 @@ func (c *Cursor) nsearch(key []byte) {
// keyValue returns the key and value of the current leaf element. // keyValue returns the key and value of the current leaf element.
func (c *Cursor) keyValue() ([]byte, []byte, uint32) { func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
ref := &c.stack[len(c.stack)-1] ref := &c.stack[len(c.stack)-1]
// If the cursor is pointing to the end of page/node then return nil.
if ref.count() == 0 || ref.index >= ref.count() { if ref.count() == 0 || ref.index >= ref.count() {
return nil, nil, 0 return nil, nil, 0
} }

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"errors" "errors"
@ -7,8 +7,7 @@ import (
"log" "log"
"os" "os"
"runtime" "runtime"
"runtime/debug" "sort"
"strings"
"sync" "sync"
"time" "time"
"unsafe" "unsafe"
@ -23,6 +22,8 @@ const version = 2
// Represents a marker value to indicate that a file is a Bolt DB. // Represents a marker value to indicate that a file is a Bolt DB.
const magic uint32 = 0xED0CDAED const magic uint32 = 0xED0CDAED
const pgidNoFreelist pgid = 0xffffffffffffffff
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when // IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
// syncing changes to a file. This is required as some operating systems, // 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 // such as OpenBSD, do not have a unified buffer cache (UBC) and writes
@ -39,6 +40,9 @@ const (
// default page size for db is set to the OS page size. // default page size for db is set to the OS page size.
var defaultPageSize = os.Getpagesize() var defaultPageSize = os.Getpagesize()
// The time elapsed between consecutive file locking attempts.
const flockRetryTimeout = 50 * time.Millisecond
// DB represents a collection of buckets persisted to a file on disk. // DB represents a collection of buckets persisted to a file on disk.
// All data access is performed through transactions which can be obtained through the DB. // All data access is performed through transactions which can be obtained through the DB.
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
@ -61,6 +65,11 @@ type DB struct {
// THIS IS UNSAFE. PLEASE USE WITH CAUTION. // THIS IS UNSAFE. PLEASE USE WITH CAUTION.
NoSync bool NoSync bool
// When true, skips syncing freelist to disk. This improves the database
// write performance under normal operation, but requires a full database
// re-sync during recovery.
NoFreelistSync bool
// When true, skips the truncate call when growing the database. // When true, skips the truncate call when growing the database.
// Setting this to true is only safe on non-ext3/ext4 systems. // Setting this to true is only safe on non-ext3/ext4 systems.
// Skipping truncation avoids preallocation of hard drive space and // Skipping truncation avoids preallocation of hard drive space and
@ -96,8 +105,7 @@ type DB struct {
path string path string
file *os.File file *os.File
lockfile *os.File // windows only dataref []byte // mmap'ed readonly, write throws SEGV
dataref []byte // mmap'ed readonly, write throws SEGV
data *[maxMapSize]byte data *[maxMapSize]byte
datasz int datasz int
filesz int // current on disk file size filesz int // current on disk file size
@ -107,9 +115,11 @@ type DB struct {
opened bool opened bool
rwtx *Tx rwtx *Tx
txs []*Tx txs []*Tx
freelist *freelist
stats Stats stats Stats
freelist *freelist
freelistLoad sync.Once
pagePool sync.Pool pagePool sync.Pool
batchMu sync.Mutex batchMu sync.Mutex
@ -148,14 +158,17 @@ func (db *DB) String() string {
// If the file does not exist then it will be created automatically. // If the file does not exist then it will be created automatically.
// Passing in nil options will cause Bolt to open the database with the default options. // 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) { func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
var db = &DB{opened: true} db := &DB{
opened: true,
}
// Set default options if no options are provided. // Set default options if no options are provided.
if options == nil { if options == nil {
options = DefaultOptions options = DefaultOptions
} }
db.NoSync = options.NoSync
db.NoGrowSync = options.NoGrowSync db.NoGrowSync = options.NoGrowSync
db.MmapFlags = options.MmapFlags db.MmapFlags = options.MmapFlags
db.NoFreelistSync = options.NoFreelistSync
// Set default values for later DB operations. // Set default values for later DB operations.
db.MaxBatchSize = DefaultMaxBatchSize db.MaxBatchSize = DefaultMaxBatchSize
@ -183,7 +196,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// if !options.ReadOnly. // if !options.ReadOnly.
// The database file is locked using the shared lock (more than one process may // 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). // hold a lock at the same time) otherwise (options.ReadOnly is set).
if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil { if err := flock(db, !db.readOnly, options.Timeout); err != nil {
_ = db.close() _ = db.close()
return nil, err return nil, err
} }
@ -191,31 +204,41 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// Default values for test hooks // Default values for test hooks
db.ops.writeAt = db.file.WriteAt db.ops.writeAt = db.file.WriteAt
if db.pageSize = options.PageSize; db.pageSize == 0 {
// Set the default page size to the OS page size.
db.pageSize = defaultPageSize
}
// Initialize the database if it doesn't exist. // Initialize the database if it doesn't exist.
if info, err := db.file.Stat(); err != nil { if info, err := db.file.Stat(); err != nil {
_ = db.close()
return nil, err return nil, err
} else if info.Size() == 0 { } else if info.Size() == 0 {
// Initialize new files with meta pages. // 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()
return nil, err return nil, err
} }
} else { } else {
// Read the first meta page to determine the page size. // Read the first meta page to determine the page size.
var buf [0x1000]byte var buf [0x1000]byte
if _, err := db.file.ReadAt(buf[:], 0); err == nil { // If we can't read the page size, but can read a page, assume
m := db.pageInBuffer(buf[:], 0).meta() // it's the same as the OS or one given -- since that's how the
if err := m.validate(); err != nil { // page size was chosen in the first place.
// If we can't read the page size, we can assume it's the same //
// as the OS -- since that's how the page size was chosen in the // If the first page is invalid and this OS uses a different
// first place. // page size than what the database was created with then we
// // are out of luck and cannot access the database.
// If the first page is invalid and this OS uses a different //
// page size than what the database was created with then we // TODO: scan for next page
// are out of luck and cannot access the database. if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) {
db.pageSize = os.Getpagesize() if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
} else {
db.pageSize = int(m.pageSize) db.pageSize = int(m.pageSize)
} }
} else {
_ = db.close()
return nil, ErrInvalid
} }
} }
@ -232,14 +255,50 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return nil, err return nil, err
} }
// Read in the freelist. if db.readOnly {
db.freelist = newFreelist() return db, nil
db.freelist.read(db.page(db.meta().freelist)) }
db.loadFreelist()
// 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)
if tx != nil {
err = tx.Commit()
}
if err != nil {
_ = db.close()
return nil, err
}
}
// Mark the database as opened and return. // Mark the database as opened and return.
return db, nil return db, nil
} }
// loadFreelist reads the freelist if it is synced, or reconstructs it
// by scanning the DB if it is not synced. It assumes there are no
// concurrent accesses being made to the freelist.
func (db *DB) loadFreelist() {
db.freelistLoad.Do(func() {
db.freelist = newFreelist()
if !db.hasSyncedFreelist() {
// Reconstruct free list by scanning the DB.
db.freelist.readIDs(db.freepages())
} else {
// Read free list from freelist page.
db.freelist.read(db.page(db.meta().freelist))
}
db.stats.FreePageN = len(db.freelist.ids)
})
}
func (db *DB) hasSyncedFreelist() bool {
return db.meta().freelist != pgidNoFreelist
}
// mmap opens the underlying memory-mapped file and initializes the meta references. // mmap opens the underlying memory-mapped file and initializes the meta references.
// minsz is the minimum size that the new mmap can be. // minsz is the minimum size that the new mmap can be.
func (db *DB) mmap(minsz int) error { func (db *DB) mmap(minsz int) error {
@ -341,9 +400,6 @@ func (db *DB) mmapSize(size int) (int, error) {
// init creates a new database file and initializes its meta pages. // init creates a new database file and initializes its meta pages.
func (db *DB) init() error { func (db *DB) init() error {
// Set the page size to the OS page size.
db.pageSize = os.Getpagesize()
// Create two meta pages on a buffer. // Create two meta pages on a buffer.
buf := make([]byte, db.pageSize*4) buf := make([]byte, db.pageSize*4)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
@ -387,7 +443,8 @@ func (db *DB) init() error {
} }
// Close releases all database resources. // Close releases all database resources.
// All transactions must be closed before closing the database. // It will block waiting for any open transactions to finish
// before closing the database and returning.
func (db *DB) Close() error { func (db *DB) Close() error {
db.rwlock.Lock() db.rwlock.Lock()
defer db.rwlock.Unlock() defer db.rwlock.Unlock()
@ -395,8 +452,8 @@ func (db *DB) Close() error {
db.metalock.Lock() db.metalock.Lock()
defer db.metalock.Unlock() defer db.metalock.Unlock()
db.mmaplock.RLock() db.mmaplock.Lock()
defer db.mmaplock.RUnlock() defer db.mmaplock.Unlock()
return db.close() return db.close()
} }
@ -526,21 +583,36 @@ func (db *DB) beginRWTx() (*Tx, error) {
t := &Tx{writable: true} t := &Tx{writable: true}
t.init(db) t.init(db)
db.rwtx = t db.rwtx = t
db.freePages()
return t, nil
}
// Free any pages associated with closed read-only transactions. // freePages releases any pages associated with closed read-only transactions.
var minid txid = 0xFFFFFFFFFFFFFFFF func (db *DB) freePages() {
for _, t := range db.txs { // Free all pending pages prior to earliest open transaction.
if t.meta.txid < minid { sort.Sort(txsById(db.txs))
minid = t.meta.txid minid := txid(0xFFFFFFFFFFFFFFFF)
} if len(db.txs) > 0 {
minid = db.txs[0].meta.txid
} }
if minid > 0 { if minid > 0 {
db.freelist.release(minid - 1) db.freelist.release(minid - 1)
} }
// Release unused txid extents.
return t, nil 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. // removeTx removes a transaction from the database.
func (db *DB) removeTx(tx *Tx) { func (db *DB) removeTx(tx *Tx) {
// Release the read lock on the mmap. // Release the read lock on the mmap.
@ -633,11 +705,7 @@ func (db *DB) View(fn func(*Tx) error) error {
return err return err
} }
if err := t.Rollback(); err != nil { return t.Rollback()
return err
}
return nil
} }
// Batch calls fn as part of a batch. It behaves similar to Update, // Batch calls fn as part of a batch. It behaves similar to Update,
@ -737,9 +805,7 @@ retry:
// pass success, or bolt internal errors, to all callers // pass success, or bolt internal errors, to all callers
for _, c := range b.calls { for _, c := range b.calls {
if c.err != nil { c.err <- err
c.err <- err
}
} }
break retry break retry
} }
@ -826,7 +892,7 @@ func (db *DB) meta() *meta {
} }
// allocate returns a contiguous block of memory starting at a given page. // allocate returns a contiguous block of memory starting at a given page.
func (db *DB) allocate(count int) (*page, error) { func (db *DB) allocate(txid txid, count int) (*page, error) {
// Allocate a temporary buffer for the page. // Allocate a temporary buffer for the page.
var buf []byte var buf []byte
if count == 1 { if count == 1 {
@ -838,7 +904,7 @@ func (db *DB) allocate(count int) (*page, error) {
p.overflow = uint32(count - 1) p.overflow = uint32(count - 1)
// Use pages from the freelist if they are available. // Use pages from the freelist if they are available.
if p.id = db.freelist.allocate(count); p.id != 0 { if p.id = db.freelist.allocate(txid, count); p.id != 0 {
return p, nil return p, nil
} }
@ -893,6 +959,38 @@ func (db *DB) IsReadOnly() bool {
return db.readOnly return db.readOnly
} }
func (db *DB) freepages() []pgid {
tx, err := db.beginTx()
defer func() {
err = tx.Rollback()
if err != nil {
panic("freepages: failed to rollback tx")
}
}()
if err != nil {
panic("freepages: failed to open read only tx")
}
reachable := make(map[pgid]*page)
nofreed := make(map[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, ech)
close(ech)
var fids []pgid
for i := pgid(2); i < db.meta().pgid; i++ {
if _, ok := reachable[i]; !ok {
fids = append(fids, i)
}
}
return fids
}
// Options represents the options that can be set when opening a database. // Options represents the options that can be set when opening a database.
type Options struct { type Options struct {
// Timeout is the amount of time to wait to obtain a file lock. // Timeout is the amount of time to wait to obtain a file lock.
@ -903,6 +1001,10 @@ type Options struct {
// Sets the DB.NoGrowSync flag before memory mapping the file. // Sets the DB.NoGrowSync flag before memory mapping the file.
NoGrowSync bool NoGrowSync bool
// Do not sync freelist to disk. This improves the database write performance
// under normal operation, but requires a full database re-sync during recovery.
NoFreelistSync bool
// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to // Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
// grab a shared lock (UNIX). // grab a shared lock (UNIX).
ReadOnly bool ReadOnly bool
@ -919,6 +1021,14 @@ type Options struct {
// If initialMmapSize is smaller than the previous database size, // If initialMmapSize is smaller than the previous database size,
// it takes no effect. // it takes no effect.
InitialMmapSize int InitialMmapSize int
// PageSize overrides the default OS page size.
PageSize int
// NoSync sets the initial value of DB.NoSync. Normally this can just be
// set directly on the DB itself when returned from Open(), but this option
// is useful in APIs which expose Options but not the underlying DB.
NoSync bool
} }
// DefaultOptions represent the options used if nil options are passed into Open(). // DefaultOptions represent the options used if nil options are passed into Open().
@ -960,10 +1070,6 @@ func (s *Stats) Sub(other *Stats) Stats {
return diff return diff
} }
func (s *Stats) add(other *Stats) {
s.TxStats.add(&other.TxStats)
}
type Info struct { type Info struct {
Data uintptr Data uintptr
PageSize int PageSize int
@ -1002,7 +1108,8 @@ func (m *meta) copy(dest *meta) {
func (m *meta) write(p *page) { func (m *meta) write(p *page) {
if m.root.root >= m.pgid { if m.root.root >= m.pgid {
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", 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 { } 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)) panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
} }
@ -1029,11 +1136,3 @@ func _assert(condition bool, msg string, v ...interface{}) {
panic(fmt.Sprintf("assertion failed: "+msg, v...)) panic(fmt.Sprintf("assertion failed: "+msg, v...))
} }
} }
func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) }
func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }
func printstack() {
stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
fmt.Fprintln(os.Stderr, stack)
}

View File

@ -1,5 +1,5 @@
/* /*
Package bolt implements a low-level key/value store in pure Go. It supports package bbolt implements a low-level key/value store in pure Go. It supports
fully serializable transactions, ACID semantics, and lock-free MVCC with fully serializable transactions, ACID semantics, and lock-free MVCC with
multiple readers and a single writer. Bolt can be used for projects that multiple readers and a single writer. Bolt can be used for projects that
want a simple data store without the need to add large dependencies such as want a simple data store without the need to add large dependencies such as
@ -41,4 +41,4 @@ point to different data or can point to invalid memory which will cause a panic.
*/ */
package bolt package bbolt

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import "errors" import "errors"

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"fmt" "fmt"
@ -6,18 +6,28 @@ import (
"unsafe" "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
}
// freelist represents a list of all pages that are available for allocation. // 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. // It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct { type freelist struct {
ids []pgid // all free and available free page ids. ids []pgid // all free and available free page ids.
pending map[txid][]pgid // mapping of soon-to-be free page ids by tx. allocs map[pgid]txid // mapping of txid that allocated a pgid.
cache map[pgid]bool // fast lookup of all free and pending page ids. pending map[txid]*txPending // mapping of soon-to-be free page ids by tx.
cache map[pgid]bool // fast lookup of all free and pending page ids.
} }
// newFreelist returns an empty, initialized freelist. // newFreelist returns an empty, initialized freelist.
func newFreelist() *freelist { func newFreelist() *freelist {
return &freelist{ return &freelist{
pending: make(map[txid][]pgid), allocs: make(map[pgid]txid),
pending: make(map[txid]*txPending),
cache: make(map[pgid]bool), cache: make(map[pgid]bool),
} }
} }
@ -45,8 +55,8 @@ func (f *freelist) free_count() int {
// pending_count returns count of pending pages // pending_count returns count of pending pages
func (f *freelist) pending_count() int { func (f *freelist) pending_count() int {
var count int var count int
for _, list := range f.pending { for _, txp := range f.pending {
count += len(list) count += len(txp.ids)
} }
return count return count
} }
@ -55,8 +65,8 @@ func (f *freelist) pending_count() int {
// f.count returns the minimum length required for dst. // f.count returns the minimum length required for dst.
func (f *freelist) copyall(dst []pgid) { func (f *freelist) copyall(dst []pgid) {
m := make(pgids, 0, f.pending_count()) m := make(pgids, 0, f.pending_count())
for _, list := range f.pending { for _, txp := range f.pending {
m = append(m, list...) m = append(m, txp.ids...)
} }
sort.Sort(m) sort.Sort(m)
mergepgids(dst, f.ids, m) mergepgids(dst, f.ids, m)
@ -64,7 +74,7 @@ func (f *freelist) copyall(dst []pgid) {
// allocate returns the starting page id of a contiguous list of pages of a given size. // allocate 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. // If a contiguous block cannot be found then 0 is returned.
func (f *freelist) allocate(n int) pgid { func (f *freelist) allocate(txid txid, n int) pgid {
if len(f.ids) == 0 { if len(f.ids) == 0 {
return 0 return 0
} }
@ -97,7 +107,7 @@ func (f *freelist) allocate(n int) pgid {
for i := pgid(0); i < pgid(n); i++ { for i := pgid(0); i < pgid(n); i++ {
delete(f.cache, initial+i) delete(f.cache, initial+i)
} }
f.allocs[initial] = txid
return initial return initial
} }
@ -114,28 +124,73 @@ func (f *freelist) free(txid txid, p *page) {
} }
// Free page and all its overflow pages. // Free page and all its overflow pages.
var ids = f.pending[txid] 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++ { for id := p.id; id <= p.id+pgid(p.overflow); id++ {
// Verify that page is not already free. // Verify that page is not already free.
if f.cache[id] { if f.cache[id] {
panic(fmt.Sprintf("page %d already freed", id)) panic(fmt.Sprintf("page %d already freed", id))
} }
// Add to the freelist and cache. // Add to the freelist and cache.
ids = append(ids, id) txp.ids = append(txp.ids, id)
txp.alloctx = append(txp.alloctx, allocTxid)
f.cache[id] = true f.cache[id] = true
} }
f.pending[txid] = ids
} }
// release moves all page ids for a transaction id (or older) to the freelist. // release moves all page ids for a transaction id (or older) to the freelist.
func (f *freelist) release(txid txid) { func (f *freelist) release(txid txid) {
m := make(pgids, 0) m := make(pgids, 0)
for tid, ids := range f.pending { for tid, txp := range f.pending {
if tid <= txid { if tid <= txid {
// Move transaction's pending pages to the available freelist. // Move transaction's pending pages to the available freelist.
// Don't remove from the cache since the page is still free. // Don't remove from the cache since the page is still free.
m = append(m, ids...) m = append(m, txp.ids...)
delete(f.pending, tid)
}
}
sort.Sort(m)
f.ids = pgids(f.ids).merge(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) delete(f.pending, tid)
} }
} }
@ -146,12 +201,29 @@ func (f *freelist) release(txid txid) {
// rollback removes the pages from a given pending tx. // rollback removes the pages from a given pending tx.
func (f *freelist) rollback(txid txid) { func (f *freelist) rollback(txid txid) {
// Remove page ids from cache. // Remove page ids from cache.
for _, id := range f.pending[txid] { txp := f.pending[txid]
delete(f.cache, id) if txp == nil {
return
} }
var m pgids
// Remove pages from pending list. 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) delete(f.pending, txid)
sort.Sort(m)
f.ids = pgids(f.ids).merge(m)
} }
// freed returns whether a given page is in the free list. // freed returns whether a given page is in the free list.
@ -161,6 +233,9 @@ func (f *freelist) freed(pgid pgid) bool {
// read initializes the freelist from a freelist page. // read initializes the freelist from a freelist page.
func (f *freelist) read(p *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 // 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. // an overflow and the size of the freelist is stored as the first element.
idx, count := 0, int(p.count) idx, count := 0, int(p.count)
@ -173,7 +248,7 @@ func (f *freelist) read(p *page) {
if count == 0 { if count == 0 {
f.ids = nil f.ids = nil
} else { } else {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx : idx+count]
f.ids = make([]pgid, len(ids)) f.ids = make([]pgid, len(ids))
copy(f.ids, ids) copy(f.ids, ids)
@ -185,6 +260,12 @@ func (f *freelist) read(p *page) {
f.reindex() f.reindex()
} }
// read initializes the freelist from a given list of ids.
func (f *freelist) readIDs(ids []pgid) {
f.ids = ids
f.reindex()
}
// write writes the page ids onto a freelist page. All free and pending ids are // 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 // saved to disk since in the event of a program crash, all pending ids will
// become free. // become free.
@ -217,8 +298,8 @@ func (f *freelist) reload(p *page) {
// Build a cache of only pending pages. // Build a cache of only pending pages.
pcache := make(map[pgid]bool) pcache := make(map[pgid]bool)
for _, pendingIDs := range f.pending { for _, txp := range f.pending {
for _, pendingID := range pendingIDs { for _, pendingID := range txp.ids {
pcache[pendingID] = true pcache[pendingID] = true
} }
} }
@ -244,8 +325,8 @@ func (f *freelist) reindex() {
for _, id := range f.ids { for _, id := range f.ids {
f.cache[id] = true f.cache[id] = true
} }
for _, pendingIDs := range f.pending { for _, txp := range f.pending {
for _, pendingID := range pendingIDs { for _, pendingID := range txp.ids {
f.cache[pendingID] = true f.cache[pendingID] = true
} }
} }

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"bytes" "bytes"
@ -365,7 +365,7 @@ func (n *node) spill() error {
} }
// Allocate contiguous space for the node. // Allocate contiguous space for the node.
p, err := tx.allocate((node.size() / tx.db.pageSize) + 1) p, err := tx.allocate((node.size() + tx.db.pageSize - 1) / tx.db.pageSize)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package bolt package bbolt
import ( import (
"fmt" "fmt"
@ -126,10 +126,7 @@ func (tx *Tx) DeleteBucket(name []byte) error {
// the error is returned to the caller. // the error is returned to the caller.
func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error { func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error {
return tx.root.ForEach(func(k, v []byte) error { return tx.root.ForEach(func(k, v []byte) error {
if err := fn(k, tx.root.Bucket(k)); err != nil { return fn(k, tx.root.Bucket(k))
return err
}
return nil
}) })
} }
@ -169,28 +166,18 @@ func (tx *Tx) Commit() error {
// Free the old root bucket. // Free the old root bucket.
tx.meta.root.root = tx.root.root tx.meta.root.root = tx.root.root
opgid := tx.meta.pgid // Free the old freelist because commit writes out a fresh freelist.
if tx.meta.freelist != pgidNoFreelist {
// Free the freelist and allocate new pages for it. This will overestimate tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
// the size of the freelist but not underestimate the size (which would be bad).
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
p, err := tx.allocate((tx.db.freelist.size() / 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
// If the high water mark has moved up then attempt to grow the database. if !tx.db.NoFreelistSync {
if tx.meta.pgid > opgid { err := tx.commitFreelist()
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil { if err != nil {
tx.rollback()
return err return err
} }
} else {
tx.meta.freelist = pgidNoFreelist
} }
// Write dirty pages to disk. // Write dirty pages to disk.
@ -235,6 +222,31 @@ func (tx *Tx) Commit() error {
return nil return nil
} }
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).
opgid := tx.meta.pgid
p, err := tx.allocate((tx.db.freelist.size() / 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
// If the high water mark has moved up then attempt to grow the database.
if tx.meta.pgid > opgid {
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
tx.rollback()
return err
}
}
return nil
}
// Rollback closes the transaction and ignores all previous updates. Read-only // Rollback closes the transaction and ignores all previous updates. Read-only
// transactions must be rolled back and not committed. // transactions must be rolled back and not committed.
func (tx *Tx) Rollback() error { func (tx *Tx) Rollback() error {
@ -291,7 +303,9 @@ func (tx *Tx) close() {
} }
// Copy writes the entire database to a writer. // Copy writes the entire database to a writer.
// This function exists for backwards compatibility. Use WriteTo() instead. // This function exists for backwards compatibility.
//
// Deprecated; Use WriteTo() instead.
func (tx *Tx) Copy(w io.Writer) error { func (tx *Tx) Copy(w io.Writer) error {
_, err := tx.WriteTo(w) _, err := tx.WriteTo(w)
return err return err
@ -305,7 +319,11 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer func() { _ = f.Close() }() defer func() {
if cerr := f.Close(); err == nil {
err = cerr
}
}()
// Generate a meta page. We use the same page data for both meta pages. // Generate a meta page. We use the same page data for both meta pages.
buf := make([]byte, tx.db.pageSize) buf := make([]byte, tx.db.pageSize)
@ -333,7 +351,7 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
} }
// Move past the meta pages in the file. // Move past the meta pages in the file.
if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil { if _, err := f.Seek(int64(tx.db.pageSize*2), io.SeekStart); err != nil {
return n, fmt.Errorf("seek: %s", err) return n, fmt.Errorf("seek: %s", err)
} }
@ -344,7 +362,7 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
return n, err return n, err
} }
return n, f.Close() return n, nil
} }
// CopyFile copies the entire database to file at the given path. // CopyFile copies the entire database to file at the given path.
@ -379,6 +397,9 @@ func (tx *Tx) Check() <-chan error {
} }
func (tx *Tx) check(ch chan error) { func (tx *Tx) check(ch chan error) {
// Force loading free list if opened in ReadOnly mode.
tx.db.loadFreelist()
// Check if any pages are double freed. // Check if any pages are double freed.
freed := make(map[pgid]bool) freed := make(map[pgid]bool)
all := make([]pgid, tx.db.freelist.count()) all := make([]pgid, tx.db.freelist.count())
@ -394,8 +415,10 @@ func (tx *Tx) check(ch chan error) {
reachable := make(map[pgid]*page) reachable := make(map[pgid]*page)
reachable[0] = tx.page(0) // meta0 reachable[0] = tx.page(0) // meta0
reachable[1] = tx.page(1) // meta1 reachable[1] = tx.page(1) // meta1
for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ { if tx.meta.freelist != pgidNoFreelist {
reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist) for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ {
reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist)
}
} }
// Recursively check buckets. // Recursively check buckets.
@ -453,7 +476,7 @@ func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bo
// allocate returns a contiguous block of memory starting at a given page. // allocate returns a contiguous block of memory starting at a given page.
func (tx *Tx) allocate(count int) (*page, error) { func (tx *Tx) allocate(count int) (*page, error) {
p, err := tx.db.allocate(count) p, err := tx.db.allocate(tx.meta.txid, count)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -462,7 +485,7 @@ func (tx *Tx) allocate(count int) (*page, error) {
tx.pages[p.id] = p tx.pages[p.id] = p
// Update statistics. // Update statistics.
tx.stats.PageCount++ tx.stats.PageCount += count
tx.stats.PageAlloc += count * tx.db.pageSize tx.stats.PageAlloc += count * tx.db.pageSize
return p, nil return p, nil