diff --git a/.travis.yml b/.travis.yml index 5901e0b10..ea05101aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,16 @@ notifications: slack: secure: X7uBLWYbuUhf8QFE16CoS5z7WvFR8EN9j6cEectMW6mKZ3vwXGwVXRIPsgUq/606DsQdCCx34MR8MRWYGlu6TBolbSe9y0EP0i46yipPz22YtuT7umcVUbGEyx8MZKgG0v1u/zA0O4aCsOBpGAA3gxz8h3JlEHDt+hv6U8xRsSllVLzLSNb5lwxDtcfEDxVVqP47GMEgjLPM28Pyt5qwjk7o5a4YSVzkfdxBXxd3gWzFUWzJ5E3cTacli50dK4GVfiLcQY2aQYoYO7AAvDnvP+TPfjDkBlUEE4MUz5CDIN51Xb+WW33sX7g+r3Bj7V5IRcF973RiYkpEh+3eoiPnyWyxhDZBYilty3b+Hysp6d4Ov/3I3ll7Bcny5+cYjakjkMH3l9w3gs6Y82GlpSLSJshKWS8vPRsxFe0Pstj6QSJXTd9EBaFr+l1ScXjJv/Sya9j8N9FfTuOTESWuaL1auX4Y7zEEVHlA8SCNOO8K0eTfxGZnC/YcIHsR8rePEAcFxfOYQppkyLF/XvAtnb/LMUuu0g4y2qNdme6Oelvyar1tFEMRtbl4mRCdu/krXBFtkrsfUaVY6WTPdvXAGotsFJ0wuA53zGVhlcd3+xAlSlR3c1QX95HIMeivJKb5L4nTjP+xnrmQNtnVk+tG4LSH2ltuwcZSSczModtcBmRefrk= -script: -- go test -v ./... -# Another round of tests after turning off mmap -- go test -v -vlog_mmap=false github.com/dgraph-io/badger +script: >- + if [ $TRAVIS_OS_NAME = "linux" ] && [ $go_32 ]; then + uname -a + GOOS=linux GOARCH=arm go test -v ./... + # Another round of tests after turning off mmap. + GOOS=linux GOARCH=arm go test -v -vlog_mmap=false github.com/dgraph-io/badger + else + go test -v ./... + # Another round of tests after turning off mmap. + go test -v -vlog_mmap=false github.com/dgraph-io/badger + # Cross-compile for Plan 9 + GOOS=plan9 go build ./... + fi diff --git a/dir_plan9.go b/dir_plan9.go new file mode 100644 index 000000000..ad323d70a --- /dev/null +++ b/dir_plan9.go @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +package badger + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part +// of the locking mechanism, it's just advisory. +type directoryLockGuard struct { + // File handle on the directory, which we've locked. + f *os.File + // The absolute path to our pid file. + path string +} + +// acquireDirectoryLock gets a lock on the directory. +// It will also write our pid to dirPath/pidFileName for convenience. +// readOnly is not supported on Plan 9. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) ( + *directoryLockGuard, error) { + if readOnly { + return nil, ErrPlan9NotSupported + } + + // Convert to absolute path so that Release still works even if we do an unbalanced + // chdir in the meantime. + absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) + if err != nil { + return nil, errors.Wrap(err, "cannot get absolute path for pid lock file") + } + + // If the file was unpacked or created by some other program, it might not + // have the ModeExclusive bit set. Set it before we call OpenFile, so that we + // can be confident that a successful OpenFile implies exclusive use. + // + // OpenFile fails if the file ModeExclusive bit set *and* the file is already open. + // So, if the file is closed when the DB crashed, we're fine. When the process + // that was managing the DB crashes, the OS will close the file for us. + // + // This bit of code is copied from Go's lockedfile internal package: + // https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L58 + if fi, err := os.Stat(absPidFilePath); err == nil { + if fi.Mode()&os.ModeExclusive == 0 { + if err := os.Chmod(absPidFilePath, fi.Mode()|os.ModeExclusive); err != nil { + return nil, errors.Wrapf(err, "could not set exclusive mode bit") + } + } + } else if !os.IsNotExist(err) { + return nil, err + } + f, err := os.OpenFile(absPidFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666|os.ModeExclusive) + if err != nil { + if isLocked(err) { + return nil, errors.Wrapf(err, + "Cannot open pid lock file %q. Another process is using this Badger database", + absPidFilePath) + } + return nil, errors.Wrapf(err, "Cannot open pid lock file %q", absPidFilePath) + } + + if _, err = fmt.Fprintf(f, "%d\n", os.Getpid()); err != nil { + f.Close() + return nil, errors.Wrapf(err, "could not write pid") + } + return &directoryLockGuard{f, absPidFilePath}, nil +} + +// Release deletes the pid file and releases our lock on the directory. +func (guard *directoryLockGuard) release() error { + // It's important that we remove the pid file first. + err := os.Remove(guard.path) + + if closeErr := guard.f.Close(); err == nil { + err = closeErr + } + guard.path = "" + guard.f = nil + + return err +} + +// openDir opens a directory for syncing. +func openDir(path string) (*os.File, error) { return os.Open(path) } + +// When you create or delete a file, you have to ensure the directory entry for the file is synced +// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync, +// or see https://github.com/coreos/etcd/issues/6368 for an example.) +func syncDir(dir string) error { + f, err := openDir(dir) + if err != nil { + return errors.Wrapf(err, "While opening directory: %s.", dir) + } + + err = f.Sync() + closeErr := f.Close() + if err != nil { + return errors.Wrapf(err, "While syncing directory: %s.", dir) + } + return errors.Wrapf(closeErr, "While closing directory: %s.", dir) +} + +// Opening an exclusive-use file returns an error. +// The expected error strings are: +// +// - "open/create -- file is locked" (cwfs, kfs) +// - "exclusive lock" (fossil) +// - "exclusive use file already open" (ramfs) +// +// See https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L16 +var lockedErrStrings = [...]string{ + "file is locked", + "exclusive lock", + "exclusive use file already open", +} + +// Even though plan9 doesn't support the Lock/RLock/Unlock functions to +// manipulate already-open files, IsLocked is still meaningful: os.OpenFile +// itself may return errors that indicate that a file with the ModeExclusive bit +// set is already open. +func isLocked(err error) bool { + s := err.Error() + + for _, frag := range lockedErrStrings { + if strings.Contains(s, frag) { + return true + } + } + return false +} diff --git a/dir_unix.go b/dir_unix.go index d56e6e821..a3fef6697 100644 --- a/dir_unix.go +++ b/dir_unix.go @@ -1,4 +1,4 @@ -// +build !windows +// +build !windows,!plan9 /* * Copyright 2017 Dgraph Labs, Inc. and Contributors diff --git a/errors.go b/errors.go index 8d2df6833..933cd1307 100644 --- a/errors.go +++ b/errors.go @@ -103,6 +103,9 @@ var ( // ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows") + // ErrPlan9NotSupported is returned when opt.ReadOnly is used on Plan 9 + ErrPlan9NotSupported = errors.New("Read-only mode is not supported on Plan 9") + // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of // corrupt data to allow Badger to run properly. ErrTruncateNeeded = errors.New( diff --git a/y/file_dsync.go b/y/file_dsync.go index 3f3445e2e..ea4d9ab26 100644 --- a/y/file_dsync.go +++ b/y/file_dsync.go @@ -1,4 +1,4 @@ -// +build !dragonfly,!freebsd,!windows +// +build !dragonfly,!freebsd,!windows,!plan9 /* * Copyright 2017 Dgraph Labs, Inc. and Contributors diff --git a/y/file_nodsync.go b/y/file_nodsync.go index b68be7ab9..54a2184e1 100644 --- a/y/file_nodsync.go +++ b/y/file_nodsync.go @@ -1,4 +1,4 @@ -// +build dragonfly freebsd windows +// +build dragonfly freebsd windows plan9 /* * Copyright 2017 Dgraph Labs, Inc. and Contributors diff --git a/y/mmap_plan9.go b/y/mmap_plan9.go new file mode 100644 index 000000000..21db76bf0 --- /dev/null +++ b/y/mmap_plan9.go @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +package y + +import ( + "os" + "syscall" +) + +// Mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + return nil, syscall.EPLAN9 +} + +// Munmap unmaps a previously mapped slice. +func munmap(b []byte) error { + return syscall.EPLAN9 +} + +// Madvise uses the madvise system call to give advise about the use of memory +// when using a slice that is memory-mapped to a file. Set the readahead flag to +// false if page references are expected in random order. +func madvise(b []byte, readahead bool) error { + return syscall.EPLAN9 +} diff --git a/y/mmap_unix.go b/y/mmap_unix.go index 8d2a18ddd..003f59723 100644 --- a/y/mmap_unix.go +++ b/y/mmap_unix.go @@ -1,4 +1,4 @@ -// +build !windows,!darwin +// +build !windows,!darwin,!plan9 /* * Copyright 2019 Dgraph Labs, Inc. and Contributors