From 49176c60c539fc191b350cc0128a24cbee73284a Mon Sep 17 00:00:00 2001 From: Naman Jain Date: Wed, 9 Sep 2020 18:13:02 +0530 Subject: [PATCH] Fix build on Plan 9 (#1451) (#1508) Plan 9 doesn't have mmap, so we just return an error. File locking is implemented using the exclusive mode bit. From http://man.cat-v.org/plan_9/5/open: If the file is marked for exclusive use (see stat(5)), only one client can have the file open at any time. That is, after such a file has been opened, further opens will fail until fid has been clunked. Initial attempt at implementing directory locking using this method was not successful because badger seems to be opening the directory multiple times (at least while running the test suite). As a workaround, we lock the pid lock file instead of the directory itself, same as the windows implementation. Co-authored-by: Ibrahim Jarif (cherry picked from commit 07fa3ebd84ac171ddc5cde8bc44f2be8e039b1ee) Co-authored-by: Fazlul Shahriar --- .travis.yml | 17 ++++-- dir_plan9.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++ dir_unix.go | 2 +- errors.go | 3 + y/file_dsync.go | 2 +- y/file_nodsync.go | 2 +- y/mmap_plan9.go | 40 +++++++++++++ y/mmap_unix.go | 2 +- 8 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 dir_plan9.go create mode 100644 y/mmap_plan9.go 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