package filelock import ( "errors" "fmt" "os" "path" "time" ) var ErrLocked = errors.New("failed to get a lock in a timely manner") type FileLock struct { lockPath string } func New(filepath string) *FileLock { dir, filename := path.Split(filepath) lockPath := path.Join(dir, fmt.Sprintf(".%s.lock", filename)) return &FileLock{ lockPath: lockPath, } } func (fl *FileLock) lockFileExists() bool { if _, err := os.Stat(fl.lockPath); errors.Is(err, os.ErrNotExist) { return false } return true } func (fl *FileLock) Lock(timeout time.Duration) error { start := time.Now().UTC() end := start.Add(timeout) for { if !fl.lockFileExists() { if _, err := os.Create(fl.lockPath); err != nil { return fmt.Errorf("failed to create lock: %w", err) } return nil } if time.Now().After(end) { return ErrLocked } time.Sleep(100 * time.Millisecond) } } func (fl *FileLock) Unlock() error { if err := os.Remove(fl.lockPath); err != nil { return fmt.Errorf("failed to remove lock: %w", err) } return nil }