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  }