Skip to content

Commit 90f62bf

Browse files
committed
fix lock race
1 parent a77d3a8 commit 90f62bf

1 file changed

Lines changed: 39 additions & 34 deletions

File tree

internal/state/lock.go

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,44 +32,49 @@ func (fl *FileLock) Lock() error {
3232
defer ticker.Stop()
3333

3434
for {
35-
select {
36-
case <-timeout:
37-
return fmt.Errorf("timeout waiting for file lock")
38-
case <-ticker.C:
39-
// Try to create lock file exclusively
40-
f, err := os.OpenFile(fl.lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
41-
if err == nil {
42-
// Successfully created lock file
43-
_, err = f.WriteString(strconv.Itoa(fl.pid))
44-
f.Close()
45-
// Check for write error
46-
if err != nil {
47-
// We got the lock but failed to write.
48-
// Best effort to clean up, then return the error.
49-
_ = os.Remove(fl.lockPath)
50-
return fmt.Errorf("failed to write pid to lock file: %v", err)
51-
}
52-
// We hold the lock
53-
return nil
35+
// Try to create lock file exclusively
36+
f, err := os.OpenFile(fl.lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
37+
if err == nil {
38+
// Successfully created lock file
39+
_, err = f.WriteString(strconv.Itoa(fl.pid))
40+
f.Close()
41+
// Check for write error
42+
if err != nil {
43+
// We got the lock but failed to write.
44+
// Best effort to clean up, then return the error.
45+
_ = os.Remove(fl.lockPath)
46+
return fmt.Errorf("failed to write pid to lock file: %v", err)
5447
}
48+
// We hold the lock
49+
return nil
50+
}
5551

56-
// If we're here, os.OpenFile failed, likely because the file exists.
57-
// Check if lock file is stale (older than 10 seconds)
58-
info, statErr := os.Stat(fl.lockPath)
59-
if statErr == nil {
60-
if time.Since(info.ModTime()) > 10*time.Second {
61-
// Lock file is stale, try to remove it
62-
err = os.Remove(fl.lockPath)
63-
64-
if err != nil && !os.IsNotExist(err) {
65-
// If we can't remove it (and it's not 'not exist'),
66-
// something is wrong (e.g., permissions).
67-
return fmt.Errorf("unable to remove stale lock: %v", err)
68-
}
52+
// If we're here, os.OpenFile failed, likely because the file exists.
53+
// Check if lock file is stale (older than 10 seconds)
54+
info, statErr := os.Stat(fl.lockPath)
55+
if statErr == nil {
56+
if time.Since(info.ModTime()) > 10*time.Second {
57+
// Lock file is stale, try to remove it
58+
removeErr := os.Remove(fl.lockPath)
59+
60+
if removeErr != nil && !os.IsNotExist(removeErr) {
61+
// If we can't remove it (and it's not 'not exist'),
62+
// something is wrong (e.g., permissions).
63+
return fmt.Errorf("unable to remove stale lock: %v", removeErr)
6964
}
65+
// Successfully removed stale lock, retry immediately
66+
// This reduces the race window significantly
67+
continue
7068
}
71-
// If stat failed (e.g., file removed between OpenFile and Stat)
72-
// or lock is not stale, just loop and wait for next tick.
69+
}
70+
71+
// If stat failed (e.g., file removed between OpenFile and Stat)
72+
// or lock is not stale, wait for next tick
73+
select {
74+
case <-timeout:
75+
return fmt.Errorf("timeout waiting for file lock")
76+
case <-ticker.C:
77+
// Continue to next iteration
7378
}
7479
}
7580
}

0 commit comments

Comments
 (0)