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 }