85 lines
2.2 KiB
Go
85 lines
2.2 KiB
Go
//go:build linux
|
|
|
|
package processlock
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Lock 持有与文件描述符绑定的 Linux advisory flock。
|
|
// 锁文件可以永久存在;只有内核锁状态表示当前所有权。
|
|
type Lock struct {
|
|
mu sync.Mutex
|
|
file *os.File
|
|
}
|
|
|
|
// Acquire 以非阻塞方式获取排他锁,并把当前 PID 写入文件用于诊断。
|
|
func Acquire(path string) (*Lock, error) {
|
|
if path == "" {
|
|
return nil, errors.New("process lock path is required")
|
|
}
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolve process lock path: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(absPath), 0o750); err != nil {
|
|
return nil, fmt.Errorf("create process lock directory: %w", err)
|
|
}
|
|
file, err := os.OpenFile(absPath, os.O_CREATE|os.O_RDWR, 0o640)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open process lock: %w", err)
|
|
}
|
|
if err := unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
|
|
_ = file.Close()
|
|
if errors.Is(err, unix.EWOULDBLOCK) || errors.Is(err, unix.EAGAIN) {
|
|
return nil, ErrAlreadyLocked
|
|
}
|
|
return nil, fmt.Errorf("acquire process lock: %w", err)
|
|
}
|
|
if err := writeOwnerPID(file); err != nil {
|
|
_ = unix.Flock(int(file.Fd()), unix.LOCK_UN)
|
|
_ = file.Close()
|
|
return nil, err
|
|
}
|
|
return &Lock{file: file}, nil
|
|
}
|
|
|
|
func writeOwnerPID(file *os.File) error {
|
|
if err := file.Truncate(0); err != nil {
|
|
return fmt.Errorf("truncate process lock metadata: %w", err)
|
|
}
|
|
if _, err := file.Seek(0, 0); err != nil {
|
|
return fmt.Errorf("seek process lock metadata: %w", err)
|
|
}
|
|
if _, err := fmt.Fprintf(file, "%d\n", os.Getpid()); err != nil {
|
|
return fmt.Errorf("write process lock metadata: %w", err)
|
|
}
|
|
if err := file.Sync(); err != nil {
|
|
return fmt.Errorf("flush process lock metadata: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close 释放内核锁并关闭文件描述符,不删除锁文件。
|
|
func (l *Lock) Close() error {
|
|
if l == nil {
|
|
return nil
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if l.file == nil {
|
|
return nil
|
|
}
|
|
file := l.file
|
|
l.file = nil
|
|
unlockErr := unix.Flock(int(file.Fd()), unix.LOCK_UN)
|
|
closeErr := file.Close()
|
|
return errors.Join(unlockErr, closeErr)
|
|
}
|