File Lock Guard
Take an exclusive non-blocking flock around a critical section, reject a second holder, and reacquire after release.
Code
package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
type FileLock struct {
file *os.File
path string
}
func AcquireFileLock(path string) (*FileLock, error) {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return nil, fmt.Errorf("open lock file: %w", err)
}
if err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
file.Close()
return nil, fmt.Errorf("lock held elsewhere: %w", err)
}
return &FileLock{file: file, path: path}, nil
}
func (l *FileLock) Release() error {
if err := syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN); err != nil {
l.file.Close()
return fmt.Errorf("unlock: %w", err)
}
return l.file.Close()
}
func withLock(path string, work func() error) error {
lock, err := AcquireFileLock(path)
if err != nil {
return err
}
defer lock.Release()
return work()
}
func main() {
path := filepath.Join(os.TempDir(), "devexamples-file-lock.lock")
lock, err := AcquireFileLock(path)
if err != nil {
fmt.Println("first acquire failed:", err)
return
}
fmt.Println("locked:", filepath.Base(lock.path))
if _, err := AcquireFileLock(path); err != nil {
fmt.Println("second acquire rejected:", err != nil)
}
if err := lock.Release(); err != nil {
fmt.Println("release failed:", err)
}
reopened, err := AcquireFileLock(path)
fmt.Println("reacquired:", err == nil)
if err == nil {
_ = reopened.Release()
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.