diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index a3ee45ba7f..a0a5c95588 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -286,29 +286,29 @@ func ImportPreimages(db ethdb.Database, fn string) error { // ExportPreimages exports all known hash preimages into the specified file, // truncating any data already present in the file. func ExportPreimages(db ethdb.Database, fn string) error { - log.Info("Exporting preimages", "file", fn) + log.Info("Not exporting preimages", "file", fn) - // Open the file handle and potentially wrap with a gzip stream - fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } - defer fh.Close() - - var writer io.Writer = fh - if strings.HasSuffix(fn, ".gz") { - writer = gzip.NewWriter(writer) - defer writer.(*gzip.Writer).Close() - } - // Iterate over the preimages and export them - it := db.NewIteratorWithPrefix([]byte("secure-key-")) - defer it.Release() - - for it.Next() { - if err := rlp.Encode(writer, it.Value()); err != nil { - return err - } - } - log.Info("Exported preimages", "file", fn) + //// Open the file handle and potentially wrap with a gzip stream + //fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + //if err != nil { + // return err + //} + //defer fh.Close() + // + //var writer io.Writer = fh + //if strings.HasSuffix(fn, ".gz") { + // writer = gzip.NewWriter(writer) + // defer writer.(*gzip.Writer).Close() + //} + //// Iterate over the preimages and export them + //it := db.NewIteratorWithPrefix([]byte("secure-key-")) + //defer it.Release() + // + //for it.Next() { + // if err := rlp.Encode(writer, it.Value()); err != nil { + // return err + // } + //} + //log.Info("Exported preimages", "file", fn) return nil } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 353b7dce62..a936b0c50e 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/leveldb" + "github.com/ethereum/go-ethereum/ethdb/pogreb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/log" "github.com/olekukonko/tablewriter" @@ -197,7 +197,7 @@ func NewMemoryDatabaseWithCap(size int) ethdb.Database { // NewLevelDBDatabase creates a persistent key-value database without a freezer // moving immutable chain segments into cold storage. func NewLevelDBDatabase(file string, cache int, handles int, namespace string) (ethdb.Database, error) { - db, err := leveldb.New(file, cache, handles, namespace) + db, err := pogreb.New(file, cache) if err != nil { return nil, err } @@ -207,7 +207,8 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string) ( // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a // freezer moving immutable chain segments into cold storage. func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { - kvdb, err := leveldb.New(file, cache, handles, namespace) + //kvdb, err := leveldb.New(file, cache, handles, namespace) + kvdb, err := pogreb.New(file, cache) if err != nil { return nil, err } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 41677fbba2..78620dc1b7 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -86,10 +86,16 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { ) // Ensure the datadir is not a symbolic link if it exists. if info, err := os.Lstat(datadir); !os.IsNotExist(err) { - if info.Mode()&os.ModeSymlink != 0 { + if info != nil { + fmt.Printf("Error %v \n", err) + } + if info != nil && info.Mode()&os.ModeSymlink != 0 { log.Warn("Symbolic link ancient database is not supported", "path", datadir) return nil, errSymlinkDatadir } + //panic(err) + //return nil, err + } // Leveldb uses LOCK as the filelock filename. To prevent the // name collision, we use FLOCK as the lock name. diff --git a/vendor/github.com/akrylysov/pogreb/CHANGELOG.md b/vendor/github.com/akrylysov/pogreb/CHANGELOG.md new file mode 100644 index 0000000000..aec0639bf6 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [0.8.1] - 2019-06-30 +### Fixed +- Fix panic when accessing closed database. +- Return error opening invalid database. + +## [0.8] - 2019-03-30 +### Changed +- ~2x write performance improvement on non-Windows. + +## [0.7] - 2019-03-23 +### Added +- Windows support (@mattn). +### Changed +- Improve freelist performance. + diff --git a/vendor/github.com/akrylysov/pogreb/LICENSE b/vendor/github.com/akrylysov/pogreb/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/akrylysov/pogreb/README.md b/vendor/github.com/akrylysov/pogreb/README.md new file mode 100644 index 0000000000..a55da699ea --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/README.md @@ -0,0 +1,98 @@ +

+ +# Pogreb [![GoDoc](https://godoc.org/github.com/akrylysov/pogreb?status.svg)](https://godoc.org/github.com/akrylysov/pogreb) [![Build Status](https://travis-ci.com/akrylysov/pogreb.svg?branch=master)](https://travis-ci.com/akrylysov/pogreb) [![Windows Build status](https://ci.appveyor.com/api/projects/status/sg77adt2eqx9jgga/branch/master?svg=true)](https://ci.appveyor.com/project/akrylysov/pogreb/branch/master) [![Go Report Card](https://goreportcard.com/badge/github.com/akrylysov/pogreb)](https://goreportcard.com/report/github.com/akrylysov/pogreb) + +Pogreb is an embedded key-value store for read-heavy workloads written in Go. + +## Key characteristics + +- 100% Go. +- Optimized for fast random lookups and infrequent bulk inserts. +- Can store larger-than-memory data sets. +- Low memory usage. +- All DB methods are safe for concurrent use by multiple goroutines. + +## Installation + +```sh +$ go get -u github.com/akrylysov/pogreb +``` + +## Usage + +### Opening a database + +To open or create a new database, use the `pogreb.Open()` function: + +```go +package main + +import ( + "log" + + "github.com/akrylysov/pogreb" +) + +func main() { + db, err := pogreb.Open("pogreb.test", nil) + if err != nil { + log.Fatal(err) + return + } + defer db.Close() +} +``` + +### Writing to a database + +Use the `DB.Put()` function to insert a new key/value pair: + +```go +err := db.Put([]byte("testKey"), []byte("testValue")) +if err != nil { + log.Fatal(err) +} +``` + +### Reading from a database + +Use the `DB.Get()` function to retrieve the inserted value: + +```go +val, err := db.Get([]byte("testKey")) +if err != nil { + log.Fatal(err) +} +log.Printf("%s", val) +``` + +### Iterating over items + +Use the `DB.Items()` function which returns a new instance of `ItemIterator`: + +```go +it := db.Items() +for { + key, val, err := it.Next() + if err != nil { + if err != pogreb.ErrIterationDone { + log.Fatal(err) + } + break + } + log.Printf("%s %s", key, val) +} +``` + +## Performance + +The benchmarking code can be found in the [pogreb-bench](https://github.com/akrylysov/pogreb-bench) repository. + +Results of read performance benchmark of pogreb, goleveldb, bolt and badgerdb +on DigitalOcean 8 CPUs / 16 GB RAM / 160 GB SSD + Ubuntu 16.04.3 (higher is better): + +

+ +## Internals + +[Pogreb - how it works](https://artem.krylysov.com/blog/2018/03/24/pogreb-key-value-store/). diff --git a/vendor/github.com/akrylysov/pogreb/appveyor.yml b/vendor/github.com/akrylysov/pogreb/appveyor.yml new file mode 100644 index 0000000000..0c9e1be20b --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/appveyor.yml @@ -0,0 +1,15 @@ +version: "{build}" + +clone_folder: c:\gopath\src\github.com\akrylysov/pogreb + +environment: + GOPATH: c:\gopath + +install: + - echo %PATH% + - echo %GOPATH% + - go version + - go env + +build_script: + - go test -v ./... diff --git a/vendor/github.com/akrylysov/pogreb/bucket.go b/vendor/github.com/akrylysov/pogreb/bucket.go new file mode 100644 index 0000000000..182ddfbf45 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/bucket.go @@ -0,0 +1,121 @@ +package pogreb + +import ( + "encoding/binary" + + "github.com/akrylysov/pogreb/fs" +) + +type slot struct { + hash uint32 + keySize uint16 + valueSize uint32 + kvOffset int64 +} + +func (sl slot) kvSize() uint32 { + return uint32(sl.keySize) + sl.valueSize +} + +type bucket struct { + slots [slotsPerBucket]slot + next int64 +} + +type bucketHandle struct { + bucket + file fs.MmapFile + offset int64 +} + +const ( + bucketSize uint32 = 512 +) + +func align512(n uint32) uint32 { + return (n + 511) &^ 511 +} + +func (b bucket) MarshalBinary() ([]byte, error) { + buf := make([]byte, bucketSize) + data := buf + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + binary.LittleEndian.PutUint32(buf[:4], sl.hash) + binary.LittleEndian.PutUint16(buf[4:6], sl.keySize) + binary.LittleEndian.PutUint32(buf[6:10], sl.valueSize) + binary.LittleEndian.PutUint64(buf[10:18], uint64(sl.kvOffset)) + buf = buf[18:] + } + binary.LittleEndian.PutUint64(buf[:8], uint64(b.next)) + return data, nil +} + +func (b *bucket) UnmarshalBinary(data []byte) error { + for i := 0; i < slotsPerBucket; i++ { + _ = data[18] // bounds check hint to compiler; see golang.org/issue/14808 + b.slots[i].hash = binary.LittleEndian.Uint32(data[:4]) + b.slots[i].keySize = binary.LittleEndian.Uint16(data[4:6]) + b.slots[i].valueSize = binary.LittleEndian.Uint32(data[6:10]) + b.slots[i].kvOffset = int64(binary.LittleEndian.Uint64(data[10:18])) + data = data[18:] + } + b.next = int64(binary.LittleEndian.Uint64(data[:8])) + return nil +} + +func (b *bucket) del(slotIdx int) { + i := slotIdx + for ; i < slotsPerBucket-1; i++ { + b.slots[i] = b.slots[i+1] + } + b.slots[i] = slot{} +} + +func (b *bucketHandle) read() error { + buf, err := b.file.Slice(b.offset, b.offset+int64(bucketSize)) + if err != nil { + return err + } + return b.UnmarshalBinary(buf) +} + +func (b *bucketHandle) write() error { + buf, err := b.MarshalBinary() + if err != nil { + return err + } + _, err = b.file.WriteAt(buf, b.offset) + return err +} + +type slotWriter struct { + bucket *bucketHandle + slotIdx int + prevBuckets []*bucketHandle +} + +func (sw *slotWriter) insert(sl slot, db *DB) error { + if sw.slotIdx == slotsPerBucket { + nextBucket, err := db.createOverflowBucket() + if err != nil { + return err + } + sw.bucket.next = nextBucket.offset + sw.prevBuckets = append(sw.prevBuckets, sw.bucket) + sw.bucket = nextBucket + sw.slotIdx = 0 + } + sw.bucket.slots[sw.slotIdx] = sl + sw.slotIdx++ + return nil +} + +func (sw *slotWriter) write() error { + for i := len(sw.prevBuckets) - 1; i >= 0; i-- { + if err := sw.prevBuckets[i].write(); err != nil { + return err + } + } + return sw.bucket.write() +} diff --git a/vendor/github.com/akrylysov/pogreb/datafile.go b/vendor/github.com/akrylysov/pogreb/datafile.go new file mode 100644 index 0000000000..f57dda247b --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/datafile.go @@ -0,0 +1,47 @@ +package pogreb + +type dataFile struct { + file + fl freelist +} + +func (f *dataFile) readKeyValue(sl slot) ([]byte, []byte, error) { + keyValue, err := f.Slice(sl.kvOffset, sl.kvOffset+int64(sl.kvSize())) + if err != nil { + return nil, nil, err + } + return keyValue[:sl.keySize], keyValue[sl.keySize:], nil +} + +func (f *dataFile) readKey(sl slot) ([]byte, error) { + return f.Slice(sl.kvOffset, sl.kvOffset+int64(sl.keySize)) +} + +func (f *dataFile) allocate(size uint32) (int64, error) { + size = align512(size) + if off := f.fl.allocate(size); off > 0 { + return off, nil + } + return f.extend(size) +} + +func (f *dataFile) free(size uint32, off int64) { + size = align512(size) + f.fl.free(off, size) +} + +func (f *dataFile) writeKeyValue(key []byte, value []byte) (int64, error) { + dataLen := align512(uint32(len(key) + len(value))) + data := make([]byte, dataLen) + copy(data, key) + copy(data[len(key):], value) + off := f.fl.allocate(dataLen) + if off != -1 { + if _, err := f.WriteAt(data, off); err != nil { + return 0, err + } + } else { + return f.append(data) + } + return off, nil +} diff --git a/vendor/github.com/akrylysov/pogreb/db.go b/vendor/github.com/akrylysov/pogreb/db.go new file mode 100644 index 0000000000..967bbf07c9 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/db.go @@ -0,0 +1,568 @@ +package pogreb + +import ( + "bytes" + "context" + "math" + "os" + "sync" + "time" + + "github.com/akrylysov/pogreb/fs" + "github.com/akrylysov/pogreb/hash" +) + +const ( + slotsPerBucket = 28 + loadFactor = 0.7 + indexPostfix = ".index" + lockPostfix = ".lock" + version = 1 // file format version + + // MaxKeyLength is the maximum size of a key in bytes. + MaxKeyLength = 1 << 16 + + // MaxValueLength is the maximum size of a value in bytes. + MaxValueLength = 1 << 30 + + // MaxKeys is the maximum numbers of keys in the DB. + MaxKeys = math.MaxUint32 +) + +type dbInfo struct { + level uint8 + count uint32 + nBuckets uint32 + splitBucketIdx uint32 + freelistOff int64 + hashSeed uint32 +} + +// DB represents the key-value storage. +// All DB methods are safe for concurrent use by multiple goroutines. +type DB struct { + mu sync.RWMutex + index file + data dataFile + lock fs.LockFile + metrics Metrics + cancelSyncer context.CancelFunc + syncWrites bool + dbInfo +} + +// Open opens or creates a new DB. +func Open(path string, opts *Options) (*DB, error) { + opts = opts.copyWithDefaults() + fileFlag := os.O_CREATE | os.O_RDWR + fsys := opts.FileSystem + fileMode := os.FileMode(0666) + lock, needsRecovery, err := fsys.CreateLockFile(path+lockPostfix, fileMode) + if err != nil { + if err == os.ErrExist { + err = errLocked + } + return nil, err + } + index, err := openFile(fsys, path+indexPostfix, fileFlag, fileMode) + if err != nil { + return nil, err + } + data, err := openFile(fsys, path, fileFlag, fileMode) + if err != nil { + return nil, err + } + db := &DB{ + index: index, + data: dataFile{file: data}, + lock: lock, + metrics: newMetrics(), + dbInfo: dbInfo{ + nBuckets: 1, + freelistOff: -1, + }, + } + if index.size == 0 { + if data.size != 0 { + if err := index.Close(); err != nil { + logger.Print(err) + } + if err := data.Close(); err != nil { + logger.Print(err) + } + if err := lock.Unlock(); err != nil { + logger.Print(err) + } + // Data file exists, but index is missing. + return nil, errCorrupted + } + seed, err := hash.RandSeed() + if err != nil { + return nil, err + } + db.hashSeed = seed + if _, err = db.index.extend(headerSize + bucketSize); err != nil { + return nil, err + } + if _, err = db.data.extend(headerSize); err != nil { + return nil, err + } + if err := db.writeHeader(); err != nil { + return nil, err + } + } else { + if err := db.readHeader(!needsRecovery); err != nil { + if err := index.Close(); err != nil { + logger.Print(err) + } + if err := data.Close(); err != nil { + logger.Print(err) + } + if err := lock.Unlock(); err != nil { + logger.Print(err) + } + return nil, err + } + } + if needsRecovery { + if err := db.recover(); err != nil { + return nil, err + } + } + if opts.BackgroundSyncInterval > 0 { + db.startSyncer(opts.BackgroundSyncInterval) + } else if opts.BackgroundSyncInterval == -1 { + db.syncWrites = true + } + return db, nil +} + +func bucketOffset(idx uint32) int64 { + return int64(headerSize) + (int64(bucketSize) * int64(idx)) +} + +func (db *DB) startSyncer(interval time.Duration) { + ctx, cancel := context.WithCancel(context.Background()) + db.cancelSyncer = cancel + go func() { + var lastModifications int64 + for { + select { + case <-ctx.Done(): + return + default: + modifications := db.metrics.Puts.Value() + db.metrics.Dels.Value() + if modifications != lastModifications { + if err := db.Sync(); err != nil { + logger.Printf("Error synchronizing databse: %v", err) + } + lastModifications = modifications + } + time.Sleep(interval) + } + } + }() +} + +func (db *DB) forEachBucket(startBucketIdx uint32, cb func(bucketHandle) (bool, error)) error { + off := bucketOffset(startBucketIdx) + f := db.index.MmapFile + for { + b := bucketHandle{file: f, offset: off} + if err := b.read(); err != nil { + return err + } + if stop, err := cb(b); stop || err != nil { + return err + } + if b.next == 0 { + return nil + } + off = b.next + f = db.data.MmapFile + db.metrics.BucketProbes.Add(1) + } +} + +func (db *DB) createOverflowBucket() (*bucketHandle, error) { + off, err := db.data.allocate(bucketSize) + if err != nil { + return nil, err + } + return &bucketHandle{file: db.data, offset: off}, nil +} + +func (db *DB) writeHeader() error { + db.data.fl.defrag() + freelistOff, err := db.data.fl.write(db.data.file) + if err != nil { + return err + } + db.dbInfo.freelistOff = freelistOff + h := header{ + signature: signature, + version: version, + dbInfo: db.dbInfo, + } + return db.index.writeMarshalableAt(h, 0) +} + +func (db *DB) readHeader(readFreeList bool) error { + h := &header{} + if err := db.index.readUnmarshalableAt(h, headerSize, 0); err != nil { + return err + } + if !bytes.Equal(h.signature[:], signature[:]) { + return errCorrupted + } + db.dbInfo = h.dbInfo + if readFreeList { + if err := db.data.fl.read(db.data.file, db.dbInfo.freelistOff); err != nil { + return err + } + } + db.dbInfo.freelistOff = -1 + return nil +} + +// Close closes the DB. +func (db *DB) Close() error { + db.mu.Lock() + defer db.mu.Unlock() + if db.cancelSyncer != nil { + db.cancelSyncer() + } + if err := db.writeHeader(); err != nil { + return err + } + if err := db.data.Close(); err != nil { + return err + } + if err := db.index.Close(); err != nil { + return err + } + if err := db.lock.Unlock(); err != nil { + return err + } + return nil +} + +func (db *DB) bucketIndex(hash uint32) uint32 { + idx := hash & ((1 << db.level) - 1) + if idx < db.splitBucketIdx { + return hash & ((1 << (db.level + 1)) - 1) + } + return idx +} + +func (db *DB) hash(data []byte) uint32 { + return hash.Sum32WithSeed(data, db.hashSeed) +} + +// Get returns the value for the given key stored in the DB or nil if the key doesn't exist. +func (db *DB) Get(key []byte) ([]byte, error) { + h := db.hash(key) + db.metrics.Gets.Add(1) + db.mu.RLock() + defer db.mu.RUnlock() + var retValue []byte + err := db.forEachBucket(db.bucketIndex(h), func(b bucketHandle) (bool, error) { + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + if sl.kvOffset == 0 { + return b.next == 0, nil + } else if h == sl.hash && uint16(len(key)) == sl.keySize { + slKey, value, err := db.data.readKeyValue(sl) + if err != nil { + return true, err + } + if bytes.Equal(key, slKey) { + retValue = value + return true, nil + } + db.metrics.HashCollisions.Add(1) + } + } + return false, nil + }) + if err != nil { + return nil, err + } + return retValue, nil +} + +// Has returns true if the DB contains the given key. +func (db *DB) Has(key []byte) (bool, error) { + h := db.hash(key) + found := false + db.mu.RLock() + defer db.mu.RUnlock() + err := db.forEachBucket(db.bucketIndex(h), func(b bucketHandle) (bool, error) { + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + if sl.kvOffset == 0 { + return b.next == 0, nil + } else if h == sl.hash && uint16(len(key)) == sl.keySize { + slKey, err := db.data.readKey(sl) + if err != nil { + return true, err + } + if bytes.Equal(key, slKey) { + found = true + return true, nil + } + } + } + return false, nil + }) + if err != nil { + return false, err + } + return found, nil +} + +// Items returns a new ItemIterator. +func (db *DB) Items() *ItemIterator { + return &ItemIterator{db: db} +} + +func (db *DB) sync() error { + if err := db.data.Sync(); err != nil { + return err + } + if err := db.index.Sync(); err != nil { + return err + } + return nil +} + +// Sync commits the contents of the database to the backing FileSystem; this is effectively a noop for an in-memory database. It must only be called while the database is opened. +func (db *DB) Sync() error { + db.mu.Lock() + defer db.mu.Unlock() + return db.sync() +} + +// Put sets the value for the given key. It updates the value for the existing key. +func (db *DB) Put(key []byte, value []byte) error { + switch { + case len(key) == 0: + return errKeyEmpty + case len(key) > MaxKeyLength: + return errKeyTooLarge + case len(value) > MaxValueLength: + return errValueTooLarge + } + h := db.hash(key) + db.metrics.Puts.Add(1) + db.mu.Lock() + defer db.mu.Unlock() + if err := db.put(h, key, value); err != nil { + return err + } + if float64(db.count)/float64(db.nBuckets*slotsPerBucket) > loadFactor { + if err := db.split(); err != nil { + return err + } + } + if db.syncWrites { + return db.sync() + } + return nil +} + +func (db *DB) put(hash uint32, key []byte, value []byte) error { + var b *bucketHandle + var originalB *bucketHandle + slotIdx := 0 + err := db.forEachBucket(db.bucketIndex(hash), func(curb bucketHandle) (bool, error) { + b = &curb + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + slotIdx = i + if sl.kvOffset == 0 { + // Found an empty slot. + return true, nil + } else if hash == sl.hash && uint16(len(key)) == sl.keySize { + // Key already exists. + if slKey, err := db.data.readKey(sl); bytes.Equal(key, slKey) || err != nil { + return true, err + } + } + } + if b.next == 0 { + // Couldn't find free space in the current bucketHandle, creating a new overflow bucketHandle. + nextBucket, err := db.createOverflowBucket() + if err != nil { + return false, err + } + b.next = nextBucket.offset + originalB = b + b = nextBucket + slotIdx = 0 + return true, nil + } + return false, nil + }) + if err != nil { + return err + } + + // Inserting a new item. + if b.slots[slotIdx].kvOffset == 0 { + if db.count == MaxKeys { + return errFull + } + db.count++ + } else { + defer db.data.free(b.slots[slotIdx].kvSize(), b.slots[slotIdx].kvOffset) + } + + b.slots[slotIdx] = slot{ + hash: hash, + keySize: uint16(len(key)), + valueSize: uint32(len(value)), + } + if b.slots[slotIdx].kvOffset, err = db.data.writeKeyValue(key, value); err != nil { + return err + } + if err := b.write(); err != nil { + return err + } + if originalB != nil { + return originalB.write() + } + return nil +} + +func (db *DB) split() error { + updatedBucketIdx := db.splitBucketIdx + updatedBucketOff := bucketOffset(updatedBucketIdx) + updatedBucket := slotWriter{ + bucket: &bucketHandle{file: db.index, offset: updatedBucketOff}, + } + + newBucketOff, err := db.index.extend(bucketSize) + if err != nil { + return err + } + newBucket := slotWriter{ + bucket: &bucketHandle{file: db.index, offset: newBucketOff}, + } + + db.splitBucketIdx++ + if db.splitBucketIdx == 1<= size + }) +} + +func (fl *freelist) free(off int64, size uint32) { + if size == 0 { + panic("unable to free zero bytes") + } + i := fl.search(size) + if i < len(fl.blocks) && off == fl.blocks[i].offset { + panic("freeing already freed offset") + } + + fl.blocks = append(fl.blocks, block{}) + copy(fl.blocks[i+1:], fl.blocks[i:]) + fl.blocks[i] = block{offset: off, size: size} +} + +func (fl *freelist) allocate(size uint32) int64 { + if size == 0 { + panic("unable to allocate zero bytes") + } + i := fl.search(size) + if i >= len(fl.blocks) { + return -1 + } + off := fl.blocks[i].offset + if fl.blocks[i].size == size { + copy(fl.blocks[i:], fl.blocks[i+1:]) + fl.blocks[len(fl.blocks)-1] = block{} + fl.blocks = fl.blocks[:len(fl.blocks)-1] + } else { + fl.blocks[i].size -= size + fl.blocks[i].offset += int64(size) + } + return off +} + +func (fl *freelist) defrag() { + if len(fl.blocks) <= 1 { + return + } + sort.Slice(fl.blocks, func(i, j int) bool { + return fl.blocks[i].offset < fl.blocks[j].offset + }) + var merged []block + curOff := fl.blocks[0].offset + curSize := fl.blocks[0].size + for i := 1; i < len(fl.blocks); i++ { + if curOff+int64(curSize) == fl.blocks[i].offset { + curSize += fl.blocks[i].size + } else { + merged = append(merged, block{size: curSize, offset: curOff}) + curOff = fl.blocks[i].offset + curSize = fl.blocks[i].size + } + } + merged = append(merged, block{offset: curOff, size: curSize}) + sort.Slice(merged, func(i, j int) bool { + return merged[i].size < merged[j].size + }) + fl.blocks = merged +} + +func (fl *freelist) MarshalBinary() ([]byte, error) { + size := fl.binarySize() + buf := make([]byte, size) + data := buf + binary.LittleEndian.PutUint32(data[:4], uint32(len(fl.blocks))) + data = data[4:] + for i := 0; i < len(fl.blocks); i++ { + binary.LittleEndian.PutUint64(data[:8], uint64(fl.blocks[i].offset)) + binary.LittleEndian.PutUint32(data[8:12], fl.blocks[i].size) + data = data[12:] + } + return buf, nil +} + +func (fl *freelist) binarySize() uint32 { + return uint32(4 + (8+4)*len(fl.blocks)) // FIXME: this is ugly +} + +func (fl *freelist) read(f file, off int64) error { + if off == -1 { + return nil + } + buf := make([]byte, 4) + if _, err := f.ReadAt(buf, off); err != nil { + return err + } + n := binary.LittleEndian.Uint32(buf) + buf = make([]byte, (4+8)*n) + if _, err := f.ReadAt(buf, off+4); err != nil { + return err + } + for i := uint32(0); i < n; i++ { + blockOff := int64(binary.LittleEndian.Uint64(buf[:8])) + blockSize := binary.LittleEndian.Uint32(buf[8:12]) + if blockOff != 0 { + fl.blocks = append(fl.blocks, block{size: blockSize, offset: blockOff}) + } + buf = buf[12:] + } + fl.free(off, align512(4+(4+8)*n)) + return nil +} + +func (fl *freelist) write(f file) (int64, error) { + if len(fl.blocks) == 0 { + return -1, nil + } + marshaledSize := align512(fl.binarySize()) + i := fl.search(marshaledSize) + var off int64 + if i < len(fl.blocks) { + off = fl.blocks[i].offset + fl.blocks[i] = block{} + } else { + var err error + off, err = f.extend(marshaledSize) + if err != nil { + return -1, err + } + } + buf, err := fl.MarshalBinary() + if err != nil { + return -1, err + } + _, err = f.WriteAt(buf, off) + return off, err +} diff --git a/vendor/github.com/akrylysov/pogreb/fs/file.go b/vendor/github.com/akrylysov/pogreb/fs/file.go new file mode 100644 index 0000000000..bf3fa08401 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/fs/file.go @@ -0,0 +1,40 @@ +package fs + +import ( + "io" + "os" +) + +// File is the interface compatible with os.File. +type File interface { + io.Closer + io.Reader + io.ReaderAt + io.Seeker + io.Writer + io.WriterAt + + Stat() (os.FileInfo, error) + Sync() error + Truncate(size int64) error +} + +// MmapFile represents a memory-mapped file. +type MmapFile interface { + File + Mmap(size int64) error + Slice(start int64, end int64) ([]byte, error) +} + +// LockFile represents a lock file. +type LockFile interface { + Unlock() error +} + +// FileSystem represents a virtual file system. +type FileSystem interface { + OpenFile(name string, flag int, perm os.FileMode) (MmapFile, error) + CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) + Stat(name string) (os.FileInfo, error) + Remove(name string) error +} diff --git a/vendor/github.com/akrylysov/pogreb/fs/mem.go b/vendor/github.com/akrylysov/pogreb/fs/mem.go new file mode 100644 index 0000000000..4ed30706a1 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/fs/mem.go @@ -0,0 +1,201 @@ +package fs + +import ( + "io" + "os" + "time" +) + +type memfs struct { + files map[string]*memfile +} + +// Mem is a file system backed by memory. +var Mem = &memfs{files: map[string]*memfile{}} + +func (fs *memfs) OpenFile(name string, flag int, perm os.FileMode) (MmapFile, error) { + // TODO: respect flag + f := fs.files[name] + if f == nil { + f = &memfile{} + fs.files[name] = f + } else if !f.closed { + return nil, os.ErrExist + } else { + f.closed = false + } + return f, nil +} + +func (fs *memfs) CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) { + f, err := fs.OpenFile(name, 0, perm) + if err != nil { + return nil, false, err + } + return &memlockfile{f, name}, false, nil +} + +func (fs *memfs) Stat(name string) (os.FileInfo, error) { + if f, ok := fs.files[name]; ok { + return f, nil + } + return nil, os.ErrNotExist +} + +func (fs *memfs) Remove(name string) error { + if _, ok := fs.files[name]; ok { + delete(fs.files, name) + return nil + } + return os.ErrNotExist +} + +type memlockfile struct { + File + name string +} + +func (f *memlockfile) Unlock() error { + if err := f.Close(); err != nil { + return err + } + return Mem.Remove(f.name) +} + +type memfile struct { + buf []byte + size int64 + offset int64 + closed bool +} + +func (m *memfile) Close() error { + if m.closed { + return os.ErrClosed + } + m.closed = true + return nil +} + +func (m *memfile) ReadAt(p []byte, off int64) (int, error) { + if m.closed { + return 0, os.ErrClosed + } + n := len(p) + if int64(n) > m.size-off { + return 0, io.EOF + } + copy(p, m.buf[off:off+int64(n)]) + return n, nil +} + +func (m *memfile) Read(p []byte) (int, error) { + n, err := m.ReadAt(p, m.offset) + if err != nil { + return n, err + } + m.offset += int64(n) + return n, err +} + +func (m *memfile) WriteAt(p []byte, off int64) (int, error) { + if m.closed { + return 0, os.ErrClosed + } + n := len(p) + if off == m.size { + m.buf = append(m.buf, p...) + m.size += int64(n) + } else if off+int64(n) > m.size { + panic("trying to write past EOF - undefined behavior") + } else { + copy(m.buf[off:off+int64(n)], p) + } + return n, nil +} + +func (m *memfile) Write(p []byte) (int, error) { + n, err := m.WriteAt(p, m.offset) + if err != nil { + return n, err + } + m.offset += int64(n) + return n, err +} + +func (m *memfile) Seek(offset int64, whence int) (int64, error) { + if m.closed { + return 0, os.ErrClosed + } + if whence == io.SeekEnd { + m.offset = m.size + offset + } else if whence == io.SeekStart { + m.offset = offset + } else if whence == io.SeekCurrent { + m.offset += offset + } + return m.offset, nil +} + +func (m *memfile) Stat() (os.FileInfo, error) { + if m.closed { + return m, os.ErrClosed + } + return m, nil +} + +func (m *memfile) Sync() error { + if m.closed { + return os.ErrClosed + } + return nil +} + +func (m *memfile) Truncate(size int64) error { + if m.closed { + return os.ErrClosed + } + if size > m.size { + diff := int(size - m.size) + m.buf = append(m.buf, make([]byte, diff)...) + } else { + m.buf = m.buf[:m.size] + } + m.size = size + return nil +} + +func (m *memfile) Name() string { + return "" +} + +func (m *memfile) Size() int64 { + return m.size +} + +func (m *memfile) Mode() os.FileMode { + return os.FileMode(0) +} + +func (m *memfile) ModTime() time.Time { + return time.Now() +} + +func (m *memfile) IsDir() bool { + return false +} + +func (m *memfile) Sys() interface{} { + return nil +} + +func (m *memfile) Slice(start int64, end int64) ([]byte, error) { + if m.closed { + return nil, os.ErrClosed + } + return m.buf[start:end], nil +} + +func (m *memfile) Mmap(size int64) error { + return nil +} diff --git a/vendor/github.com/akrylysov/pogreb/fs/os.go b/vendor/github.com/akrylysov/pogreb/fs/os.go new file mode 100644 index 0000000000..2cfaf46767 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/fs/os.go @@ -0,0 +1,110 @@ +package fs + +import ( + "os" +) + +const ( + initialMmapSize = 1024 << 20 +) + +type osfile struct { + *os.File + data []byte + mmapSize int64 +} + +type osfs struct{} + +// OS is a file system backed by the os package. +var OS = &osfs{} + +func (fs *osfs) OpenFile(name string, flag int, perm os.FileMode) (MmapFile, error) { + f, err := os.OpenFile(name, flag, perm) + if err != nil { + return nil, err + } + stat, err := f.Stat() + if err != nil { + return nil, err + } + mf := &osfile{f, nil, 0} + if stat.Size() > 0 { + if err := mf.Mmap(stat.Size()); err != nil { + return nil, err + } + } + return mf, err +} + +func (fs *osfs) CreateLockFile(name string, perm os.FileMode) (LockFile, bool, error) { + return createLockFile(name, perm) +} + +func (fs *osfs) Stat(name string) (os.FileInfo, error) { + return os.Stat(name) +} + +func (fs *osfs) Remove(name string) error { + return os.Remove(name) +} + +type oslockfile struct { + File + path string +} + +func (f *oslockfile) Unlock() error { + if err := os.Remove(f.path); err != nil { + return err + } + return f.Close() +} + +func (f *osfile) Slice(start int64, end int64) ([]byte, error) { + if f.data == nil { + return nil, os.ErrClosed + } + return f.data[start:end], nil +} + +func (f *osfile) Close() error { + if f.data != nil { + if err := munmap(f.data); err != nil { + return nil + } + f.data = nil + } + return f.File.Close() +} + +func (f *osfile) Mmap(fileSize int64) error { + mmapSize := f.mmapSize + + if mmapSize >= fileSize { + return nil + } + + if mmapSize == 0 { + mmapSize = initialMmapSize + if mmapSize < fileSize { + mmapSize = fileSize + } + } else { + if err := munmap(f.data); err != nil { + return err + } + mmapSize *= 2 + } + + data, mappedSize, err := mmap(f.File, fileSize, mmapSize) + if err != nil { + return err + } + + madviceRandom(data) + + f.data = data + f.mmapSize = mappedSize + return nil +} diff --git a/vendor/github.com/akrylysov/pogreb/fs/os_unix.go b/vendor/github.com/akrylysov/pogreb/fs/os_unix.go new file mode 100644 index 0000000000..73250b5504 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/fs/os_unix.go @@ -0,0 +1,44 @@ +// +build !windows + +package fs + +import ( + "os" + "syscall" + "unsafe" +) + +func mmap(f *os.File, fileSize int64, mmapSize int64) ([]byte, int64, error) { + p, err := syscall.Mmap(int(f.Fd()), 0, int(mmapSize), syscall.PROT_READ, syscall.MAP_SHARED) + return p, mmapSize, err +} + +func munmap(data []byte) error { + return syscall.Munmap(data) +} + +func madviceRandom(data []byte) error { + _, _, errno := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&data[0])), uintptr(len(data)), uintptr(syscall.MADV_RANDOM)) + if errno != 0 { + return errno + } + return nil +} + +func createLockFile(name string, perm os.FileMode) (LockFile, bool, error) { + acquiredExisting := false + if _, err := os.Stat(name); err == nil { + acquiredExisting = true + } + f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, perm) + if err != nil { + return nil, false, err + } + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { + if err == syscall.EWOULDBLOCK { + err = os.ErrExist + } + return nil, false, err + } + return &oslockfile{f, name}, acquiredExisting, nil +} diff --git a/vendor/github.com/akrylysov/pogreb/fs/os_windows.go b/vendor/github.com/akrylysov/pogreb/fs/os_windows.go new file mode 100644 index 0000000000..e1d0f4cc78 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/fs/os_windows.go @@ -0,0 +1,84 @@ +// +build windows + +package fs + +import ( + "os" + "syscall" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + procLockFileEx = modkernel32.NewProc("LockFileEx") +) + +const ( + errorLockViolation = 0x21 +) + +func mmap(f *os.File, fileSize int64, mmapSize int64) ([]byte, int64, error) { + size := fileSize + low, high := uint32(size), uint32(size>>32) + fmap, err := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, syscall.PAGE_READONLY, high, low, nil) + if err != nil { + return nil, 0, err + } + defer syscall.CloseHandle(fmap) + ptr, err := syscall.MapViewOfFile(fmap, syscall.FILE_MAP_READ, 0, 0, uintptr(size)) + if err != nil { + return nil, 0, err + } + data := (*[1 << 30]byte)(unsafe.Pointer(ptr))[:size] + return data, size, nil +} + +func munmap(data []byte) error { + return syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&data[0]))) +} + +func madviceRandom(data []byte) error { + return nil +} + +func lockfile(f *os.File) error { + var ol syscall.Overlapped + + r1, _, err := syscall.Syscall6( + procLockFileEx.Addr(), + 6, + uintptr(f.Fd()), // handle + uintptr(0x0003), + uintptr(0), // reserved + uintptr(1), // locklow + uintptr(0), // lockhigh + uintptr(unsafe.Pointer(&ol)), + ) + if r1 == 0 && (err == syscall.ERROR_FILE_EXISTS || err == errorLockViolation) { + return os.ErrExist + } + return nil +} + +func createLockFile(name string, perm os.FileMode) (LockFile, bool, error) { + acquiredExisting := false + if _, err := os.Stat(name); err == nil { + acquiredExisting = true + } + fd, err := syscall.CreateFile(&(syscall.StringToUTF16(name)[0]), + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, + nil, + syscall.CREATE_ALWAYS, + syscall.FILE_ATTRIBUTE_NORMAL, + 0) + if err != nil { + return nil, false, os.ErrExist + } + f := os.NewFile(uintptr(fd), name) + if err := lockfile(f); err != nil { + f.Close() + return nil, false, err + } + return &oslockfile{f, name}, acquiredExisting, nil +} diff --git a/vendor/github.com/akrylysov/pogreb/go.mod b/vendor/github.com/akrylysov/pogreb/go.mod new file mode 100644 index 0000000000..d2a3a28717 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/go.mod @@ -0,0 +1,3 @@ +module github.com/akrylysov/pogreb + +go 1.12 diff --git a/vendor/github.com/akrylysov/pogreb/hash/murmurhash32.go b/vendor/github.com/akrylysov/pogreb/hash/murmurhash32.go new file mode 100644 index 0000000000..2b0fdb282e --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/hash/murmurhash32.go @@ -0,0 +1,61 @@ +package hash + +import ( + "unsafe" +) + +const ( + c1 uint32 = 0xcc9e2d51 + c2 uint32 = 0x1b873593 +) + +// Sum32WithSeed is a port of MurmurHash3_x86_32 function. +func Sum32WithSeed(data []byte, seed uint32) uint32 { + h1 := seed + + nblocks := len(data) / 4 + var p uintptr + if len(data) > 0 { + p = uintptr(unsafe.Pointer(&data[0])) + } + p1 := p + uintptr(4*nblocks) + for ; p < p1; p += 4 { + k1 := *(*uint32)(unsafe.Pointer(p)) + + k1 *= c1 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2 + + h1 ^= k1 + h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) + h1 = h1*5 + 0xe6546b64 + } + + tail := data[nblocks*4:] + + var k1 uint32 + switch len(tail) & 3 { + case 3: + k1 ^= uint32(tail[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(tail[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(tail[0]) + k1 *= c1 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2 + h1 ^= k1 + } + + h1 ^= uint32(len(data)) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} diff --git a/vendor/github.com/akrylysov/pogreb/hash/seed.go b/vendor/github.com/akrylysov/pogreb/hash/seed.go new file mode 100644 index 0000000000..1fbc5bdfbb --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/hash/seed.go @@ -0,0 +1,15 @@ +package hash + +import ( + "crypto/rand" + "encoding/binary" +) + +// RandSeed generates a random hash seed. +func RandSeed() (uint32, error) { + b := make([]byte, 4) + if _, err := rand.Read(b); err != nil { + return 0, err + } + return binary.LittleEndian.Uint32(b), nil +} diff --git a/vendor/github.com/akrylysov/pogreb/header.go b/vendor/github.com/akrylysov/pogreb/header.go new file mode 100644 index 0000000000..0336e801dc --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/header.go @@ -0,0 +1,46 @@ +package pogreb + +import ( + "encoding/binary" +) + +var ( + signature = [8]byte{'p', 'o', 'g', 'r', 'e', 'b', '\x0e', '\xfd'} + headerSize uint32 +) + +type header struct { + signature [8]byte + version uint32 + dbInfo + _ [256]byte +} + +func init() { + headerSize = align512(uint32(binary.Size(header{}))) +} + +func (h header) MarshalBinary() ([]byte, error) { + buf := make([]byte, headerSize) + copy(buf[:8], h.signature[:]) + binary.LittleEndian.PutUint32(buf[8:12], h.version) + buf[12] = h.level + binary.LittleEndian.PutUint32(buf[13:17], h.count) + binary.LittleEndian.PutUint32(buf[17:21], h.nBuckets) + binary.LittleEndian.PutUint32(buf[21:25], h.splitBucketIdx) + binary.LittleEndian.PutUint64(buf[25:33], uint64(h.freelistOff)) + binary.LittleEndian.PutUint32(buf[33:37], h.hashSeed) + return buf, nil +} + +func (h *header) UnmarshalBinary(data []byte) error { + copy(h.signature[:], data[:8]) + h.version = binary.LittleEndian.Uint32(data[8:12]) + h.level = data[12] + h.count = binary.LittleEndian.Uint32(data[13:17]) + h.nBuckets = binary.LittleEndian.Uint32(data[17:21]) + h.splitBucketIdx = binary.LittleEndian.Uint32(data[21:25]) + h.freelistOff = int64(binary.LittleEndian.Uint64(data[25:33])) + h.hashSeed = binary.LittleEndian.Uint32(data[33:37]) + return nil +} diff --git a/vendor/github.com/akrylysov/pogreb/iterator.go b/vendor/github.com/akrylysov/pogreb/iterator.go new file mode 100644 index 0000000000..e06505a0ec --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/iterator.go @@ -0,0 +1,65 @@ +package pogreb + +import ( + "errors" + "sync" +) + +// ErrIterationDone is returned by ItemIterator.Next calls when there are no more items to return. +var ErrIterationDone = errors.New("no more items in iterator") + +type item struct { + key []byte + value []byte +} + +// ItemIterator is an iterator over DB key/value pairs. It iterates the items in an unspecified order. +type ItemIterator struct { + db *DB + nextBucketIdx uint32 + queue []item + mu sync.Mutex +} + +// Next returns the next key/value pair if available, otherwise it returns ErrIterationDone error. +func (it *ItemIterator) Next() ([]byte, []byte, error) { + it.mu.Lock() + defer it.mu.Unlock() + + it.db.mu.RLock() + defer it.db.mu.RUnlock() + + if len(it.queue) == 0 { + for it.nextBucketIdx < it.db.nBuckets { + err := it.db.forEachBucket(it.nextBucketIdx, func(b bucketHandle) (bool, error) { + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + if sl.kvOffset == 0 { + return true, nil + } + key, value, err := it.db.data.readKeyValue(sl) + if err != nil { + return true, err + } + it.queue = append(it.queue, item{key: key, value: value}) + } + return false, nil + }) + if err != nil { + return nil, nil, err + } + it.nextBucketIdx++ + if len(it.queue) > 0 { + break + } + } + } + + if len(it.queue) > 0 { + item := it.queue[0] + it.queue = it.queue[1:] + return item.key, item.value, nil + } + + return nil, nil, ErrIterationDone +} diff --git a/vendor/github.com/akrylysov/pogreb/logger.go b/vendor/github.com/akrylysov/pogreb/logger.go new file mode 100644 index 0000000000..84bc7c2ac4 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/logger.go @@ -0,0 +1,15 @@ +package pogreb + +import ( + "log" + "os" +) + +var logger = log.New(os.Stderr, "pogreb: ", 0) + +// SetLogger sets the global logger. +func SetLogger(l *log.Logger) { + if l != nil { + logger = l + } +} diff --git a/vendor/github.com/akrylysov/pogreb/metrics.go b/vendor/github.com/akrylysov/pogreb/metrics.go new file mode 100644 index 0000000000..75c6ba4b37 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/metrics.go @@ -0,0 +1,22 @@ +package pogreb + +import "expvar" + +// Metrics holds the DB metrics. +type Metrics struct { + Puts *expvar.Int + Dels *expvar.Int + Gets *expvar.Int + HashCollisions *expvar.Int + BucketProbes *expvar.Int +} + +func newMetrics() Metrics { + return Metrics{ + Puts: &expvar.Int{}, + Dels: &expvar.Int{}, + Gets: &expvar.Int{}, + HashCollisions: &expvar.Int{}, + BucketProbes: &expvar.Int{}, + } +} diff --git a/vendor/github.com/akrylysov/pogreb/options.go b/vendor/github.com/akrylysov/pogreb/options.go new file mode 100644 index 0000000000..4e0f688606 --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/options.go @@ -0,0 +1,29 @@ +package pogreb + +import ( + "time" + + "github.com/akrylysov/pogreb/fs" +) + +// Options holds the optional DB parameters. +type Options struct { + // BackgroundSyncInterval sets the amount of time between background fsync() calls. + // + // Setting the value to 0 disables the automatic background synchronization. + // Setting the value to -1 makes the DB call fsync() after every write operation. + BackgroundSyncInterval time.Duration + + FileSystem fs.FileSystem +} + +func (src *Options) copyWithDefaults() *Options { + opts := Options{} + if src != nil { + opts = *src + } + if opts.FileSystem == nil { + opts.FileSystem = fs.OS + } + return &opts +} diff --git a/vendor/github.com/akrylysov/pogreb/recovery.go b/vendor/github.com/akrylysov/pogreb/recovery.go new file mode 100644 index 0000000000..654e1e7baa --- /dev/null +++ b/vendor/github.com/akrylysov/pogreb/recovery.go @@ -0,0 +1,154 @@ +package pogreb + +import ( + "math" + "sort" +) + +func align51264(n int64) int64 { + return (n + 511) &^ 511 +} + +func truncateFiles(db *DB) error { + db.index.size = align51264(db.index.size) + if err := db.index.Truncate(db.index.size); err != nil { + return err + } + if err := db.index.Mmap(db.index.size); err != nil { + return err + } + db.data.size = align51264(db.data.size) + if err := db.data.Truncate(db.data.size); err != nil { + return err + } + if err := db.data.Mmap(db.data.size); err != nil { + return err + } + return nil +} + +func getUsedBlocks(db *DB) (uint32, []block, error) { + var itemCount uint32 + var usedBlocks []block + for bucketIdx := uint32(0); bucketIdx < db.nBuckets; bucketIdx++ { + err := db.forEachBucket(bucketIdx, func(b bucketHandle) (bool, error) { + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + if sl.kvOffset == 0 { + return true, nil + } + itemCount++ + usedBlocks = append(usedBlocks, block{size: align512(sl.kvSize()), offset: sl.kvOffset}) + } + if b.next != 0 { + usedBlocks = append(usedBlocks, block{size: bucketSize, offset: b.next}) + } + return false, nil + }) + if err != nil { + return 0, nil, err + } + } + return itemCount, usedBlocks, nil +} + +func recoverSplitCrash(db *DB) error { + if db.nBuckets == 1 { + return nil + } + prevnBuckets := db.nBuckets - 1 + prevLevel := uint8(math.Floor(math.Log2(float64(prevnBuckets)))) + prevSplitBucketIdx := prevnBuckets - (uint32(1) << prevLevel) + splitCrash := false + err := db.forEachBucket(prevSplitBucketIdx, func(b bucketHandle) (bool, error) { + for i := 0; i < slotsPerBucket; i++ { + sl := b.slots[i] + if sl.kvOffset == 0 { + return true, nil + } + if db.bucketIndex(sl.hash) != prevSplitBucketIdx { + splitCrash = true + return true, nil + } + } + return false, nil + }) + if err != nil { + return err + } + if !splitCrash { + return nil + } + logger.Print("Detected split crash. Truncating index file...") + if err := db.index.Truncate(db.index.size - int64(bucketSize)); err != nil { + return err + } + db.index.size -= int64(bucketSize) + if err := db.index.Mmap(db.index.size); err != nil { + return err + } + db.nBuckets = prevnBuckets + db.level = prevLevel + db.splitBucketIdx = prevSplitBucketIdx + return nil +} + +func recoverFreeList(db *DB, usedBlocks []block) error { + if len(usedBlocks) == 0 { + return nil + } + sort.Slice(usedBlocks, func(i, j int) bool { + return usedBlocks[i].offset < usedBlocks[j].offset + }) + fl := freelist{} + expectedOff := int64(headerSize) + for _, bl := range usedBlocks { + if bl.offset > expectedOff { + fl.free(expectedOff, uint32(bl.offset-expectedOff)) + } + expectedOff = bl.offset + int64(bl.size) + } + lastBlock := usedBlocks[len(usedBlocks)-1] + lastOffset := int64(lastBlock.size) + lastBlock.offset + if db.data.size > lastOffset { + fl.free(lastOffset, uint32(db.data.size-lastOffset)) + logger.Println(lastBlock, db.data.size) + } + logger.Printf("Recovered freelist. Old len=%d; new len=%d\n", len(db.data.fl.blocks), len(fl.blocks)) + db.data.fl = fl + return nil +} + +func (db *DB) recover() error { + logger.Println("Performing recovery...") + logger.Printf("Index file size=%d; data file size=%d\n", db.index.size, db.data.size) + logger.Printf("Header dbInfo %+v\n", db.dbInfo) + + // Truncate index and data files. + if err := truncateFiles(db); err != nil { + return err + } + + // Recover header. + db.nBuckets = uint32((db.index.size - int64(headerSize)) / int64(bucketSize)) + db.level = uint8(math.Floor(math.Log2(float64(db.nBuckets)))) + db.splitBucketIdx = db.nBuckets - (uint32(1) << db.level) + itemCount, usedBlocks, err := getUsedBlocks(db) + if err != nil { + return err + } + db.count = itemCount + + // Check if crash occurred during split. + if err := recoverSplitCrash(db); err != nil { + return err + } + logger.Printf("Recovered dbInfo %+v\n", db.dbInfo) + + // Recover free list. + if err := recoverFreeList(db, usedBlocks); err != nil { + return err + } + logger.Println("Recovery complete.") + return nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 33aab0e119..c28fc3cefd 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -20,6 +20,24 @@ "revision": "5d049714c4a64225c3c79a7cf7d02f7fb5b96338", "revisionTime": "2018-01-16T20:38:02Z" }, + { + "checksumSHA1": "EBi6awUmT1usWPTFQseEzp70578=", + "path": "github.com/akrylysov/pogreb", + "revision": "828d0ef698122ac786f0c769eeea8c2b41ac1493", + "revisionTime": "2019-06-30T21:56:05Z" + }, + { + "checksumSHA1": "7/INULWANAgDLXBI4eLIOgjjpzg=", + "path": "github.com/akrylysov/pogreb/fs", + "revision": "828d0ef698122ac786f0c769eeea8c2b41ac1493", + "revisionTime": "2019-06-30T21:56:05Z" + }, + { + "checksumSHA1": "ykjZaRe9OBpXJFXqjlzInpzPM24=", + "path": "github.com/akrylysov/pogreb/hash", + "revision": "828d0ef698122ac786f0c769eeea8c2b41ac1493", + "revisionTime": "2019-06-30T21:56:05Z" + }, { "checksumSHA1": "8skJYOdQytXjimcDPLRW4tonX3A=", "path": "github.com/allegro/bigcache",