compiler_wrapper: work around gcc failing due to a kernel bug

Very rarely on old GCCs, we'll complaints about GCC itself being handed
ERESTARTSYS. Retry the compilation in those cases. Similarly, we'll
sometimes see `run()` give us random errors with things like `waitid()`,
so handle those.

BUG=chromium:1166017
TEST=CQ

Change-Id: If9b6fdc523f60719608739da0eefa94c82164ae7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2648090
Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
Tested-by: George Burgess <gbiv@chromium.org>
diff --git a/compiler_wrapper/compiler_wrapper.go b/compiler_wrapper/compiler_wrapper.go
index 21a308b..009cc87 100644
--- a/compiler_wrapper/compiler_wrapper.go
+++ b/compiler_wrapper/compiler_wrapper.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
 	"path/filepath"
@@ -74,6 +75,8 @@
 	env = mainBuilder.env
 	var compilerCmd *command
 	clangSyntax := processClangSyntaxFlag(mainBuilder)
+
+	workAroundKernelBugWithRetries := false
 	if cfg.isAndroidWrapper {
 		mainBuilder.path = calculateAndroidWrapperPath(mainBuilder.path, mainBuilder.absWrapperPath)
 		switch mainBuilder.target.compilerType {
@@ -139,6 +142,7 @@
 			if err != nil {
 				return 0, err
 			}
+			workAroundKernelBugWithRetries = true
 		}
 	}
 
@@ -174,26 +178,75 @@
 		}
 	}
 
-	commitRusage, err := maybeCaptureRusage(env, rusageLogfileName, compilerCmd, func(willLogRusage bool) error {
-		var err error
-		if willLogRusage {
-			err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr())
-		} else {
-			// Note: We return from this in non-fatal circumstances only if the
-			// underlying env is not really doing an exec, e.g. commandRecordingEnv.
-			err = env.exec(compilerCmd)
+	errRetryCompilation := errors.New("compilation retry requested")
+	var runCompiler func(willLogRusage bool) (int, error)
+	if !workAroundKernelBugWithRetries {
+		runCompiler = func(willLogRusage bool) (int, error) {
+			var err error
+			if willLogRusage {
+				err = env.run(compilerCmd, env.stdin(), env.stdout(), env.stderr())
+			} else {
+				// Note: We return from this in non-fatal circumstances only if the
+				// underlying env is not really doing an exec, e.g. commandRecordingEnv.
+				err = env.exec(compilerCmd)
+			}
+			return wrapSubprocessErrorWithSourceLoc(compilerCmd, err)
 		}
-		exitCode, err = wrapSubprocessErrorWithSourceLoc(compilerCmd, err)
-		return err
-	})
-	if err != nil {
-		return exitCode, err
-	}
-	if err := commitRusage(exitCode); err != nil {
-		return exitCode, fmt.Errorf("commiting rusage: %v", err)
+	} else {
+		getStdin, err := prebufferStdinIfNeeded(env, compilerCmd)
+		if err != nil {
+			return 0, wrapErrorwithSourceLocf(err, "prebuffering stdin: %v", err)
+		}
+
+		stdoutBuffer := &bytes.Buffer{}
+		stderrBuffer := &bytes.Buffer{}
+		retryAttempt := 0
+		runCompiler = func(willLogRusage bool) (int, error) {
+			retryAttempt++
+			stdoutBuffer.Reset()
+			stderrBuffer.Reset()
+
+			exitCode, compilerErr := wrapSubprocessErrorWithSourceLoc(compilerCmd,
+				env.run(compilerCmd, getStdin(), stdoutBuffer, stderrBuffer))
+
+			if compilerErr != nil || exitCode != 0 {
+				if retryAttempt < kernelBugRetryLimit && (errorContainsTracesOfKernelBug(compilerErr) || containsTracesOfKernelBug(stdoutBuffer.Bytes()) || containsTracesOfKernelBug(stderrBuffer.Bytes())) {
+					return exitCode, errRetryCompilation
+				}
+			}
+			_, stdoutErr := stdoutBuffer.WriteTo(env.stdout())
+			_, stderrErr := stderrBuffer.WriteTo(env.stderr())
+			if stdoutErr != nil {
+				return exitCode, wrapErrorwithSourceLocf(err, "writing stdout: %v", stdoutErr)
+			}
+			if stderrErr != nil {
+				return exitCode, wrapErrorwithSourceLocf(err, "writing stderr: %v", stderrErr)
+			}
+			return exitCode, compilerErr
+		}
 	}
 
-	return exitCode, err
+	for {
+		var exitCode int
+		commitRusage, err := maybeCaptureRusage(env, rusageLogfileName, compilerCmd, func(willLogRusage bool) error {
+			var err error
+			exitCode, err = runCompiler(willLogRusage)
+			return err
+		})
+
+		switch {
+		case err == errRetryCompilation:
+			// Loop around again.
+		case err != nil:
+			return exitCode, err
+		default:
+			if err := commitRusage(exitCode); err != nil {
+				return exitCode, fmt.Errorf("commiting rusage: %v", err)
+			}
+
+			return exitCode, err
+		}
+	}
 }
 
 func prepareClangCommand(builder *commandBuilder) (err error) {