返回 DeepSeek-Reasonix
session_lock_unix.go
根目录 / internal / agent / session_lock_unix.go
1 //go:build !windows
2
3 package agent
4
5 import (
6 "errors"
7 "os"
8
9 "reasonix/internal/store"
10
11 "golang.org/x/sys/unix"
12 )
13
14 // tryLockSessionFile attempts the compatibility save lock once without
15 // blocking. The shared wrapper in save.go supplies the bounded retry window.
16 func tryLockSessionFile(path string) (func(), error) {
17 f, err := os.OpenFile(store.SessionLockFile(path), os.O_CREATE|os.O_RDWR, 0o600)
18 if err != nil {
19 return nil, err
20 }
21 if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
22 _ = f.Close()
23 if errors.Is(err, unix.EWOULDBLOCK) || errors.Is(err, unix.EAGAIN) {
24 return nil, ErrSessionFileLockHeld
25 }
26 return nil, err
27 }
28 return func() {
29 _ = unix.Flock(int(f.Fd()), unix.LOCK_UN)
30 _ = f.Close()
31 }, nil
32 }
33
34 // sessionLockFile is a non-blocking exclusive lock on a lock file itself,
35 // used by cleanup paths that may need to delete the file they locked.
36 type sessionLockFile struct {
37 f *os.File
38 }
39
40 // tryTakeSessionLockFile opens lockPath and takes its exclusive flock without
41 // blocking. A live holder surfaces as ErrSessionFileLockHeld.
42 func tryTakeSessionLockFile(lockPath string) (*sessionLockFile, error) {
43 f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600)
44 if err != nil {
45 return nil, err
46 }
47 if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
48 _ = f.Close()
49 if errors.Is(err, unix.EWOULDBLOCK) || errors.Is(err, unix.EAGAIN) {
50 return nil, ErrSessionFileLockHeld
51 }
52 return nil, err
53 }
54 return &sessionLockFile{f: f}, nil
55 }
56
57 func (l *sessionLockFile) Unlock() {
58 _ = unix.Flock(int(l.f.Fd()), unix.LOCK_UN)
59 _ = l.f.Close()
60 }
61
62 // RemoveAndUnlock deletes the lock file atomically with the release: the
63 // unlink happens while the flock is still held, so a waiter blocked on this
64 // inode can never adopt a file that is about to disappear for everyone else.
65 func (l *sessionLockFile) RemoveAndUnlock() error {
66 removeErr := os.Remove(l.f.Name())
67 l.Unlock()
68 if removeErr != nil && !os.IsNotExist(removeErr) {
69 return removeErr
70 }
71 return nil
72 }
73
74 func tryLockSessionLeaseFile(path string) (func(), error) {
75 f, err := os.OpenFile(store.SessionLeaseLock(path), os.O_CREATE|os.O_RDWR, 0o600)
76 if err != nil {
77 return nil, err
78 }
79 if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
80 _ = f.Close()
81 if errors.Is(err, unix.EWOULDBLOCK) || errors.Is(err, unix.EAGAIN) {
82 return nil, ErrSessionLeaseHeld
83 }
84 return nil, err
85 }
86 return func() {
87 _ = unix.Flock(int(f.Fd()), unix.LOCK_UN)
88 _ = f.Close()
89 }, nil
90 }
91
91 lines GO