github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/cmd/reproxy/main.go (about)

     1  // Copyright 2023 Google LLC
     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  // Binary reproxy is a long running server that rewrapper binary talks to
    16  // for fast and efficient remote-execution and caching of various types of actions.
    17  package main
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"net/http"
    25  	_ "net/http/pprof"
    26  	"os"
    27  	"os/signal"
    28  	"runtime"
    29  	"runtime/pprof"
    30  	"strings"
    31  	"sync"
    32  	"syscall"
    33  	"time"
    34  
    35  	"github.com/bazelbuild/reclient/internal/pkg/auth"
    36  	"github.com/bazelbuild/reclient/internal/pkg/ignoremismatch"
    37  	"github.com/bazelbuild/reclient/internal/pkg/interceptors"
    38  	"github.com/bazelbuild/reclient/internal/pkg/ipc"
    39  	"github.com/bazelbuild/reclient/internal/pkg/localresources"
    40  	"github.com/bazelbuild/reclient/internal/pkg/localresources/usage"
    41  	"github.com/bazelbuild/reclient/internal/pkg/logger"
    42  	"github.com/bazelbuild/reclient/internal/pkg/loghttp"
    43  	"github.com/bazelbuild/reclient/internal/pkg/monitoring"
    44  	"github.com/bazelbuild/reclient/internal/pkg/pathtranslator"
    45  	"github.com/bazelbuild/reclient/internal/pkg/rbeflag"
    46  	"github.com/bazelbuild/reclient/internal/pkg/reproxy"
    47  	"github.com/bazelbuild/reclient/internal/pkg/reproxypid"
    48  	"github.com/bazelbuild/reclient/internal/pkg/stats"
    49  	"github.com/bazelbuild/reclient/internal/pkg/subprocess"
    50  	"github.com/bazelbuild/reclient/pkg/inputprocessor"
    51  	"github.com/bazelbuild/reclient/pkg/version"
    52  
    53  	"cloud.google.com/go/profiler"
    54  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/client"
    55  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/rexec"
    56  
    57  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata"
    58  	"google.golang.org/grpc"
    59  	"google.golang.org/grpc/codes"
    60  	"google.golang.org/grpc/status"
    61  
    62  	pb "github.com/bazelbuild/reclient/api/proxy"
    63  
    64  	rflags "github.com/bazelbuild/remote-apis-sdks/go/pkg/flags"
    65  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/moreflag"
    66  	log "github.com/golang/glog"
    67  )
    68  
    69  var (
    70  	homeDir, _ = os.UserHomeDir()
    71  	labels     = make(map[string]string)
    72  	start      = time.Now()
    73  )
    74  
    75  var (
    76  	proxyLogDir                []string
    77  	clangDepScanIgnoredPlugins = flag.String("clang_depscan_ignored_plugins", "", `Comma-separated list of plugins that should be ignored by clang dependency scanner.
    78  	Use this flag if you're using custom llvm build as your toolchain and your llvm plugins cause dependency scanning failures.`)
    79  	serverAddr               = flag.String("server_address", "127.0.0.1:8000", "The server address in the format of host:port for network, or unix:///file for unix domain sockets.")
    80  	logFormat                = flag.String("log_format", "reducedtext", "Format of proxy log. Currently only text and reducedtext are supported. Defaults to reducedtext.")
    81  	logPath                  = flag.String("log_path", "", "DEPRECATED. Use proxy_log_dir instead. If provided, the path to a log file of all executed records. The format is e.g. text://full/file/path.")
    82  	mismatchIgnoreConfigPath = flag.String("mismatch_ignore_config_path", "", "If provided, mismatches will be ignored according to the provided rule config.")
    83  	enableDepsCache          = flag.Bool("enable_deps_cache", false, "Enables the deps cache if --cache_dir is provided")
    84  	cacheDir                 = flag.String("cache_dir", "", "Directory from which to load the cache files at startup and update at shutdown.")
    85  	keepRecords              = flag.Int("num_records_to_keep", 0, "The number of last executed records to keep in memory for serving.")
    86  	// TODO(b/157446611): remove this flag.
    87  	_                     = flag.String("cpp_dependency_scanner_plugin", "", "Deprecated: Location of the CPP dependency scanner plugin.")
    88  	localResourceFraction = flag.Float64("local_resource_fraction", 1, "Number [0,1] indicating how much of the local machine resources are available for local execution, 1 being all of the machine's CPUs and RAM, 0 being no resources available for local execution.")
    89  	cacheSilo             = flag.String("cache_silo", "", "Cache silo key to be used for all the actions. Usually used to segregate cache-hits between various builds.")
    90  	versionCacheSilo      = flag.Bool("version_cache_silo", false, "Indicates whether to add a re-client version as cache-silo key to all remotely-executed actions. Not applicable for actions run in local-execution-remote-cache (LERC) mode.")
    91  	remoteDisabled        = flag.Bool("remote_disabled", false, "Whether to disable all remote operations and run all actions locally.")
    92  	dumpInputTree         = flag.Bool("dump_input_tree", false, "Whether to dump the input tree of received actions to the tmp directory.")
    93  	useUnifiedCASOps      = flag.Bool("use_unified_cas_ops", false, "Deprecated: use_unified_uploads/downloads instead. Whether to use the unified uploader / downloader for deduplicating uploads / downloads.")
    94  	useUnifiedUploads     = flag.Bool("use_unified_uploads", false, "Whether to use the unified uploader for deduplicating uploads.")
    95  	uploadBufferSize      = flag.Int("upload_buffer_size", 10000, "Buffer size to flush unified uploader daemon.")
    96  	uploadTickDuration    = flag.Duration("upload_tick_duration", 50*time.Millisecond, "How often to flush unified uploader daemon.")
    97  	useUnifiedDownloads   = flag.Bool("use_unified_downloads", false, "Whether to use the unified downloader for deduplicating downloads.")
    98  	downloadBufferSize    = flag.Int("download_buffer_size", 10000, "Buffer size to flush unified downloader daemon.")
    99  	downloadTickDuration  = flag.Duration("download_tick_duration", 50*time.Millisecond, "How often to flush unified downloader daemon.")
   100  	compressionThreshold  = flag.Int("compression_threshold", -1, "Threshold size in bytes for compressing Bytestream reads or writes. Use a negative value for turning off compression.")
   101  	useBatches            = flag.Bool("use_batches", true, "Use batch operations for relatively small blobs.")
   102  	logKeepDuration       = flag.Duration("log_keep_duration", 24*time.Hour, "Delete all RE logs older than the specified duration on startup.")
   103  	idleTimeout           = flag.Duration("proxy_idle_timeout", 6*time.Hour, "Inactivity period after which the running reproxy process will be killed. Default is 6 hours. When set to 0, idle timeout is disabled.")
   104  	depsCacheMaxMb        = flag.Int("deps_cache_max_mb", 128, "Maximum size of the deps cache file (for goma input processor only).")
   105  	// TODO(b/233275188): remove this flag.
   106  	_                                 = flag.Duration("ip_reset_min_delay", 3*time.Minute, "Deprecated. The minimum time after the input processor has been reset before it can be reset again. Negative values disable resetting.")
   107  	ipTimeout                         = flag.Duration("ip_timeout", 10*time.Minute, "The maximum time to wait for an input processor action. Zero and negative values disable timeout.")
   108  	metricsProject                    = flag.String("metrics_project", "", "If set, action and build metrics are exported to Cloud Monitoring in the specified GCP project")
   109  	metricsPrefix                     = flag.String("metrics_prefix", "", "Prefix of metrics exported to Cloud Monitoring")
   110  	metricsNamespace                  = flag.String("metrics_namespace", "", "Namespace of metrics exported to Cloud Monitoring (e.g. RBE project)")
   111  	experimentalCredentialsHelper     = flag.String(auth.CredshelperPathFlag, "", "Path to the credentials helper binary. If given execrel://, looks for the `credshelper` binary in the same folder as reproxy")
   112  	experimentalCredentialsHelperArgs = flag.String(auth.CredshelperArgsFlag, "", "Arguments for the experimental credentials helper, separated by space.")
   113  	failEarlyMinActionCount   = flag.Int64("fail_early_min_action_count", 0, "Minimum number of actions received by reproxy before the fail early mechanism can take effect. 0 indicates fail early is disabled.")
   114  	failEarlyMinFallbackRatio = flag.Float64("fail_early_min_fallback_ratio", 0, "Minimum ratio of fallbacks to total actions above which the build terminates early. Ratio is a number in the range [0,1]. 0 indicates fail early is disabled.")
   115  	failEarlyWindow           = flag.Duration("fail_early_window", 0, "Window of time to consider for fail_early_min_action_count and fail_early_min_fallback_ratio. 0 indicates all datapoints should be used.")
   116  	racingBias                = flag.Float64("racing_bias", 0.75, "Value between [0,1] to indicate how racing manages the tradeoff of saving bandwidth (0) versus speed (1). The default is to prefer speed over bandwidth.")
   117  	racingTmp                 = flag.String("racing_tmp_dir", "", "DEPRECATED. Use download_tmp_dir instead.")
   118  	downloadTmp               = flag.String("download_tmp_dir", "", "Directory where reproxy should store outputs temporarily before moving them to the desired location. This should be on the same device as the output directory for the build. The default is outputs will be written to a subdirectory inside the action's working directory. Note that the download_tmp_dir will only be used if the action has racing as its exec strategy or it explicitly sets EnableAtomicDownloads=true. See proxy.proto for details.")
   119  
   120  	debugPort   = flag.Int("pprof_port", 0, "Enable pprof http server if not zero")
   121  	cpuProfFile = flag.String("pprof_file", "", "Enable cpu pprof if not empty. Will not work on windows as reproxy shutdowns through an uncatchable sigkill.")
   122  	memProfFile = flag.String("pprof_mem_file", "", "Enable memory pprof if not empty. Will not work on windows as reproxy shutdowns through an uncatchable sigkill.")
   123  
   124  	profilerService   = flag.String("profiler_service", "", "Service name to associate with profiles uploaded to Cloud Profiling. If unset, Cloud Profiling is disabled.")
   125  	profilerProjectID = flag.String("profiler_project_id", "", "project id used for cloud profiler")
   126  
   127  	cppLinkDeepScan = flag.Bool("clang_depscan_archive", false, "Deep scan .a files for dependencies during clang linking")
   128  
   129  	depsScannerAddress = flag.String("depsscanner_address", "execrel://", "If set, connects to the given address for C++ dependency scanning; a path with the prefix 'exec://' will start the target executable and connect to it. Defaults to execrel:// which looks for the `scandeps_server` binary in the same folder as reproxy. When set to \"\", the internal dependency scanner will be used.")
   130  
   131  	credsFile          = flag.String("creds_file", "", "Path to file where short-lived credentials are stored. If the file includes a token, reproxy will update the token if it refreshes it. Token refresh is only applicable if use_external_auth_token is used.")
   132  	waitForShutdownRPC = flag.Bool("wait_for_shutdown_rpc", false, "If set, will only shutdown after 3 SIGINT signals")
   133  	useCasNg           = flag.Bool("use_casng", false, "Use casng pkg.")
   134  	logHTTPCalls       = flag.Bool("log_http_calls", false, "Log all http requests made with the default http client.")
   135  )
   136  
   137  func verifyFlags() {
   138  	if *localResourceFraction < 0 || *localResourceFraction > 1 {
   139  		log.Exitf("Invalid local_resource_fraction: %v, want [0,1]", *localResourceFraction)
   140  	}
   141  	if *failEarlyMinActionCount < 0 {
   142  		log.Exitf("Invalid fail_early_min_action_acount: %v, want [0,MaxInt64]", *failEarlyMinActionCount)
   143  	}
   144  	if *failEarlyMinFallbackRatio < 0 {
   145  		log.Exitf("Invalid fail_early_min_fallback_ratio: %v, want [0,1]", *failEarlyMinFallbackRatio)
   146  	}
   147  	if *failEarlyWindow < 0 {
   148  		log.Exitf("Invalid fail_early_window: %v, want >0", *failEarlyWindow)
   149  	}
   150  	if *racingBias < 0 || *racingBias > 1 {
   151  		log.Exitf("Invalid racing_bias: %v, want [0,1]", *racingBias)
   152  	}
   153  	if *failEarlyMinActionCount == 0 && *failEarlyMinFallbackRatio > 0 {
   154  		log.Exitf("fail_early_min_fallback_ratio is set to %v while fail_early_min_action_count is disabled", *failEarlyMinFallbackRatio)
   155  	}
   156  	if *failEarlyMinActionCount > 0 && *failEarlyMinFallbackRatio == 0 {
   157  		log.Exitf("fail_early_min_action_count is set to %v while fail_early_min_fallback_ratio is disabled", *failEarlyMinActionCount)
   158  	}
   159  	os.Setenv("RBE_clang_depscan_ignored_plugins", *clangDepScanIgnoredPlugins)
   160  }
   161  
   162  func main() {
   163  	flag.Var((*moreflag.StringListValue)(&proxyLogDir), "proxy_log_dir", "If provided, the directory path to a proxy log file of executed records.")
   164  	flag.StringVar(&filemetadata.XattrDigestName, "xattr_digest", "", "Extended file attribute to obtain the digest from, if available, formatted as hash/size. If the value contains the hash only, the file size as reported by stat is used.")
   165  	flag.Var((*moreflag.StringMapValue)(&labels), "metrics_labels", "Comma-separated key value pairs in the form key=value. This is used to add arbitrary labels to exported metrics.")
   166  	rbeflag.Parse()
   167  	rbeflag.LogAllFlags(0)
   168  	defer log.Flush()
   169  	if *logHTTPCalls {
   170  		loghttp.Register()
   171  	}
   172  	defer func() {
   173  		pf, err := reproxypid.ReadFile(*serverAddr)
   174  		if err != nil {
   175  			log.Warningf("Unable to find pid file for deletion: %v", err)
   176  			return
   177  		}
   178  		pf.Delete()
   179  	}()
   180  	if *depsScannerAddress != "" {
   181  		if *depsScannerAddress == "execrel://" {
   182  			scandepsServerPath, err := pathtranslator.BinaryRelToAbs("scandeps_server")
   183  			if err != nil {
   184  				log.Fatalf("Specified --depsscanner_address=execrel:// but `scandeps_server` was not found in the same directory as `reproxy`: %v", err)
   185  			}
   186  			*depsScannerAddress = "exec://" + scandepsServerPath
   187  		}
   188  		// If the depsscanner crashes or times out, all actions in flight will be counted as
   189  		// timeouts.  Therefore we bump the number allowed to account for multiple fallbacks from
   190  		// one failure.
   191  		// There will be at most NumCPU actions at any given time; this gives us approximately
   192  		// two failures before aborting the build on the third.
   193  		reproxy.AllowedIPTimeouts += int64(runtime.NumCPU() * 2)
   194  	} else {
   195  		log.Fatalf("--depsscanner_address must be specified")
   196  	}
   197  	log.Flush()
   198  	version.PrintAndExitOnVersionFlag(true)
   199  	verifyFlags()
   200  
   201  	if *profilerService != "" {
   202  		log.Infof("Enable cloud profiler: service=%s project=%s", *profilerService, *profilerProjectID)
   203  		err := profiler.Start(profiler.Config{
   204  			Service:        *profilerService,
   205  			ServiceVersion: version.CurrentVersion(),
   206  			MutexProfiling: true,
   207  			ProjectID:      *profilerProjectID,
   208  		})
   209  		if err != nil {
   210  			log.Errorf("Failed to start cloud profiler: %v", err)
   211  		}
   212  	}
   213  
   214  	if *debugPort > 0 {
   215  		go func() {
   216  			addr := fmt.Sprintf("127.0.0.1:%d", *debugPort)
   217  			log.Infof("start http server for pprof at %s", addr)
   218  			log.Exit(http.ListenAndServe(addr, nil))
   219  		}()
   220  	} else {
   221  		if *cpuProfFile != "" {
   222  			f, err := os.Create(*cpuProfFile)
   223  			if err != nil {
   224  				log.Fatal("Could not create CPU profile: ", err)
   225  			}
   226  			defer f.Close()
   227  			if err := pprof.StartCPUProfile(f); err != nil {
   228  				log.Fatal("Could not start CPU profile: ", err)
   229  			}
   230  		}
   231  		if *memProfFile != "" {
   232  			f, err := os.Create(*memProfFile)
   233  			if err != nil {
   234  				log.Fatal("Could not create memory profile: ", err)
   235  			}
   236  			defer f.Close()
   237  			if err := pprof.WriteHeapProfile(f); err != nil {
   238  				log.Fatal("Could not start memory profile: ", err)
   239  			}
   240  		}
   241  	}
   242  
   243  	listener, err := ipc.Listen(*serverAddr)
   244  	if err != nil {
   245  		log.Exitf("Failed to listen: %v", err)
   246  	}
   247  
   248  	logDir := getLogDir()
   249  	var opts []grpc.ServerOption
   250  	truncateInterceptor := interceptors.NewTruncInterceptor(ipc.GrpcMaxMsgSize, logDir)
   251  	opts = append(
   252  		opts,
   253  		grpc.MaxRecvMsgSize(ipc.GrpcMaxMsgSize),
   254  		grpc.ChainUnaryInterceptor(interceptors.UnaryServerInterceptor, truncateInterceptor),
   255  		grpc.StreamInterceptor(interceptors.StreamServerInterceptor),
   256  	)
   257  	grpcServer := grpc.NewServer(opts...)
   258  
   259  	ctx := context.Background()
   260  	var c *auth.Credentials
   261  	if !*remoteDisabled {
   262  		c = mustBuildCredentials()
   263  		defer c.SaveToDisk()
   264  	}
   265  	var e *monitoring.Exporter
   266  	var exportActionMetrics logger.ExportActionMetricsFunc
   267  	if *metricsProject != "" {
   268  		e, err = newExporter(c)
   269  		if err != nil {
   270  			log.Warningf("Failed to initialize cloud monitoring: %v", err)
   271  		} else {
   272  			exportActionMetrics = e.ExportActionMetrics
   273  			defer e.Close()
   274  		}
   275  	}
   276  	mi, err := ignoremismatch.New(*mismatchIgnoreConfigPath)
   277  	if err != nil {
   278  		log.Errorf("Failed to create mismatch ignorer: %v", err)
   279  	}
   280  	l, err := initializeLogger(mi, exportActionMetrics)
   281  	if err != nil {
   282  		log.Exitf("%v", err)
   283  	}
   284  
   285  	st := filemetadata.NewSingleFlightCache()
   286  
   287  	exec := &subprocess.SystemExecutor{}
   288  	resMgr := localresources.NewFractionalDefaultManager(*localResourceFraction)
   289  
   290  	dTmp := *racingTmp
   291  	if *downloadTmp != "" {
   292  		dTmp = *downloadTmp
   293  	}
   294  
   295  	initCtx, cancelInit := context.WithCancel(ctx)
   296  	server := &reproxy.Server{
   297  		FileMetadataStore:         st,
   298  		LocalPool:                 reproxy.NewLocalPool(exec, resMgr),
   299  		KeepLastRecords:           *keepRecords,
   300  		CacheSilo:                 *cacheSilo,
   301  		VersionCacheSilo:          *versionCacheSilo,
   302  		RemoteDisabled:            *remoteDisabled,
   303  		DumpInputTree:             *dumpInputTree,
   304  		Forecast:                  &reproxy.Forecast{},
   305  		StartTime:                 start,
   306  		FailEarlyMinActionCount:   *failEarlyMinActionCount,
   307  		FailEarlyMinFallbackRatio: *failEarlyMinFallbackRatio,
   308  		FailEarlyWindow:           *failEarlyWindow,
   309  		RacingBias:                *racingBias,
   310  		DownloadTmp:               dTmp,
   311  		MaxHoldoff:                time.Minute,
   312  		Logger:                    l,
   313  		StartupCancelFn:           cancelInit,
   314  	}
   315  	server.Init()
   316  
   317  	ipOpts := &inputprocessor.Options{
   318  		CacheDir:           *cacheDir,
   319  		EnableDepsCache:    *enableDepsCache,
   320  		LogDir:             logDir,
   321  		DepsCacheMaxMb:     *depsCacheMaxMb,
   322  		CppLinkDeepScan:    *cppLinkDeepScan,
   323  		IPTimeout:          *ipTimeout,
   324  		DepsScannerAddress: *depsScannerAddress,
   325  		ProxyServerAddress: *serverAddr,
   326  	}
   327  	go func() {
   328  		log.Infof("Setting up input processor")
   329  		ip, cleanup, err := inputprocessor.NewInputProcessor(initCtx, exec, resMgr, st, l, ipOpts)
   330  		if err != nil {
   331  			log.Errorf("Failed to initialize input processor: %+v", err)
   332  			server.SetStartupErr(status.Error(codes.Internal, err.Error()))
   333  			cancelInit()
   334  		} else {
   335  			log.Infof("Finished setting up input processor")
   336  			server.SetInputProcessor(ip, cleanup)
   337  		}
   338  	}()
   339  
   340  	if *remoteDisabled {
   341  		server.SetREClient(&rexec.Client{st, nil}, func() {})
   342  	} else {
   343  		// Backward compatibility until useUnifiedCASOps is deprecated:
   344  		if *useUnifiedCASOps {
   345  			*useUnifiedUploads = true
   346  			*useUnifiedDownloads = true
   347  		}
   348  		clientOpts := []client.Opt{
   349  			client.UnifiedUploads(*useUnifiedUploads),
   350  			client.UnifiedUploadBufferSize(*uploadBufferSize),
   351  			client.UnifiedUploadTickDuration(*uploadTickDuration),
   352  			client.UnifiedDownloads(*useUnifiedDownloads),
   353  			client.UnifiedDownloadBufferSize(*downloadBufferSize),
   354  			client.UnifiedDownloadTickDuration(*downloadTickDuration),
   355  			client.UseBatchOps(*useBatches),
   356  			client.CompressedBytestreamThreshold(*compressionThreshold),
   357  			client.UseCASNG(*useCasNg),
   358  		}
   359  		if ts := c.TokenSource(); ts != nil {
   360  			clientOpts = append(clientOpts, &client.PerRPCCreds{Creds: ts})
   361  		}
   362  		go func() {
   363  			log.Infof("Creating a new SDK client")
   364  			grpcClient, err := rflags.NewClientFromFlags(initCtx, clientOpts...)
   365  			if err != nil {
   366  				log.Errorf("Failed to initialize SDK client: %+v", err)
   367  				if ce, ok := err.(*client.InitError); ok {
   368  					err = formatAuthError(c.Mechanism(), ce)
   369  				}
   370  				server.SetStartupErr(err)
   371  				cancelInit()
   372  			} else {
   373  				log.Infof("Finished setting up SDK client")
   374  				server.SetREClient(&rexec.Client{st, grpcClient}, func() { grpcClient.Close() })
   375  			}
   376  		}()
   377  	}
   378  	go server.Forecast.Run(ctx)
   379  	go server.MonitorFailBuildConditions(ctx)
   380  	go reproxy.IdleTimeout(ctx, *idleTimeout)
   381  	// Log all reproxy flags.
   382  	if server.Logger != nil {
   383  		server.Logger.AddFlags(flag.CommandLine)
   384  	} else {
   385  		log.Warningf("nil logger pointer")
   386  	}
   387  	// Delete old logs in the background.
   388  	go reproxy.DeleteOldLogFiles(*logKeepDuration, logDir)
   389  	pb.RegisterCommandsServer(grpcServer, server)
   390  	pb.RegisterStatsServer(grpcServer, server)
   391  	pb.RegisterStatusServer(grpcServer, l)
   392  	log.Infof("RE proxy server listening on %s://%s", listener.Addr().Network(), listener.Addr().String())
   393  	log.Flush()
   394  	sigs := make(chan os.Signal, 1)
   395  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   396  	wg := sync.WaitGroup{}
   397  	wg.Add(2)
   398  	go func() {
   399  		sigCnt := 1
   400  		if *waitForShutdownRPC {
   401  			sigCnt = 3
   402  		}
   403  		for sigCnt > 0 {
   404  			select {
   405  			case sig := <-sigs:
   406  				sigCnt--
   407  				go server.DrainAndReleaseResources() // Start draining server immediately while waiting for Shutdown rpc
   408  				if sigCnt > 0 {
   409  					log.Infof("RE proxy server received %v signal, waiting for Shutdown rpc or %d more signals...", sig, sigCnt)
   410  				} else {
   411  					log.Infof("RE proxy server received %v signal, shutting down...", sig)
   412  				}
   413  			case <-server.WaitForShutdownCommand():
   414  				sigCnt = 0
   415  				log.Infof("RE proxy server received a Shutdown RPC call, shutting down...")
   416  			}
   417  		}
   418  		if *cpuProfFile != "" {
   419  			pprof.StopCPUProfile()
   420  		}
   421  		grpcServer.GracefulStop()
   422  		<-server.WaitForCleanupDone()
   423  		log.Infof("Finished shutting down and wrote log records...")
   424  		log.Flush()
   425  		wg.Done()
   426  	}()
   427  	go func() {
   428  		grpcServer.Serve(listener)
   429  		wg.Done()
   430  	}()
   431  	wg.Wait()
   432  }
   433  
   434  func formatAuthError(m auth.Mechanism, ce *client.InitError) error {
   435  	if errors.Is(ce.Err, context.Canceled) {
   436  		return ce.Err
   437  	}
   438  	errMsg := "Unable to authenticate with RBE"
   439  	switch ce.AuthUsed {
   440  	case client.ExternalTokenAuth:
   441  		errMsg += ", externally provided auth token was invalid"
   442  	case client.ApplicationDefaultCredsAuth:
   443  		errMsg += ", try restarting the build after running the following command:\n"
   444  		errMsg += "    gcloud auth application-default login --disable-quota-project\n"
   445  		errMsg += "If this is a headless machine, use:\n"
   446  		errMsg += "    gcloud auth application-default login --no-launch-browser --disable-quota-project"
   447  	}
   448  	return status.Errorf(codes.Unauthenticated, errMsg+"\n%s", ce.Error())
   449  }
   450  
   451  // mustBuildCredentials either returns a valid auth.Credentials struct or exits
   452  func mustBuildCredentials() *auth.Credentials {
   453  	if *experimentalCredentialsHelper != "" {
   454  		creds, err := auth.NewExternalCredentials(*experimentalCredentialsHelper, strings.Fields(*experimentalCredentialsHelperArgs), *credsFile)
   455  		if err != nil {
   456  			fmt.Fprintf(os.Stderr, "Experimental credentials helper failed. Please try again or use application default credentials:%v", err)
   457  			os.Exit(auth.ExitCodeExternalTokenAuth)
   458  		}
   459  		return creds
   460  	}
   461  	m, err := auth.MechanismFromFlags()
   462  	if err != nil || m == auth.Unknown {
   463  		log.Errorf("Failed to determine auth mechanism: %v", err)
   464  		os.Exit(auth.ExitCodeNoAuth)
   465  	}
   466  	c, err := auth.NewCredentials(m, *credsFile, 0)
   467  	if err != nil {
   468  		log.Errorf("Failed to initialize credentials: %v", err)
   469  		if aerr, ok := err.(*auth.Error); ok {
   470  			os.Exit(aerr.ExitCode)
   471  		}
   472  		os.Exit(auth.ExitCodeUnknown)
   473  	}
   474  	return c
   475  }
   476  
   477  func initializeLogger(mi *ignoremismatch.MismatchIgnorer, e logger.ExportActionMetricsFunc) (*logger.Logger, error) {
   478  	u := usage.New()
   479  	if len(proxyLogDir) > 0 {
   480  		format, err := logger.ParseFormat(*logFormat)
   481  		if err != nil {
   482  			return nil, fmt.Errorf("error initializing logger: %v", err)
   483  		}
   484  		l, err := logger.New(format, proxyLogDir[0], stats.New(), mi, e, u)
   485  		if err != nil {
   486  			return nil, fmt.Errorf("error initializing logger: %v", err)
   487  		}
   488  		return l, nil
   489  	}
   490  
   491  	if *logPath != "" {
   492  		l, err := logger.NewFromFormatFile(*logPath, stats.New(), mi, e, u)
   493  		if err != nil {
   494  			return nil, fmt.Errorf("error initializing log file %v: %v", *logPath, err)
   495  		}
   496  		return l, nil
   497  	}
   498  	return nil, nil
   499  }
   500  
   501  func newExporter(creds *auth.Credentials) (*monitoring.Exporter, error) {
   502  	if err := monitoring.SetupViews(labels); err != nil {
   503  		return nil, err
   504  	}
   505  	return monitoring.NewExporter(context.Background(), *metricsProject, *metricsPrefix, *metricsNamespace, creds.TokenSource())
   506  }
   507  
   508  func getLogDir() string {
   509  	if len(proxyLogDir) > 0 {
   510  		return proxyLogDir[0]
   511  	}
   512  
   513  	if f := flag.Lookup("log_dir"); f != nil && f.Value.String() != "" {
   514  		return f.Value.String()
   515  	}
   516  	return os.TempDir()
   517  }