pax_global_header00006660000000000000000000000064140656730160014522gustar00rootroot0000000000000052 comment=6f010d1acea74a32f2f2066bfe324c08bbee30e3 flock-0.8.1/000077500000000000000000000000001406567301600126265ustar00rootroot00000000000000flock-0.8.1/.gitignore000066400000000000000000000004121406567301600146130ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof flock-0.8.1/.travis.yml000066400000000000000000000002411406567301600147340ustar00rootroot00000000000000language: go go: - 1.14.x - 1.15.x script: go test -v -check.vv -race ./... sudo: false notifications: email: on_success: never on_failure: always flock-0.8.1/LICENSE000066400000000000000000000027071406567301600136410ustar00rootroot00000000000000Copyright (c) 2015-2020, Tim Heckman All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of gofrs nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flock-0.8.1/README.md000066400000000000000000000025341406567301600141110ustar00rootroot00000000000000# flock [![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock) [![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock) [![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock) `flock` implements a thread-safe sync.Locker interface for file locking. It also includes a non-blocking TryLock() function to allow locking without blocking execution. ## License `flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details. ## Go Compatibility This package makes use of the `context` package that was introduced in Go 1.7. As such, this package has an implicit dependency on Go 1.7+. ## Installation ``` go get -u github.com/gofrs/flock ``` ## Usage ```Go import "github.com/gofrs/flock" fileLock := flock.New("/var/lock/go-lock.lock") locked, err := fileLock.TryLock() if err != nil { // handle locking error } if locked { // do work fileLock.Unlock() } ``` For more detailed usage information take a look at the package API docs on [GoDoc](https://godoc.org/github.com/gofrs/flock). flock-0.8.1/appveyor.yml000066400000000000000000000010051406567301600152120ustar00rootroot00000000000000version: '{build}' build: false deploy: false clone_folder: 'c:\gopath\src\github.com\gofrs\flock' environment: GOPATH: 'c:\gopath' GOVERSION: '1.15' init: - git config --global core.autocrlf input install: - rmdir c:\go /s /q - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - msiexec /i go%GOVERSION%.windows-amd64.msi /q - set Path=c:\go\bin;c:\gopath\bin;%Path% - go version - go env test_script: - go get -t ./... - go test -race -v ./... flock-0.8.1/flock.go000066400000000000000000000076331406567301600142640ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. // Package flock implements a thread-safe interface for file locking. // It also includes a non-blocking TryLock() function to allow locking // without blocking execution. // // Package flock is released under the BSD 3-Clause License. See the LICENSE file // for more details. // // While using this library, remember that the locking behaviors are not // guaranteed to be the same on each platform. For example, some UNIX-like // operating systems will transparently convert a shared lock to an exclusive // lock. If you Unlock() the flock from a location where you believe that you // have the shared lock, you may accidentally drop the exclusive lock. package flock import ( "context" "os" "runtime" "sync" "time" ) // Flock is the struct type to handle file locking. All fields are unexported, // with access to some of the fields provided by getter methods (Path() and Locked()). type Flock struct { path string m sync.RWMutex fh *os.File l bool r bool } // New returns a new instance of *Flock. The only parameter // it takes is the path to the desired lockfile. func New(path string) *Flock { return &Flock{path: path} } // NewFlock returns a new instance of *Flock. The only parameter // it takes is the path to the desired lockfile. // // Deprecated: Use New instead. func NewFlock(path string) *Flock { return New(path) } // Close is equivalent to calling Unlock. // // This will release the lock and close the underlying file descriptor. // It will not remove the file from disk, that's up to your application. func (f *Flock) Close() error { return f.Unlock() } // Path returns the path as provided in NewFlock(). func (f *Flock) Path() string { return f.path } // Locked returns the lock state (locked: true, unlocked: false). // // Warning: by the time you use the returned value, the state may have changed. func (f *Flock) Locked() bool { f.m.RLock() defer f.m.RUnlock() return f.l } // RLocked returns the read lock state (locked: true, unlocked: false). // // Warning: by the time you use the returned value, the state may have changed. func (f *Flock) RLocked() bool { f.m.RLock() defer f.m.RUnlock() return f.r } func (f *Flock) String() string { return f.path } // TryLockContext repeatedly tries to take an exclusive lock until one of the // conditions is met: TryLock succeeds, TryLock fails with error, or Context // Done channel is closed. func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { return tryCtx(ctx, f.TryLock, retryDelay) } // TryRLockContext repeatedly tries to take a shared lock until one of the // conditions is met: TryRLock succeeds, TryRLock fails with error, or Context // Done channel is closed. func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { return tryCtx(ctx, f.TryRLock, retryDelay) } func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) { if ctx.Err() != nil { return false, ctx.Err() } for { if ok, err := fn(); ok || err != nil { return ok, err } select { case <-ctx.Done(): return false, ctx.Err() case <-time.After(retryDelay): // try again } } } func (f *Flock) setFh() error { // open a new os.File instance // create it if it doesn't exist, and open the file read-only. flags := os.O_CREATE if runtime.GOOS == "aix" { // AIX cannot preform write-lock (ie exclusive) on a // read-only file. flags |= os.O_RDWR } else { flags |= os.O_RDONLY } fh, err := os.OpenFile(f.path, flags, os.FileMode(0600)) if err != nil { return err } // set the filehandle on the struct f.fh = fh return nil } // ensure the file handle is closed if no lock is held func (f *Flock) ensureFhState() { if !f.l && !f.r && f.fh != nil { f.fh.Close() f.fh = nil } } flock-0.8.1/flock_aix.go000066400000000000000000000156041406567301600151220ustar00rootroot00000000000000// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is // governed by the BSD 3-Clause license that can be found in the LICENSE file. // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This code implements the filelock API using POSIX 'fcntl' locks, which attach // to an (inode, process) pair rather than a file descriptor. To avoid unlocking // files prematurely when the same file is opened through different descriptors, // we allow only one read-lock at a time. // // This code is adapted from the Go package: // cmd/go/internal/lockedfile/internal/filelock //+build aix package flock import ( "errors" "io" "os" "sync" "syscall" "golang.org/x/sys/unix" ) type lockType int16 const ( readLock lockType = unix.F_RDLCK writeLock lockType = unix.F_WRLCK ) type cmdType int const ( tryLock cmdType = unix.F_SETLK waitLock cmdType = unix.F_SETLKW ) type inode = uint64 type inodeLock struct { owner *Flock queue []<-chan *Flock } var ( mu sync.Mutex inodes = map[*Flock]inode{} locks = map[inode]inodeLock{} ) // Lock is a blocking call to try and take an exclusive file lock. It will wait // until it is able to obtain the exclusive file lock. It's recommended that // TryLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already exclusive-locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. // // If the *Flock has a shared lock (RLock), this may transparently replace the // shared lock with an exclusive lock on some UNIX-like operating systems. Be // careful when using exclusive locks in conjunction with shared locks // (RLock()), because calling Unlock() may accidentally release the exclusive // lock that was once a shared lock. func (f *Flock) Lock() error { return f.lock(&f.l, writeLock) } // RLock is a blocking call to try and take a shared file lock. It will wait // until it is able to obtain the shared file lock. It's recommended that // TryRLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already shared-locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. func (f *Flock) RLock() error { return f.lock(&f.r, readLock) } func (f *Flock) lock(locked *bool, flag lockType) error { f.m.Lock() defer f.m.Unlock() if *locked { return nil } if f.fh == nil { if err := f.setFh(); err != nil { return err } defer f.ensureFhState() } if _, err := f.doLock(waitLock, flag, true); err != nil { return err } *locked = true return nil } func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) { // POSIX locks apply per inode and process, and the lock for an inode is // released when *any* descriptor for that inode is closed. So we need to // synchronize access to each inode internally, and must serialize lock and // unlock calls that refer to the same inode through different descriptors. fi, err := f.fh.Stat() if err != nil { return false, err } ino := inode(fi.Sys().(*syscall.Stat_t).Ino) mu.Lock() if i, dup := inodes[f]; dup && i != ino { mu.Unlock() return false, &os.PathError{ Path: f.Path(), Err: errors.New("inode for file changed since last Lock or RLock"), } } inodes[f] = ino var wait chan *Flock l := locks[ino] if l.owner == f { // This file already owns the lock, but the call may change its lock type. } else if l.owner == nil { // No owner: it's ours now. l.owner = f } else if !blocking { // Already owned: cannot take the lock. mu.Unlock() return false, nil } else { // Already owned: add a channel to wait on. wait = make(chan *Flock) l.queue = append(l.queue, wait) } locks[ino] = l mu.Unlock() if wait != nil { wait <- f } err = setlkw(f.fh.Fd(), cmd, lt) if err != nil { f.doUnlock() if cmd == tryLock && err == unix.EACCES { return false, nil } return false, err } return true, nil } func (f *Flock) Unlock() error { f.m.Lock() defer f.m.Unlock() // if we aren't locked or if the lockfile instance is nil // just return a nil error because we are unlocked if (!f.l && !f.r) || f.fh == nil { return nil } if err := f.doUnlock(); err != nil { return err } f.fh.Close() f.l = false f.r = false f.fh = nil return nil } func (f *Flock) doUnlock() (err error) { var owner *Flock mu.Lock() ino, ok := inodes[f] if ok { owner = locks[ino].owner } mu.Unlock() if owner == f { err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK) } mu.Lock() l := locks[ino] if len(l.queue) == 0 { // No waiters: remove the map entry. delete(locks, ino) } else { // The first waiter is sending us their file now. // Receive it and update the queue. l.owner = <-l.queue[0] l.queue = l.queue[1:] locks[ino] = l } delete(inodes, f) mu.Unlock() return err } // TryLock is the preferred function for taking an exclusive file lock. This // function takes an RW-mutex lock before it tries to lock the file, so there is // the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the exclusive // file lock, the function will return false instead of waiting for the lock. If // we get the lock, we also set the *Flock instance as being exclusive-locked. func (f *Flock) TryLock() (bool, error) { return f.try(&f.l, writeLock) } // TryRLock is the preferred function for taking a shared file lock. This // function takes an RW-mutex lock before it tries to lock the file, so there is // the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the shared file // lock, the function will return false instead of waiting for the lock. If we // get the lock, we also set the *Flock instance as being share-locked. func (f *Flock) TryRLock() (bool, error) { return f.try(&f.r, readLock) } func (f *Flock) try(locked *bool, flag lockType) (bool, error) { f.m.Lock() defer f.m.Unlock() if *locked { return true, nil } if f.fh == nil { if err := f.setFh(); err != nil { return false, err } defer f.ensureFhState() } haslock, err := f.doLock(tryLock, flag, false) if err != nil { return false, err } *locked = haslock return haslock, nil } // setlkw calls FcntlFlock with cmd for the entire file indicated by fd. func setlkw(fd uintptr, cmd cmdType, lt lockType) error { for { err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{ Type: int16(lt), Whence: io.SeekStart, Start: 0, Len: 0, // All bytes. }) if err != unix.EINTR { return err } } } flock-0.8.1/flock_example_test.go000066400000000000000000000031071406567301600170260ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Copyright 2018 The Gofrs. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. package flock_test import ( "context" "fmt" "os" "time" "github.com/gofrs/flock" ) func ExampleFlock_Locked() { f := flock.New(os.TempDir() + "/go-lock.lock") f.TryLock() // unchecked errors here fmt.Printf("locked: %v\n", f.Locked()) f.Unlock() fmt.Printf("locked: %v\n", f.Locked()) // Output: locked: true // locked: false } func ExampleFlock_TryLock() { // should probably put these in /var/lock fileLock := flock.New(os.TempDir() + "/go-lock.lock") locked, err := fileLock.TryLock() if err != nil { // handle locking error } if locked { fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) if err := fileLock.Unlock(); err != nil { // handle unlock error } } fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) } func ExampleFlock_TryLockContext() { // should probably put these in /var/lock fileLock := flock.New(os.TempDir() + "/go-lock.lock") lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() locked, err := fileLock.TryLockContext(lockCtx, 678*time.Millisecond) if err != nil { // handle locking error } if locked { fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) if err := fileLock.Unlock(); err != nil { // handle unlock error } } fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) } flock-0.8.1/flock_internal_test.go000066400000000000000000000011721406567301600172070ustar00rootroot00000000000000package flock import ( "io/ioutil" "os" "testing" ) func Test(t *testing.T) { tmpFileFh, err := ioutil.TempFile(os.TempDir(), "go-flock-") tmpFileFh.Close() tmpFile := tmpFileFh.Name() os.Remove(tmpFile) lock := New(tmpFile) locked, err := lock.TryLock() if locked == false || err != nil { t.Fatalf("failed to lock: locked: %t, err: %v", locked, err) } newLock := New(tmpFile) locked, err = newLock.TryLock() if locked != false || err != nil { t.Fatalf("should have failed locking: locked: %t, err: %v", locked, err) } if newLock.fh != nil { t.Fatal("file handle should have been released and be nil") } } flock-0.8.1/flock_test.go000066400000000000000000000156041406567301600153200ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Copyright 2018 The Gofrs. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. package flock_test import ( "context" "io/ioutil" "os" "runtime" "testing" "time" "github.com/gofrs/flock" . "gopkg.in/check.v1" ) type TestSuite struct { path string flock *flock.Flock } var _ = Suite(&TestSuite{}) func Test(t *testing.T) { TestingT(t) } func (t *TestSuite) SetUpTest(c *C) { tmpFile, err := ioutil.TempFile(os.TempDir(), "go-flock-") c.Assert(err, IsNil) c.Assert(tmpFile, Not(IsNil)) t.path = tmpFile.Name() defer os.Remove(t.path) tmpFile.Close() t.flock = flock.New(t.path) } func (t *TestSuite) TearDownTest(c *C) { t.flock.Unlock() os.Remove(t.path) } func (t *TestSuite) TestNew(c *C) { var f *flock.Flock f = flock.New(t.path) c.Assert(f, Not(IsNil)) c.Check(f.Path(), Equals, t.path) c.Check(f.Locked(), Equals, false) c.Check(f.RLocked(), Equals, false) } func (t *TestSuite) TestFlock_Path(c *C) { var path string path = t.flock.Path() c.Check(path, Equals, t.path) } func (t *TestSuite) TestFlock_Locked(c *C) { var locked bool locked = t.flock.Locked() c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_RLocked(c *C) { var locked bool locked = t.flock.RLocked() c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_String(c *C) { var str string str = t.flock.String() c.Assert(str, Equals, t.path) } func (t *TestSuite) TestFlock_TryLock(c *C) { c.Assert(t.flock.Locked(), Equals, false) c.Assert(t.flock.RLocked(), Equals, false) var locked bool var err error locked, err = t.flock.TryLock() c.Assert(err, IsNil) c.Check(locked, Equals, true) c.Check(t.flock.Locked(), Equals, true) c.Check(t.flock.RLocked(), Equals, false) locked, err = t.flock.TryLock() c.Assert(err, IsNil) c.Check(locked, Equals, true) // make sure we just return false with no error in cases // where we would have been blocked locked, err = flock.New(t.path).TryLock() c.Assert(err, IsNil) c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_TryRLock(c *C) { c.Assert(t.flock.Locked(), Equals, false) c.Assert(t.flock.RLocked(), Equals, false) var locked bool var err error locked, err = t.flock.TryRLock() c.Assert(err, IsNil) c.Check(locked, Equals, true) c.Check(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, true) locked, err = t.flock.TryRLock() c.Assert(err, IsNil) c.Check(locked, Equals, true) // shared lock should not block. flock2 := flock.New(t.path) locked, err = flock2.TryRLock() c.Assert(err, IsNil) if runtime.GOOS == "aix" { // When using POSIX locks, we can't safely read-lock the same // inode through two different descriptors at the same time: // when the first descriptor is closed, the second descriptor // would still be open but silently unlocked. So a second // TryRLock must return false. c.Check(locked, Equals, false) } else { c.Check(locked, Equals, true) } // make sure we just return false with no error in cases // where we would have been blocked t.flock.Unlock() flock2.Unlock() t.flock.Lock() locked, err = flock.New(t.path).TryRLock() c.Assert(err, IsNil) c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_TryLockContext(c *C) { // happy path ctx, cancel := context.WithCancel(context.Background()) locked, err := t.flock.TryLockContext(ctx, time.Second) c.Assert(err, IsNil) c.Check(locked, Equals, true) // context already canceled cancel() locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) c.Assert(err, Equals, context.Canceled) c.Check(locked, Equals, false) // timeout ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) c.Assert(err, Equals, context.DeadlineExceeded) c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_TryRLockContext(c *C) { // happy path ctx, cancel := context.WithCancel(context.Background()) locked, err := t.flock.TryRLockContext(ctx, time.Second) c.Assert(err, IsNil) c.Check(locked, Equals, true) // context already canceled cancel() locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) c.Assert(err, Equals, context.Canceled) c.Check(locked, Equals, false) // timeout t.flock.Unlock() t.flock.Lock() ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) c.Assert(err, Equals, context.DeadlineExceeded) c.Check(locked, Equals, false) } func (t *TestSuite) TestFlock_Unlock(c *C) { var err error err = t.flock.Unlock() c.Assert(err, IsNil) // get a lock for us to unlock locked, err := t.flock.TryLock() c.Assert(err, IsNil) c.Assert(locked, Equals, true) c.Assert(t.flock.Locked(), Equals, true) c.Check(t.flock.RLocked(), Equals, false) _, err = os.Stat(t.path) c.Assert(os.IsNotExist(err), Equals, false) err = t.flock.Unlock() c.Assert(err, IsNil) c.Check(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, false) } func (t *TestSuite) TestFlock_Lock(c *C) { c.Assert(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, false) var err error err = t.flock.Lock() c.Assert(err, IsNil) c.Check(t.flock.Locked(), Equals, true) c.Check(t.flock.RLocked(), Equals, false) // test that the short-circuit works err = t.flock.Lock() c.Assert(err, IsNil) // // Test that Lock() is a blocking call // ch := make(chan error, 2) gf := flock.New(t.path) defer gf.Unlock() go func(ch chan<- error) { ch <- nil ch <- gf.Lock() close(ch) }(ch) errCh, ok := <-ch c.Assert(ok, Equals, true) c.Assert(errCh, IsNil) err = t.flock.Unlock() c.Assert(err, IsNil) errCh, ok = <-ch c.Assert(ok, Equals, true) c.Assert(errCh, IsNil) c.Check(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, false) c.Check(gf.Locked(), Equals, true) c.Check(gf.RLocked(), Equals, false) } func (t *TestSuite) TestFlock_RLock(c *C) { c.Assert(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, false) var err error err = t.flock.RLock() c.Assert(err, IsNil) c.Check(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, true) // test that the short-circuit works err = t.flock.RLock() c.Assert(err, IsNil) // // Test that RLock() is a blocking call // ch := make(chan error, 2) gf := flock.New(t.path) defer gf.Unlock() go func(ch chan<- error) { ch <- nil ch <- gf.RLock() close(ch) }(ch) errCh, ok := <-ch c.Assert(ok, Equals, true) c.Assert(errCh, IsNil) err = t.flock.Unlock() c.Assert(err, IsNil) errCh, ok = <-ch c.Assert(ok, Equals, true) c.Assert(errCh, IsNil) c.Check(t.flock.Locked(), Equals, false) c.Check(t.flock.RLocked(), Equals, false) c.Check(gf.Locked(), Equals, false) c.Check(gf.RLocked(), Equals, true) } flock-0.8.1/flock_unix.go000066400000000000000000000131731406567301600153230ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. // +build !aix,!windows package flock import ( "os" "syscall" ) // Lock is a blocking call to try and take an exclusive file lock. It will wait // until it is able to obtain the exclusive file lock. It's recommended that // TryLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already exclusive-locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. // // If the *Flock has a shared lock (RLock), this may transparently replace the // shared lock with an exclusive lock on some UNIX-like operating systems. Be // careful when using exclusive locks in conjunction with shared locks // (RLock()), because calling Unlock() may accidentally release the exclusive // lock that was once a shared lock. func (f *Flock) Lock() error { return f.lock(&f.l, syscall.LOCK_EX) } // RLock is a blocking call to try and take a shared file lock. It will wait // until it is able to obtain the shared file lock. It's recommended that // TryRLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already shared-locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. func (f *Flock) RLock() error { return f.lock(&f.r, syscall.LOCK_SH) } func (f *Flock) lock(locked *bool, flag int) error { f.m.Lock() defer f.m.Unlock() if *locked { return nil } if f.fh == nil { if err := f.setFh(); err != nil { return err } defer f.ensureFhState() } if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { shouldRetry, reopenErr := f.reopenFDOnError(err) if reopenErr != nil { return reopenErr } if !shouldRetry { return err } if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil { return err } } *locked = true return nil } // Unlock is a function to unlock the file. This file takes a RW-mutex lock, so // while it is running the Locked() and RLocked() functions will be blocked. // // This function short-circuits if we are unlocked already. If not, it calls // syscall.LOCK_UN on the file and closes the file descriptor. It does not // remove the file from disk. It's up to your application to do. // // Please note, if your shared lock became an exclusive lock this may // unintentionally drop the exclusive lock if called by the consumer that // believes they have a shared lock. Please see Lock() for more details. func (f *Flock) Unlock() error { f.m.Lock() defer f.m.Unlock() // if we aren't locked or if the lockfile instance is nil // just return a nil error because we are unlocked if (!f.l && !f.r) || f.fh == nil { return nil } // mark the file as unlocked if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { return err } f.fh.Close() f.l = false f.r = false f.fh = nil return nil } // TryLock is the preferred function for taking an exclusive file lock. This // function takes an RW-mutex lock before it tries to lock the file, so there is // the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the exclusive // file lock, the function will return false instead of waiting for the lock. If // we get the lock, we also set the *Flock instance as being exclusive-locked. func (f *Flock) TryLock() (bool, error) { return f.try(&f.l, syscall.LOCK_EX) } // TryRLock is the preferred function for taking a shared file lock. This // function takes an RW-mutex lock before it tries to lock the file, so there is // the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the shared file // lock, the function will return false instead of waiting for the lock. If we // get the lock, we also set the *Flock instance as being share-locked. func (f *Flock) TryRLock() (bool, error) { return f.try(&f.r, syscall.LOCK_SH) } func (f *Flock) try(locked *bool, flag int) (bool, error) { f.m.Lock() defer f.m.Unlock() if *locked { return true, nil } if f.fh == nil { if err := f.setFh(); err != nil { return false, err } defer f.ensureFhState() } var retried bool retry: err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) switch err { case syscall.EWOULDBLOCK: return false, nil case nil: *locked = true return true, nil } if !retried { if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil { return false, reopenErr } else if shouldRetry { retried = true goto retry } } return false, err } // reopenFDOnError determines whether we should reopen the file handle // in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c: // Since Linux 3.4 (commit 55725513) // Probably NFSv4 where flock() is emulated by fcntl(). func (f *Flock) reopenFDOnError(err error) (bool, error) { if err != syscall.EIO && err != syscall.EBADF { return false, nil } if st, err := f.fh.Stat(); err == nil { // if the file is able to be read and written if st.Mode()&0600 == 0600 { f.fh.Close() f.fh = nil // reopen in read-write mode and set the filehandle fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600)) if err != nil { return false, err } f.fh = fh return true, nil } } return false, nil } flock-0.8.1/flock_winapi.go000066400000000000000000000036331406567301600156270ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. // +build windows package flock import ( "syscall" "unsafe" ) var ( kernel32, _ = syscall.LoadLibrary("kernel32.dll") procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") ) const ( winLockfileFailImmediately = 0x00000001 winLockfileExclusiveLock = 0x00000002 winLockfileSharedLock = 0x00000000 ) // Use of 0x00000000 for the shared lock is a guess based on some the MS Windows // `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: // // > The function requests an exclusive lock. Otherwise, it requests a shared // > lock. // // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { r1, _, errNo := syscall.Syscall6( uintptr(procLockFileEx), 6, uintptr(handle), uintptr(flags), uintptr(reserved), uintptr(numberOfBytesToLockLow), uintptr(numberOfBytesToLockHigh), uintptr(unsafe.Pointer(offset))) if r1 != 1 { if errNo == 0 { return false, syscall.EINVAL } return false, errNo } return true, 0 } func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { r1, _, errNo := syscall.Syscall6( uintptr(procUnlockFileEx), 5, uintptr(handle), uintptr(reserved), uintptr(numberOfBytesToLockLow), uintptr(numberOfBytesToLockHigh), uintptr(unsafe.Pointer(offset)), 0) if r1 != 1 { if errNo == 0 { return false, syscall.EINVAL } return false, errNo } return true, 0 } flock-0.8.1/flock_windows.go000066400000000000000000000104171406567301600160300ustar00rootroot00000000000000// Copyright 2015 Tim Heckman. All rights reserved. // Use of this source code is governed by the BSD 3-Clause // license that can be found in the LICENSE file. package flock import ( "syscall" ) // ErrorLockViolation is the error code returned from the Windows syscall when a // lock would block and you ask to fail immediately. const ErrorLockViolation syscall.Errno = 0x21 // 33 // Lock is a blocking call to try and take an exclusive file lock. It will wait // until it is able to obtain the exclusive file lock. It's recommended that // TryLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. func (f *Flock) Lock() error { return f.lock(&f.l, winLockfileExclusiveLock) } // RLock is a blocking call to try and take a shared file lock. It will wait // until it is able to obtain the shared file lock. It's recommended that // TryRLock() be used over this function. This function may block the ability to // query the current Locked() or RLocked() status due to a RW-mutex lock. // // If we are already locked, this function short-circuits and returns // immediately assuming it can take the mutex lock. func (f *Flock) RLock() error { return f.lock(&f.r, winLockfileSharedLock) } func (f *Flock) lock(locked *bool, flag uint32) error { f.m.Lock() defer f.m.Unlock() if *locked { return nil } if f.fh == nil { if err := f.setFh(); err != nil { return err } defer f.ensureFhState() } if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { return errNo } *locked = true return nil } // Unlock is a function to unlock the file. This file takes a RW-mutex lock, so // while it is running the Locked() and RLocked() functions will be blocked. // // This function short-circuits if we are unlocked already. If not, it calls // UnlockFileEx() on the file and closes the file descriptor. It does not remove // the file from disk. It's up to your application to do. func (f *Flock) Unlock() error { f.m.Lock() defer f.m.Unlock() // if we aren't locked or if the lockfile instance is nil // just return a nil error because we are unlocked if (!f.l && !f.r) || f.fh == nil { return nil } // mark the file as unlocked if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { return errNo } f.fh.Close() f.l = false f.r = false f.fh = nil return nil } // TryLock is the preferred function for taking an exclusive file lock. This // function does take a RW-mutex lock before it tries to lock the file, so there // is the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the exclusive // file lock, the function will return false instead of waiting for the lock. If // we get the lock, we also set the *Flock instance as being exclusive-locked. func (f *Flock) TryLock() (bool, error) { return f.try(&f.l, winLockfileExclusiveLock) } // TryRLock is the preferred function for taking a shared file lock. This // function does take a RW-mutex lock before it tries to lock the file, so there // is the possibility that this function may block for a short time if another // goroutine is trying to take any action. // // The actual file lock is non-blocking. If we are unable to get the shared file // lock, the function will return false instead of waiting for the lock. If we // get the lock, we also set the *Flock instance as being shared-locked. func (f *Flock) TryRLock() (bool, error) { return f.try(&f.r, winLockfileSharedLock) } func (f *Flock) try(locked *bool, flag uint32) (bool, error) { f.m.Lock() defer f.m.Unlock() if *locked { return true, nil } if f.fh == nil { if err := f.setFh(); err != nil { return false, err } defer f.ensureFhState() } _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) if errNo > 0 { if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { return false, nil } return false, errNo } *locked = true return true, nil }