blob: d85283df4b6fb91e4a7a8432fc4b0f8bb3920978 [file] [log] [blame]
package cache
import (
"encoding/json"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"time"
"github.com/Masterminds/glide/msg"
gpath "github.com/Masterminds/glide/path"
)
var isStarted bool
// If the global cache lock file should be written
var shouldWriteLock = true
// SystemLock starts a system rather than application lock. This way multiple
// app instances don't cause race conditions when working in the cache.
func SystemLock() error {
if isStarted {
return nil
}
err := waitOnLock()
if err != nil {
return err
}
err = startLock()
isStarted = true
return err
}
// SystemUnlock removes the system wide Glide cache lock.
func SystemUnlock() {
lockdone <- struct{}{}
os.Remove(lockFileName)
}
var lockdone = make(chan struct{}, 1)
type lockdata struct {
Comment string `json:"comment"`
Pid int `json:"pid"`
Time string `json:"time"`
}
var lockFileName = filepath.Join(gpath.Home(), "lock.json")
// Write a lock for now.
func writeLock() error {
// If the lock should not be written exit immediately. This happens in cases
// where shutdown/clean is happening.
if !shouldWriteLock {
return nil
}
ld := &lockdata{
Comment: "File managed by Glide (https://glide.sh)",
Pid: os.Getpid(),
Time: time.Now().Format(time.RFC3339Nano),
}
out, err := json.Marshal(ld)
if err != nil {
return err
}
err = ioutil.WriteFile(lockFileName, out, 0755)
return err
}
func startLock() error {
err := writeLock()
if err != nil {
return err
}
go func() {
for {
select {
case <-lockdone:
return
default:
time.Sleep(10 * time.Second)
err := writeLock()
if err != nil {
msg.Die("Error using Glide lock: %s", err)
}
}
}
}()
// Capture ctrl-c or other interruptions then clean up the global lock.
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt, os.Kill)
go func(cc <-chan os.Signal) {
s := <-cc
shouldWriteLock = false
SystemUnlock()
// Exiting with the expected exit codes when we can.
if s == os.Interrupt {
os.Exit(130)
} else if s == os.Kill {
os.Exit(137)
} else {
os.Exit(1)
}
}(ch)
return nil
}
func waitOnLock() error {
var announced bool
for {
fi, err := os.Stat(lockFileName)
if err != nil && os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
diff := time.Now().Sub(fi.ModTime())
if diff.Seconds() > 15 {
return nil
}
if !announced {
announced = true
msg.Info("Waiting on Glide global cache access")
}
// Check on the lock file every second.
time.Sleep(time.Second)
}
}