| // 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) | 
 | } |