kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/analysis/driver/driver.go (about)

     1  /*
     2   * Copyright 2015 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Package driver contains a Driver implementation that sends analyses to a
    18  // CompilationAnalyzer based on a Queue of compilations.
    19  package driver // import "kythe.io/kythe/go/platform/analysis/driver"
    20  
    21  import (
    22  	"context"
    23  	goerrors "errors"
    24  	"time"
    25  
    26  	"kythe.io/kythe/go/platform/analysis"
    27  	"kythe.io/kythe/go/util/log"
    28  
    29  	"github.com/pkg/errors"
    30  
    31  	apb "kythe.io/kythe/proto/analysis_go_proto"
    32  )
    33  
    34  // A Compilation represents a compilation and other metadata needed to analyze it.
    35  type Compilation struct {
    36  	Unit       *apb.CompilationUnit // the compilation to analyze
    37  	Revision   string               // revision marker to attribute to the compilation
    38  	UnitDigest string               // unit digest identifying the compilation in a KCD
    39  	BuildID    string               // id of the build executing the compilation
    40  }
    41  
    42  // CompilationFunc handles a single CompilationUnit.
    43  type CompilationFunc func(context.Context, Compilation) error
    44  
    45  // A Queue represents an ordered sequence of compilation units.
    46  type Queue interface {
    47  	// Next invokes f with the next available compilation in the queue.  If no
    48  	// further values are available, Next must return ErrEndOfQueue; otherwise,
    49  	// the return value from f is propagated to the caller of Next.
    50  	Next(_ context.Context, f CompilationFunc) error
    51  }
    52  
    53  // A Context packages callbacks invoked during analysis.
    54  type Context interface {
    55  	// Setup is invoked after a compilation has been fetched from a Queue but
    56  	// before it is sent to the analyzer.  If Setup reports an error, analysis
    57  	// is aborted.
    58  	Setup(context.Context, Compilation) error
    59  
    60  	// Teardown is invoked after a analysis has completed for the compilation.
    61  	// If Teardown reports an error after analysis succeeds, it is logged but
    62  	// does not cause the analysis to fail.
    63  	Teardown(context.Context, Compilation) error
    64  
    65  	// AnalysisError is invoked for each non-nil error reported by the analyzer
    66  	// prior to calling Teardown. The error returned from AnalysisError replaces
    67  	// the error returned by the analyzer itself.
    68  	//
    69  	// If AnalysisError returns the special value ErrRetry, the analysis is
    70  	// retried immediately.
    71  	AnalysisError(context.Context, Compilation, error) error
    72  }
    73  
    74  var (
    75  	// ErrRetry can be returned from a Driver's AnalysisError function to signal
    76  	// that the driver should retry the analysis immediately.
    77  	ErrRetry = goerrors.New("retry analysis")
    78  
    79  	// ErrEndOfQueue can be returned from a Queue to signal there are no
    80  	// compilations left to analyze.
    81  	ErrEndOfQueue = goerrors.New("end of queue")
    82  )
    83  
    84  // AnalysisOptions contains extra configuration for analysis requests.
    85  type AnalysisOptions struct {
    86  	// Timeout, if nonzero, sets the given timeout for each analysis request.
    87  	Timeout time.Duration
    88  }
    89  
    90  // Driver sends compilations sequentially from a queue to an analyzer.
    91  type Driver struct {
    92  	Analyzer        analysis.CompilationAnalyzer
    93  	AnalysisOptions AnalysisOptions
    94  
    95  	FileDataService string
    96  	Context         Context             // if nil, callbacks are no-ops
    97  	WriteOutput     analysis.OutputFunc // if nil, output is discarded
    98  }
    99  
   100  func (d *Driver) writeOutput(ctx context.Context, out *apb.AnalysisOutput) error {
   101  	if write := d.WriteOutput; write != nil {
   102  		return write(ctx, out)
   103  	}
   104  	return nil
   105  }
   106  
   107  func (d *Driver) setup(ctx context.Context, unit Compilation) error {
   108  	if c := d.Context; c != nil {
   109  		return c.Setup(ctx, unit)
   110  	}
   111  	return nil
   112  }
   113  
   114  func (d *Driver) teardown(ctx context.Context, unit Compilation) error {
   115  	if c := d.Context; c != nil {
   116  		return c.Teardown(ctx, unit)
   117  	}
   118  	return nil
   119  }
   120  
   121  func (d *Driver) analysisError(ctx context.Context, unit Compilation, err error) error {
   122  	if c := d.Context; c != nil && err != nil {
   123  		return c.AnalysisError(ctx, unit, err)
   124  	}
   125  	return err
   126  }
   127  
   128  // Run sends each compilation received from the driver's Queue to the driver's
   129  // Analyzer.  All outputs are passed to Output in turn.  An error is immediately
   130  // returned if the Analyzer, Output, or Compilations fields are unset.
   131  func (d *Driver) Run(ctx context.Context, queue Queue) error {
   132  	if d.Analyzer == nil {
   133  		return errors.New("no analyzer has been specified")
   134  	}
   135  
   136  	for {
   137  		if err := queue.Next(ctx, func(ctx context.Context, cu Compilation) error {
   138  			if err := d.setup(ctx, cu); err != nil {
   139  				return errors.WithMessage(err, "driver: analysis setup")
   140  			}
   141  			err := ErrRetry
   142  			for err == ErrRetry {
   143  				err = d.analysisError(ctx, cu, d.runAnalysis(ctx, cu))
   144  			}
   145  			if terr := d.teardown(ctx, cu); terr != nil {
   146  				if err == nil {
   147  					return errors.WithMessage(terr, "driver: analysis teardown")
   148  				}
   149  				log.WarningContextf(ctx, "analysis teardown failed: %v (analysis error: %v)", terr, err)
   150  			}
   151  			return err
   152  		}); err == ErrEndOfQueue {
   153  			return nil
   154  		} else if err != nil {
   155  			return err
   156  		}
   157  	}
   158  }
   159  
   160  func (d *Driver) runAnalysis(ctx context.Context, cu Compilation) error {
   161  	if d.AnalysisOptions.Timeout != 0 {
   162  		var cancel func()
   163  		ctx, cancel = context.WithTimeout(ctx, d.AnalysisOptions.Timeout)
   164  		defer cancel()
   165  	}
   166  	_, err := d.Analyzer.Analyze(ctx, &apb.AnalysisRequest{
   167  		Compilation:     cu.Unit,
   168  		FileDataService: d.FileDataService,
   169  		Revision:        cu.Revision,
   170  		BuildId:         cu.BuildID,
   171  	}, d.writeOutput)
   172  	return err
   173  }