github.com/Kasita-Inc/glide@v0.13.2-0.20171213220317-0274b9278c36/cache/global_lock.go (about)

     1  package cache
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/signal"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/Kasita-Inc/glide/msg"
    12  	gpath "github.com/Kasita-Inc/glide/path"
    13  )
    14  
    15  var isStarted bool
    16  
    17  // If the global cache lock file should be written
    18  var shouldWriteLock = true
    19  
    20  // SystemLock starts a system rather than application lock. This way multiple
    21  // app instances don't cause race conditions when working in the cache.
    22  func SystemLock() error {
    23  	if isStarted {
    24  		return nil
    25  	}
    26  	err := waitOnLock()
    27  	if err != nil {
    28  		return err
    29  	}
    30  	err = startLock()
    31  	isStarted = true
    32  	return err
    33  }
    34  
    35  // SystemUnlock removes the system wide Glide cache lock.
    36  func SystemUnlock() {
    37  	lockdone <- struct{}{}
    38  	os.Remove(lockFileName)
    39  }
    40  
    41  var lockdone = make(chan struct{}, 1)
    42  
    43  type lockdata struct {
    44  	Comment string `json:"comment"`
    45  	Pid     int    `json:"pid"`
    46  	Time    string `json:"time"`
    47  }
    48  
    49  var lockFileName = filepath.Join(gpath.Home(), "lock.json")
    50  
    51  // Write a lock for now.
    52  func writeLock() error {
    53  
    54  	// If the lock should not be written exit immediately. This happens in cases
    55  	// where shutdown/clean is happening.
    56  	if !shouldWriteLock {
    57  		return nil
    58  	}
    59  
    60  	ld := &lockdata{
    61  		Comment: "File managed by Glide (https://glide.sh)",
    62  		Pid:     os.Getpid(),
    63  		Time:    time.Now().Format(time.RFC3339Nano),
    64  	}
    65  
    66  	out, err := json.Marshal(ld)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	err = ioutil.WriteFile(lockFileName, out, 0755)
    71  	return err
    72  }
    73  
    74  func startLock() error {
    75  	err := writeLock()
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	go func() {
    81  		for {
    82  			select {
    83  			case <-lockdone:
    84  				return
    85  			default:
    86  				time.Sleep(10 * time.Second)
    87  				err := writeLock()
    88  				if err != nil {
    89  					msg.Die("Error using Glide lock: %s", err)
    90  				}
    91  			}
    92  		}
    93  	}()
    94  
    95  	// Capture ctrl-c or other interruptions then clean up the global lock.
    96  	ch := make(chan os.Signal)
    97  	signal.Notify(ch, os.Interrupt, os.Kill)
    98  	go func(cc <-chan os.Signal) {
    99  		s := <-cc
   100  		shouldWriteLock = false
   101  		SystemUnlock()
   102  
   103  		// Exiting with the expected exit codes when we can.
   104  		if s == os.Interrupt {
   105  			os.Exit(130)
   106  		} else if s == os.Kill {
   107  			os.Exit(137)
   108  		} else {
   109  			os.Exit(1)
   110  		}
   111  	}(ch)
   112  
   113  	return nil
   114  }
   115  
   116  func waitOnLock() error {
   117  	var announced bool
   118  	for {
   119  		fi, err := os.Stat(lockFileName)
   120  		if err != nil && os.IsNotExist(err) {
   121  			return nil
   122  		} else if err != nil {
   123  			return err
   124  		}
   125  
   126  		diff := time.Now().Sub(fi.ModTime())
   127  		if diff.Seconds() > 15 {
   128  			return nil
   129  		}
   130  
   131  		if !announced {
   132  			announced = true
   133  			msg.Info("Waiting on Glide global cache access")
   134  		}
   135  
   136  		// Check on the lock file every second.
   137  		time.Sleep(time.Second)
   138  	}
   139  }