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 15package build 16 17import ( 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 32func BecomeSingletonOrFail(ctx Context, config Config) (lock *fileLock) { 33 lockingInfo, err := newLock(config.OutDir()) 34 if err != nil { 35 ctx.Logger.Fatal(err) 36 } 37 lockfilePollDuration := time.Second 38 lockfileTimeout := time.Second * 10 39 if envTimeout := os.Getenv("SOONG_LOCK_TIMEOUT"); envTimeout != "" { 40 lockfileTimeout, err = time.ParseDuration(envTimeout) 41 if err != nil { 42 ctx.Logger.Fatalf("failure parsing SOONG_LOCK_TIMEOUT %q: %s", envTimeout, err) 43 } 44 } 45 err = lockSynchronous(*lockingInfo, newSleepWaiter(lockfilePollDuration, lockfileTimeout), ctx.Logger) 46 if err != nil { 47 ctx.Logger.Fatal(err) 48 } 49 return lockingInfo 50} 51 52type lockable interface { 53 tryLock() error 54 Unlock() error 55 description() string 56} 57 58var _ lockable = (*fileLock)(nil) 59 60type fileLock struct { 61 File *os.File 62} 63 64func (l fileLock) description() (path string) { 65 return l.File.Name() 66} 67func (l fileLock) tryLock() (err error) { 68 return syscall.Flock(int(l.File.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 69} 70func (l fileLock) Unlock() (err error) { 71 return l.File.Close() 72} 73 74func lockSynchronous(lock lockable, waiter waiter, logger logger.Logger) (err error) { 75 76 waited := false 77 78 for { 79 err = lock.tryLock() 80 if err == nil { 81 if waited { 82 // If we had to wait at all, then when the wait is done, we inform the user 83 logger.Printf("Acquired lock on %v; previous Soong process must have completed. Continuing...\n", lock.description()) 84 } 85 return nil 86 } 87 88 done, description := waiter.checkDeadline() 89 90 if !waited { 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 } 93 94 waited = true 95 96 if done { 97 return fmt.Errorf("Tried to lock %s, but timed out %s . Make sure no other Soong process is using it", 98 lock.description(), waiter.summarize()) 99 } else { 100 waiter.wait() 101 } 102 } 103} 104 105func newLock(basedir string) (lock *fileLock, err error) { 106 lockPath := filepath.Join(basedir, ".lock") 107 108 os.MkdirAll(basedir, 0777) 109 lockfileDescriptor, err := os.OpenFile(lockPath, os.O_RDWR|os.O_CREATE, 0666) 110 if err != nil { 111 return nil, errors.New("failed to open " + lockPath) 112 } 113 lockingInfo := &fileLock{File: lockfileDescriptor} 114 115 return lockingInfo, nil 116} 117 118type waiter interface { 119 wait() 120 checkDeadline() (done bool, remainder string) 121 summarize() (summary string) 122} 123 124type sleepWaiter struct { 125 sleepInterval time.Duration 126 deadline time.Time 127 128 totalWait time.Duration 129} 130 131var _ waiter = (*sleepWaiter)(nil) 132 133func newSleepWaiter(interval time.Duration, duration time.Duration) (waiter *sleepWaiter) { 134 return &sleepWaiter{interval, time.Now().Add(duration), duration} 135} 136 137func (s sleepWaiter) wait() { 138 time.Sleep(s.sleepInterval) 139} 140func (s *sleepWaiter) checkDeadline() (done bool, remainder string) { 141 remainingSleep := s.deadline.Sub(time.Now()) 142 numSecondsRounded := math.Floor(remainingSleep.Seconds()*10+0.5) / 10 143 if remainingSleep > 0 { 144 return false, fmt.Sprintf("%vs", numSecondsRounded) 145 } else { 146 return true, "" 147 } 148} 149func (s sleepWaiter) summarize() (summary string) { 150 return fmt.Sprintf("polling every %v until %v", s.sleepInterval, s.totalWait) 151} 152