Revert "Revert "Disallow multiple build executions in parallel""

Bug: 36698966
Test: m -j & m -j # (run two builds at once)

This reverts commit be9cd1192bf8774f529a1ca6d2b8ee3876a80d02.

Change-Id: Ia15c0bf156a6de54da054eab8134f36bae0823ca
diff --git a/ui/build/proc_sync.go b/ui/build/proc_sync.go
new file mode 100644
index 0000000..857786d
--- /dev/null
+++ b/ui/build/proc_sync.go
@@ -0,0 +1,143 @@
+// 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)
+	}
+	err = lockSynchronous(*lockingInfo, newSleepWaiter(lockfilePollDuration, lockfileTimeout), ctx.Logger)
+	if err != nil {
+		ctx.Logger.Fatal(err)
+	}
+	return lockingInfo
+}
+
+var lockfileTimeout = time.Second * 10
+var lockfilePollDuration = time.Second
+
+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
+		}
+
+		waited = true
+
+		done, description := waiter.checkDeadline()
+
+		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 {
+			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())
+			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)
+}