github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/utils/lock/lock.go (about)

     1  package lock
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/jfrog/jfrog-cli-go/utils/cliutils"
     7  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
     8  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
     9  	"github.com/jfrog/jfrog-client-go/utils/log"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  type Lock struct {
    19  	// The current time when the lock was created
    20  	currentTime int64
    21  	// The full path to the lock file.
    22  	fileName string
    23  	pid      int
    24  }
    25  
    26  type Locks []Lock
    27  
    28  func (locks Locks) Len() int {
    29  	return len(locks)
    30  }
    31  
    32  func (locks Locks) Swap(i, j int) {
    33  	locks[i], locks[j] = locks[j], locks[i]
    34  }
    35  
    36  func (locks Locks) Less(i, j int) bool {
    37  	return locks[i].currentTime < locks[j].currentTime
    38  }
    39  
    40  // Creating a new lock object.
    41  func (lock *Lock) CreateNewLockFile() error {
    42  	lock.currentTime = time.Now().UnixNano()
    43  	folderName, err := CreateLockDir()
    44  	if err != nil {
    45  		return err
    46  	}
    47  	pid := os.Getpid()
    48  	lock.pid = pid
    49  	err = lock.CreateFile(folderName, pid)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	return nil
    54  }
    55  
    56  func CreateLockDir() (string, error) {
    57  	return cliutils.CreateDirInJfrogHome("lock")
    58  }
    59  
    60  func (lock *Lock) CreateFile(folderName string, pid int) error {
    61  	// We are creating an empty file with the pid and current time part of the name
    62  	lock.fileName = filepath.Join(folderName, "jfrog-cli.conf.lck."+strconv.Itoa(pid)+"."+strconv.FormatInt(lock.currentTime, 10))
    63  	log.Debug("Creating lock file: ", lock.fileName)
    64  	file, err := os.OpenFile(lock.fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
    65  	if err != nil {
    66  		return errorutils.CheckError(err)
    67  	}
    68  
    69  	if err = file.Close(); err != nil {
    70  		return errorutils.CheckError(err)
    71  	}
    72  	return nil
    73  }
    74  
    75  // Try to acquire a lock
    76  func (lock *Lock) Lock() error {
    77  	filesList, err := lock.getListOfFiles()
    78  	if err != nil {
    79  		return err
    80  	}
    81  	i := 0
    82  	// Trying 1200 times to acquire a lock
    83  	for i <= 1200 {
    84  		// If only one file, means that the process that is running is the one that created the file.
    85  		// We can continue
    86  		if len(filesList) == 1 {
    87  			return nil
    88  		}
    89  
    90  		locks, err := lock.getLocks(filesList)
    91  		if err != nil {
    92  			return err
    93  		}
    94  		// If the first timestamp in the sorted locks slice is equal to this timestamp
    95  		// means that the lock can be acquired
    96  		if locks[0].currentTime == lock.currentTime {
    97  			// Edge case, if at the same time (by the nano seconds) two different process created two files.
    98  			// We are checking the PID to know which process can run.
    99  			if locks[0].pid != lock.pid {
   100  				err := lock.removeOtherLockOrWait(locks[0], &filesList)
   101  				if err != nil {
   102  					return err
   103  				}
   104  			} else {
   105  				log.Debug("Lock has been acquired for", lock.fileName)
   106  				return nil
   107  			}
   108  		} else {
   109  			err := lock.removeOtherLockOrWait(locks[0], &filesList)
   110  			if err != nil {
   111  				return err
   112  			}
   113  		}
   114  		i++
   115  	}
   116  	return errors.New("Lock hasn't been acquired.")
   117  }
   118  
   119  // Checks if other lock file still exists.
   120  // Or the process that created the lock still running.
   121  func (lock *Lock) removeOtherLockOrWait(otherLock Lock, filesList *[]string) error {
   122  	// Check if file exists.
   123  	exists, err := fileutils.IsFileExists(otherLock.fileName, false)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	if !exists {
   129  		// Process already finished. Update the list.
   130  		*filesList, err = lock.getListOfFiles()
   131  		if err != nil {
   132  			return err
   133  		}
   134  		return nil
   135  	}
   136  	log.Debug("Lock hasn't been acquired.")
   137  
   138  	// Check if the process is running.
   139  	// There are two implementation of the 'isProcessRunning'.
   140  	// One for Windows and one for Unix based systems.
   141  	running, err := isProcessRunning(otherLock.pid)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	if !running {
   147  		log.Debug(fmt.Sprintf("Removing lock file %s since the creating process is no longer running", otherLock.fileName))
   148  		err := otherLock.Unlock()
   149  		// Update list of files
   150  		*filesList, err = lock.getListOfFiles()
   151  		if err != nil {
   152  			return err
   153  		}
   154  		return nil
   155  	}
   156  	// Other process is running. Wait.
   157  	time.Sleep(100 * time.Millisecond)
   158  	return nil
   159  }
   160  
   161  func (lock *Lock) getListOfFiles() ([]string, error) {
   162  	// Listing all the files in the lock directory
   163  	filesList, err := fileutils.ListFiles(filepath.Dir(lock.fileName), false)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return filesList, nil
   168  }
   169  
   170  // Returns a list of all available locks.
   171  func (lock *Lock) getLocks(filesList []string) (Locks, error) {
   172  	// Slice of all the timestamps that currently the lock directory has
   173  	var files Locks
   174  	for _, path := range filesList {
   175  		fileName := filepath.Base(path)
   176  		splitted := strings.Split(fileName, ".")
   177  
   178  		if len(splitted) != 5 {
   179  			return nil, errorutils.CheckError(fmt.Errorf("Failed while parsing the file name: %s located at: %s. Expecting a different format.", fileName, path))
   180  		}
   181  		// Last element is the timestamp.
   182  		time, err := strconv.ParseInt(splitted[4], 10, 64)
   183  		if err != nil {
   184  			return nil, errorutils.CheckError(err)
   185  		}
   186  		pid, err := strconv.Atoi(splitted[3])
   187  		if err != nil {
   188  			return nil, errorutils.CheckError(err)
   189  		}
   190  		file := Lock{
   191  			currentTime: time,
   192  			pid:         pid,
   193  			fileName:    path,
   194  		}
   195  		files = append(files, file)
   196  	}
   197  	sort.Sort(files)
   198  	return files, nil
   199  }
   200  
   201  // Removes the lock file so other process can continue.
   202  func (lock *Lock) Unlock() error {
   203  	log.Debug("Releasing lock: ", lock.fileName)
   204  	exists, err := fileutils.IsFileExists(lock.fileName, false)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	if exists {
   210  		err = os.Remove(lock.fileName)
   211  		if err != nil {
   212  			return errorutils.CheckError(err)
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  func CreateLock() (Lock, error) {
   219  	lockFile := new(Lock)
   220  	err := lockFile.CreateNewLockFile()
   221  
   222  	if err != nil {
   223  		return *lockFile, err
   224  	}
   225  
   226  	// Trying to acquire a lock for the running process.
   227  	err = lockFile.Lock()
   228  	if err != nil {
   229  		return *lockFile, errorutils.CheckError(err)
   230  	}
   231  	return *lockFile, nil
   232  }