diff --git a/db.go b/db.go index 404047709..db9564375 100644 --- a/db.go +++ b/db.go @@ -31,7 +31,6 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" - "github.com/nightlyone/lockfile" "github.com/oklog/ulid" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" @@ -94,7 +93,7 @@ type Appender interface { // a hashed partition of a seriedb. type DB struct { dir string - lockf *lockfile.Lockfile + lockf fileutil.Releaser logger log.Logger metrics *dbMetrics @@ -210,14 +209,11 @@ func Open(dir string, l log.Logger, r prometheus.Registerer, opts *Options) (db if err != nil { return nil, err } - lockf, err := lockfile.New(filepath.Join(absdir, "lock")) + lockf, _, err := fileutil.Flock(filepath.Join(absdir, "lock")) if err != nil { - return nil, err - } - if err := lockf.TryLock(); err != nil { - return nil, errors.Wrapf(err, "open DB in %s", dir) + return nil, errors.Wrap(err, "lock DB directory") } - db.lockf = &lockf + db.lockf = lockf } db.compactor, err = NewLeveledCompactor(r, l, opts.BlockRanges, db.chunkPool) @@ -719,7 +715,7 @@ func (db *DB) Close() error { merr.Add(g.Wait()) if db.lockf != nil { - merr.Add(db.lockf.Unlock()) + merr.Add(db.lockf.Release()) } merr.Add(db.head.Close()) return merr.Err() diff --git a/fileutil/flock.go b/fileutil/flock.go new file mode 100644 index 000000000..d5eaa7ca2 --- /dev/null +++ b/fileutil/flock.go @@ -0,0 +1,41 @@ +// Copyright 2016 The Prometheus Authors +// 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 fileutil + +import ( + "os" + "path/filepath" +) + +// Releaser provides the Release method to release a file lock. +type Releaser interface { + Release() error +} + +// Flock locks the file with the provided name. If the file does not exist, it is +// created. The returned Releaser is used to release the lock. existed is true +// if the file to lock already existed. A non-nil error is returned if the +// locking has failed. Neither this function nor the returned Releaser is +// goroutine-safe. +func Flock(fileName string) (r Releaser, existed bool, err error) { + if err = os.MkdirAll(filepath.Dir(fileName), 0755); err != nil { + return nil, false, err + } + + _, err = os.Stat(fileName) + existed = err == nil + + r, err = newLock(fileName) + return r, existed, err +} diff --git a/fileutil/flock_plan9.go b/fileutil/flock_plan9.go new file mode 100644 index 000000000..8a3d44c5e --- /dev/null +++ b/fileutil/flock_plan9.go @@ -0,0 +1,32 @@ +// Copyright 2016 The Prometheus Authors +// 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 fileutil + +import "os" + +type plan9Lock struct { + f *os.File +} + +func (l *plan9Lock) Release() error { + return l.f.Close() +} + +func newLock(fileName string) (Releaser, error) { + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644) + if err != nil { + return nil, err + } + return &plan9Lock{f}, nil +} diff --git a/fileutil/flock_solaris.go b/fileutil/flock_solaris.go new file mode 100644 index 000000000..7f527ae6c --- /dev/null +++ b/fileutil/flock_solaris.go @@ -0,0 +1,59 @@ +// Copyright 2016 The Prometheus Authors +// 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. + +// +build solaris + +package fileutil + +import ( + "os" + "syscall" +) + +type unixLock struct { + f *os.File +} + +func (l *unixLock) Release() error { + if err := l.set(false); err != nil { + return err + } + return l.f.Close() +} + +func (l *unixLock) set(lock bool) error { + flock := syscall.Flock_t{ + Type: syscall.F_UNLCK, + Start: 0, + Len: 0, + Whence: 1, + } + if lock { + flock.Type = syscall.F_WRLCK + } + return syscall.FcntlFlock(l.f.Fd(), syscall.F_SETLK, &flock) +} + +func newLock(fileName string) (Releaser, error) { + f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return nil, err + } + l := &unixLock{f} + err = l.set(true) + if err != nil { + f.Close() + return nil, err + } + return l, nil +} diff --git a/fileutil/flock_test.go b/fileutil/flock_test.go new file mode 100644 index 000000000..f9cbbcf2b --- /dev/null +++ b/fileutil/flock_test.go @@ -0,0 +1,80 @@ +// Copyright 2016 The Prometheus Authors +// 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 fileutil + +import ( + "os" + "path/filepath" + "testing" + + "github.com/prometheus/prometheus/util/testutil" +) + +func TestLocking(t *testing.T) { + dir := testutil.NewTemporaryDirectory("test_flock", t) + defer dir.Close() + + fileName := filepath.Join(dir.Path(), "LOCK") + + if _, err := os.Stat(fileName); err == nil { + t.Fatalf("File %q unexpectedly exists.", fileName) + } + + lock, existed, err := Flock(fileName) + if err != nil { + t.Fatalf("Error locking file %q: %s", fileName, err) + } + if existed { + t.Errorf("File %q reported as existing during locking.", fileName) + } + + // File must now exist. + if _, err = os.Stat(fileName); err != nil { + t.Errorf("Could not stat file %q expected to exist: %s", fileName, err) + } + + // Try to lock again. + lockedAgain, existed, err := Flock(fileName) + if err == nil { + t.Fatalf("File %q locked twice.", fileName) + } + if lockedAgain != nil { + t.Error("Unsuccessful locking did not return nil.") + } + if !existed { + t.Errorf("Existing file %q not recognized.", fileName) + } + + if err := lock.Release(); err != nil { + t.Errorf("Error releasing lock for file %q: %s", fileName, err) + } + + // File must still exist. + if _, err = os.Stat(fileName); err != nil { + t.Errorf("Could not stat file %q expected to exist: %s", fileName, err) + } + + // Lock existing file. + lock, existed, err = Flock(fileName) + if err != nil { + t.Fatalf("Error locking file %q: %s", fileName, err) + } + if !existed { + t.Errorf("Existing file %q not recognized.", fileName) + } + + if err := lock.Release(); err != nil { + t.Errorf("Error releasing lock for file %q: %s", fileName, err) + } +} diff --git a/fileutil/flock_unix.go b/fileutil/flock_unix.go new file mode 100644 index 000000000..f493fbd83 --- /dev/null +++ b/fileutil/flock_unix.go @@ -0,0 +1,54 @@ +// Copyright 2016 The Prometheus Authors +// 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. + +// +build darwin dragonfly freebsd linux netbsd openbsd + +package fileutil + +import ( + "os" + "syscall" +) + +type unixLock struct { + f *os.File +} + +func (l *unixLock) Release() error { + if err := l.set(false); err != nil { + return err + } + return l.f.Close() +} + +func (l *unixLock) set(lock bool) error { + how := syscall.LOCK_UN + if lock { + how = syscall.LOCK_EX + } + return syscall.Flock(int(l.f.Fd()), how|syscall.LOCK_NB) +} + +func newLock(fileName string) (Releaser, error) { + f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return nil, err + } + l := &unixLock{f} + err = l.set(true) + if err != nil { + f.Close() + return nil, err + } + return l, nil +} diff --git a/fileutil/flock_windows.go b/fileutil/flock_windows.go new file mode 100644 index 000000000..1c17ff4ea --- /dev/null +++ b/fileutil/flock_windows.go @@ -0,0 +1,36 @@ +// Copyright 2016 The Prometheus Authors +// 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 fileutil + +import "syscall" + +type windowsLock struct { + fd syscall.Handle +} + +func (fl *windowsLock) Release() error { + return syscall.Close(fl.fd) +} + +func newLock(fileName string) (Releaser, error) { + pathp, err := syscall.UTF16PtrFromString(fileName) + if err != nil { + return nil, err + } + fd, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0) + if err != nil { + return nil, err + } + return &windowsLock{fd}, nil +}