blob: 9c318509adc46814e954d936409342bdfaa3e172 [file] [log] [blame]
// Copyright 2017 by Dan Jacques. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fslock
import (
"errors"
"os"
)
// ErrLockHeld is a sentinel error returned when the lock could not be
// acquired.
var ErrLockHeld = errors.New("fslock: lock is held")
// Handle is a reference to a held lock. It must be released via Unlock when
// finished. Multiple calls to Unlock will panic.
//
// Handle is NOT safe for concurrent use.
type Handle interface {
// Unlock releases the held lock.
//
// This can error if the underlying filesystem operations fail. This should
// not happen unless something has gone externally wrong, or the lock was
// mishandled.
Unlock() error
// LockFile returns the underlying lock File. This is not generally useful,
// and should be used circumstantially. Operating on the file occurs outside
// of the scope of this package, and can result in unintended consequences.
//
// The file should NOT be directly closed or modified.
//
// The file will be valid for the duration of the Handle. Once the Handle is
// closed with Unlock, the file's state is implementation-specific and
// unspecified.
LockFile() *os.File
// PreserveExec preserves the lock across execve syscall.
//
// The lock will be held even after call execve. It is not possible to
// acquire a handle for the lock unless manually passing the fd as an
// argument.
//
// On Windows, the behaviour is not promised unless CreateProcess with
// bInheritHandles.
PreserveExec() error
}
// Blocker is used for the Delay field in a Lock.
type Blocker func() error
// L describes a filesystem lock.
//
// L's fields should not be modified concurrently, but L's methods are safe
// for concurrent use.
type L struct {
// Path is the path of the file to lock.
Path string
// Shared, if true, indicates that this should be a shared lock rather than
// an exclusive lock.
//
// See package documentation for details.
Shared bool
// Content, if populated, is the lock file content. Content is written to the
// file when the lock call creates it, and only if the lock call actually
// creates the file. Failure to write Content is non-fatal.
//
// Content should be used only as a convenience hint for users who want to
// know what the lock file is, and not for actual programmatic management.
// Several code paths can result in successful file locking and still fail to
// write Content to that file.
//
// Content is not synchronized with the actual locking. Failure to write
// Content to the lock file is considered non-fatal.
Content []byte
// Block is the configured blocking function.
//
// If not nil, an attempt to acquire the lock will loop indefinitely until an
// error other than ErrLockHeld is encountered (fatal) or the lock is
// acquired. Block will be called each time a lock attempt returns
// ErrLockHeld, and should delay and/or cancel the acquisition by returning
// nil or an error code respectively.
//
// If Block returns an error, it will be propagated as the error result of the
// locking attempt.
Block Blocker
}
// Lock attempts to acquire the configured lock.
func (l *L) Lock() (Handle, error) {
// Loop repeatedly until the lock is held or an error is encountered.
for {
switch h, err := lockImpl(l); err {
case nil:
// Acquired the lock.
return h, nil
case ErrLockHeld:
// If we have a Block function configured, invoke it, then try again.
// Otherwise, propagate ErrLockHeld.
if l.Block != nil {
if err := l.Block(); err != nil {
return nil, err
}
continue
}
fallthrough
default:
return nil, err
}
}
}
// With is a convenience method to acquire a lock via Lock, call fn, and release
// the lock on completion (via defer).
//
// If an error is encountered, it will be returned. Otherwise, the return value
// from fn will be returned.
func (l *L) With(fn func() error) (err error) {
h, err := l.Lock()
if err != nil {
return
}
defer func() {
uerr := h.Unlock()
if uerr != nil && err == nil {
err = uerr
}
}()
return fn()
}