github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/lock.go (about) 1 // Contains utility functions for managing an exclusive lock file. 2 // Based on flock() underneath so 3 4 package core 5 6 import ( 7 "io" 8 "io/ioutil" 9 "os" 10 "path" 11 "strings" 12 "syscall" 13 ) 14 15 const lockFilePath = "plz-out/.lock" 16 17 var lockFile *os.File 18 19 // AcquireRepoLock opens the lock file and acquires the lock. 20 // Dies if the lock cannot be successfully acquired. 21 func AcquireRepoLock() { 22 var err error 23 // There is of course technically a bit of a race condition between the file & flock operations here, 24 // but it shouldn't matter much since we're trying to mutually exclude plz processes started by the user 25 // which (one hopes) they wouldn't normally do simultaneously. 26 os.MkdirAll(path.Dir(lockFilePath), DirPermissions) 27 // TODO(pebers): This doesn't seem quite as intended, I think the file still gets truncated sometimes. 28 // Not sure why since I'm not passing O_TRUNC... 29 if lockFile, err = os.OpenFile(lockFilePath, os.O_RDWR|os.O_CREATE, 0644); err != nil && !os.IsNotExist(err) { 30 log.Fatalf("Failed to acquire lock: %s", err) 31 } else if lockFile, err = os.Create(lockFilePath); err != nil { 32 log.Fatalf("Failed to create lock: %s", err) 33 } 34 // Try a non-blocking acquire first so we can warn the user if we're waiting. 35 log.Debug("Attempting to acquire lock %s...", lockFilePath) 36 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { 37 log.Debug("Acquired lock %s", lockFilePath) 38 } else { 39 log.Warning("Looks like another plz is already running in this repo. Waiting for it to finish...") 40 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX); err != nil { 41 log.Fatalf("Failed to acquire lock: %s", err) 42 } 43 } 44 45 // Record the operation performed. 46 if _, err = lockFile.Seek(0, io.SeekStart); err == nil { 47 if n, err := lockFile.Write([]byte(strings.Join(os.Args[1:], " ") + "\n")); err == nil { 48 lockFile.Truncate(int64(n)) 49 } 50 } 51 } 52 53 // ReleaseRepoLock releases the lock and closes the file handle. 54 // Does not die on errors, at this point it wouldn't really do any good. 55 func ReleaseRepoLock() { 56 if lockFile == nil { 57 log.Errorf("Lock file not acquired!") 58 return 59 } 60 if err := syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN); err != nil { 61 log.Errorf("Failed to release lock: %s", err) // No point making this fatal really 62 } 63 if err := lockFile.Close(); err != nil { 64 log.Errorf("Failed to close lock file: %s", err) 65 } 66 } 67 68 // ReadLastOperationOrDie reads the last operation performed from the lock file. Dies if unsuccessful. 69 func ReadLastOperationOrDie() []string { 70 contents, err := ioutil.ReadFile(lockFilePath) 71 if err != nil || len(contents) == 0 { 72 log.Fatalf("Sorry OP, can't read previous operation :(") 73 } 74 return strings.Split(strings.TrimSpace(string(contents)), " ") 75 }