| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 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 | } | 
| Amin Shaikh | de73078 | 2019-05-23 18:29:18 -0400 | [diff] [blame] | 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 | } | 
| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 45 | err = lockSynchronous(*lockingInfo, newSleepWaiter(lockfilePollDuration, lockfileTimeout), ctx.Logger) | 
|  | 46 | if err != nil { | 
|  | 47 | ctx.Logger.Fatal(err) | 
|  | 48 | } | 
|  | 49 | return lockingInfo | 
|  | 50 | } | 
|  | 51 |  | 
| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 52 | type lockable interface { | 
|  | 53 | tryLock() error | 
|  | 54 | Unlock() error | 
|  | 55 | description() string | 
|  | 56 | } | 
|  | 57 |  | 
|  | 58 | var _ lockable = (*fileLock)(nil) | 
|  | 59 |  | 
|  | 60 | type fileLock struct { | 
|  | 61 | File *os.File | 
|  | 62 | } | 
|  | 63 |  | 
|  | 64 | func (l fileLock) description() (path string) { | 
|  | 65 | return l.File.Name() | 
|  | 66 | } | 
|  | 67 | func (l fileLock) tryLock() (err error) { | 
|  | 68 | return syscall.Flock(int(l.File.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) | 
|  | 69 | } | 
|  | 70 | func (l fileLock) Unlock() (err error) { | 
|  | 71 | return l.File.Close() | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | func 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 |  | 
| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 88 | done, description := waiter.checkDeadline() | 
|  | 89 |  | 
| Amin Shaikh | de73078 | 2019-05-23 18:29:18 -0400 | [diff] [blame] | 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 |  | 
| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 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 { | 
| Jeff Gaston | 3615fe8 | 2017-05-24 13:14:34 -0700 | [diff] [blame] | 100 | waiter.wait() | 
|  | 101 | } | 
|  | 102 | } | 
|  | 103 | } | 
|  | 104 |  | 
|  | 105 | func 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 |  | 
|  | 118 | type waiter interface { | 
|  | 119 | wait() | 
|  | 120 | checkDeadline() (done bool, remainder string) | 
|  | 121 | summarize() (summary string) | 
|  | 122 | } | 
|  | 123 |  | 
|  | 124 | type sleepWaiter struct { | 
|  | 125 | sleepInterval time.Duration | 
|  | 126 | deadline      time.Time | 
|  | 127 |  | 
|  | 128 | totalWait time.Duration | 
|  | 129 | } | 
|  | 130 |  | 
|  | 131 | var _ waiter = (*sleepWaiter)(nil) | 
|  | 132 |  | 
|  | 133 | func newSleepWaiter(interval time.Duration, duration time.Duration) (waiter *sleepWaiter) { | 
|  | 134 | return &sleepWaiter{interval, time.Now().Add(duration), duration} | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | func (s sleepWaiter) wait() { | 
|  | 138 | time.Sleep(s.sleepInterval) | 
|  | 139 | } | 
|  | 140 | func (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 | } | 
|  | 149 | func (s sleepWaiter) summarize() (summary string) { | 
|  | 150 | return fmt.Sprintf("polling every %v until %v", s.sleepInterval, s.totalWait) | 
|  | 151 | } |