1 // Copyright 2017 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package build 16 17 import ( 18 "errors" 19 "fmt" 20 "math" 21 "os" 22 "path/filepath" 23 "syscall" 24 "time" 25 26 "android/soong/ui/logger" 27 ) 28 29 // This file provides cross-process synchronization methods 30 // i.e. making sure only one Soong process is running for a given output directory 31 32 func BecomeSingletonOrFail(ctx Context, config Config) (lock *fileLock) { 33 lockingInfo, err := newLock(config.OutDir()) 34 if err != nil { 35 ctx.Logger.Fatal(err) 36 } 37 err = lockSynchronous(*lockingInfo, newSleepWaiter(lockfilePollDuration, lockfileTimeout), ctx.Logger) 38 if err != nil { 39 ctx.Logger.Fatal(err) 40 } 41 return lockingInfo 42 } 43 44 var lockfileTimeout = time.Second * 10 45 var lockfilePollDuration = time.Second 46 47 type lockable interface { 48 tryLock() error 49 Unlock() error 50 description() string 51 } 52 53 var _ lockable = (*fileLock)(nil) 54 55 type fileLock struct { 56 File *os.File 57 } 58 59 func (l fileLock) description() (path string) { 60 return l.File.Name() 61 } 62 func (l fileLock) tryLock() (err error) { 63 return syscall.Flock(int(l.File.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 64 } 65 func (l fileLock) Unlock() (err error) { 66 return l.File.Close() 67 } 68 69 func lockSynchronous(lock lockable, waiter waiter, logger logger.Logger) (err error) { 70 71 waited := false 72 73 for { 74 err = lock.tryLock() 75 if err == nil { 76 if waited { 77 // If we had to wait at all, then when the wait is done, we inform the user 78 logger.Printf("Acquired lock on %v; previous Soong process must have completed. Continuing...\n", lock.description()) 79 } 80 return nil 81 } 82 83 waited = true 84 85 done, description := waiter.checkDeadline() 86 87 if done { 88 return fmt.Errorf("Tried to lock %s, but timed out %s . Make sure no other Soong process is using it", 89 lock.description(), waiter.summarize()) 90 } else { 91 logger.Printf("Waiting up to %s to lock %v to ensure no other Soong process is running in the same output directory\n", description, lock.description()) 92 waiter.wait() 93 } 94 } 95 } 96 97 func newLock(basedir string) (lock *fileLock, err error) { 98 lockPath := filepath.Join(basedir, ".lock") 99 100 os.MkdirAll(basedir, 0777) 101 lockfileDescriptor, err := os.OpenFile(lockPath, os.O_RDWR|os.O_CREATE, 0666) 102 if err != nil { 103 return nil, errors.New("failed to open " + lockPath) 104 } 105 lockingInfo := &fileLock{File: lockfileDescriptor} 106 107 return lockingInfo, nil 108 } 109 110 type waiter interface { 111 wait() 112 checkDeadline() (done bool, remainder string) 113 summarize() (summary string) 114 } 115 116 type sleepWaiter struct { 117 sleepInterval time.Duration 118 deadline time.Time 119 120 totalWait time.Duration 121 } 122 123 var _ waiter = (*sleepWaiter)(nil) 124 125 func newSleepWaiter(interval time.Duration, duration time.Duration) (waiter *sleepWaiter) { 126 return &sleepWaiter{interval, time.Now().Add(duration), duration} 127 } 128 129 func (s sleepWaiter) wait() { 130 time.Sleep(s.sleepInterval) 131 } 132 func (s *sleepWaiter) checkDeadline() (done bool, remainder string) { 133 remainingSleep := s.deadline.Sub(time.Now()) 134 numSecondsRounded := math.Floor(remainingSleep.Seconds()*10+0.5) / 10 135 if remainingSleep > 0 { 136 return false, fmt.Sprintf("%vs", numSecondsRounded) 137 } else { 138 return true, "" 139 } 140 } 141 func (s sleepWaiter) summarize() (summary string) { 142 return fmt.Sprintf("polling every %v until %v", s.sleepInterval, s.totalWait) 143 } 144