|  | // Copyright 2017 Google Inc. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package build | 
|  |  | 
|  | import ( | 
|  | "errors" | 
|  | "fmt" | 
|  | "math" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "syscall" | 
|  | "time" | 
|  |  | 
|  | "android/soong/ui/logger" | 
|  | ) | 
|  |  | 
|  | // This file provides cross-process synchronization methods | 
|  | // i.e. making sure only one Soong process is running for a given output directory | 
|  |  | 
|  | func BecomeSingletonOrFail(ctx Context, config Config) (lock *fileLock) { | 
|  | lockingInfo, err := newLock(config.OutDir()) | 
|  | if err != nil { | 
|  | ctx.Logger.Fatal(err) | 
|  | } | 
|  | lockfilePollDuration := time.Second | 
|  | lockfileTimeout := time.Second * 10 | 
|  | if envTimeout := os.Getenv("SOONG_LOCK_TIMEOUT"); envTimeout != "" { | 
|  | lockfileTimeout, err = time.ParseDuration(envTimeout) | 
|  | if err != nil { | 
|  | ctx.Logger.Fatalf("failure parsing SOONG_LOCK_TIMEOUT %q: %s", envTimeout, err) | 
|  | } | 
|  | } | 
|  | err = lockSynchronous(*lockingInfo, newSleepWaiter(lockfilePollDuration, lockfileTimeout), ctx.Logger) | 
|  | if err != nil { | 
|  | ctx.Logger.Fatal(err) | 
|  | } | 
|  | return lockingInfo | 
|  | } | 
|  |  | 
|  | type lockable interface { | 
|  | tryLock() error | 
|  | Unlock() error | 
|  | description() string | 
|  | } | 
|  |  | 
|  | var _ lockable = (*fileLock)(nil) | 
|  |  | 
|  | type fileLock struct { | 
|  | File *os.File | 
|  | } | 
|  |  | 
|  | func (l fileLock) description() (path string) { | 
|  | return l.File.Name() | 
|  | } | 
|  | func (l fileLock) tryLock() (err error) { | 
|  | return syscall.Flock(int(l.File.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) | 
|  | } | 
|  | func (l fileLock) Unlock() (err error) { | 
|  | return l.File.Close() | 
|  | } | 
|  |  | 
|  | func lockSynchronous(lock lockable, waiter waiter, logger logger.Logger) (err error) { | 
|  |  | 
|  | waited := false | 
|  |  | 
|  | for { | 
|  | err = lock.tryLock() | 
|  | if err == nil { | 
|  | if waited { | 
|  | // If we had to wait at all, then when the wait is done, we inform the user | 
|  | logger.Printf("Acquired lock on %v; previous Soong process must have completed. Continuing...\n", lock.description()) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | done, description := waiter.checkDeadline() | 
|  |  | 
|  | if !waited { | 
|  | 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()) | 
|  | } | 
|  |  | 
|  | waited = true | 
|  |  | 
|  | if done { | 
|  | return fmt.Errorf("Tried to lock %s, but timed out %s . Make sure no other Soong process is using it", | 
|  | lock.description(), waiter.summarize()) | 
|  | } else { | 
|  | waiter.wait() | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func newLock(basedir string) (lock *fileLock, err error) { | 
|  | lockPath := filepath.Join(basedir, ".lock") | 
|  |  | 
|  | os.MkdirAll(basedir, 0777) | 
|  | lockfileDescriptor, err := os.OpenFile(lockPath, os.O_RDWR|os.O_CREATE, 0666) | 
|  | if err != nil { | 
|  | return nil, errors.New("failed to open " + lockPath) | 
|  | } | 
|  | lockingInfo := &fileLock{File: lockfileDescriptor} | 
|  |  | 
|  | return lockingInfo, nil | 
|  | } | 
|  |  | 
|  | type waiter interface { | 
|  | wait() | 
|  | checkDeadline() (done bool, remainder string) | 
|  | summarize() (summary string) | 
|  | } | 
|  |  | 
|  | type sleepWaiter struct { | 
|  | sleepInterval time.Duration | 
|  | deadline      time.Time | 
|  |  | 
|  | totalWait time.Duration | 
|  | } | 
|  |  | 
|  | var _ waiter = (*sleepWaiter)(nil) | 
|  |  | 
|  | func newSleepWaiter(interval time.Duration, duration time.Duration) (waiter *sleepWaiter) { | 
|  | return &sleepWaiter{interval, time.Now().Add(duration), duration} | 
|  | } | 
|  |  | 
|  | func (s sleepWaiter) wait() { | 
|  | time.Sleep(s.sleepInterval) | 
|  | } | 
|  | func (s *sleepWaiter) checkDeadline() (done bool, remainder string) { | 
|  | remainingSleep := s.deadline.Sub(time.Now()) | 
|  | numSecondsRounded := math.Floor(remainingSleep.Seconds()*10+0.5) / 10 | 
|  | if remainingSleep > 0 { | 
|  | return false, fmt.Sprintf("%vs", numSecondsRounded) | 
|  | } else { | 
|  | return true, "" | 
|  | } | 
|  | } | 
|  | func (s sleepWaiter) summarize() (summary string) { | 
|  | return fmt.Sprintf("polling every %v until %v", s.sleepInterval, s.totalWait) | 
|  | } |