github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/follow/grpc_client.go (about)

     1  // +build !bootstrap
     2  
     3  package follow
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"time"
     9  
    10  	"golang.org/x/net/context"
    11  	"google.golang.org/grpc"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  
    15  	"core"
    16  	pb "follow/proto/build_event"
    17  	"output"
    18  )
    19  
    20  // Used to track the state of the remote connection.
    21  var remoteClosed, remoteDisconnected bool
    22  
    23  // ConnectClient connects a gRPC client to the given URL.
    24  // It returns once the client has received that the remote build finished,
    25  // returning true if that build was successful.
    26  // It dies on any errors.
    27  func ConnectClient(state *core.BuildState, url string, retries int, delay time.Duration) bool {
    28  	connectClient(state, url, retries, delay)
    29  	// Now run output, this will exit when the goroutine in connectClient() hits its end.
    30  	return runOutput(state)
    31  }
    32  
    33  // connectClient connects a gRPC client to the given URL.
    34  // It is split out of the above for testing purposes.
    35  func connectClient(state *core.BuildState, url string, retries int, delay time.Duration) {
    36  	var err error
    37  	for i := 0; i <= retries; i++ {
    38  		if err = connectSingleTry(state, url); err == nil {
    39  			return
    40  		} else if retries > 0 && i < retries {
    41  			log.Warning("Failed to connect to remote server, will retry in %s: %s", delay, err)
    42  			time.Sleep(delay)
    43  		}
    44  	}
    45  	log.Fatalf("%s", err)
    46  }
    47  
    48  // connectSingleTry performs one try at connecting the gRPC client.
    49  func connectSingleTry(state *core.BuildState, url string) error {
    50  	// TODO(peterebden): TLS
    51  	conn, err := grpc.Dial(url, grpc.WithInsecure())
    52  	if err != nil {
    53  		return err
    54  	}
    55  	client := pb.NewPlzEventsClient(conn)
    56  	// Get the deets of what the server is doing.
    57  	resp, err := client.ServerConfig(context.Background(), &pb.ServerConfigRequest{})
    58  	if err != nil {
    59  		if s, ok := status.FromError(err); ok && s.Code() == codes.Unavailable {
    60  			// Slightly nicer version for an obvious failure which gets a bit technical by default
    61  			return fmt.Errorf("Failed to set up communication with remote server; check the address is correct and it's running")
    62  		}
    63  		return fmt.Errorf("Failed to set up communication with remote server: %s", err)
    64  	}
    65  	// Let the user know we're connected now and what it's up to.
    66  	output.PrintConnectionMessage(url, fromProtoBuildLabels(resp.OriginalTargets), resp.Tests, resp.Coverage)
    67  	// Update the config appropriately; the output code reads some of its fields.
    68  	state.Config.Please.NumThreads = int(resp.NumThreads)
    69  	state.NeedBuild = false // We're not actually building ourselves
    70  	state.NeedTests = resp.Tests
    71  	state.NeedCoverage = resp.Coverage
    72  	state.StartTime = time.Unix(0, resp.StartTime)
    73  	state.Config.Display.SystemStats = true
    74  	// Catch up on the last result of each thread
    75  	log.Info("Got %d initial build events, dispatching...", len(resp.LastEvents))
    76  	for _, r := range resp.LastEvents {
    77  		streamEvent(state, r)
    78  	}
    79  
    80  	// Now start streaming events into it
    81  	stream, err := client.BuildEvents(context.Background(), &pb.BuildEventRequest{})
    82  	if err != nil {
    83  		return fmt.Errorf("Error receiving build events: %s", err)
    84  	}
    85  	go func() {
    86  		for {
    87  			event, err := stream.Recv()
    88  			if err == io.EOF {
    89  				remoteClosed = true
    90  				break
    91  			} else if err != nil {
    92  				remoteDisconnected = true
    93  				log.Error("Error receiving build events: %s", err)
    94  				break
    95  			}
    96  			streamEvent(state, event)
    97  		}
    98  		log.Info("Reached end of server event stream, shutting down internal queue")
    99  		close(state.Results)
   100  	}()
   101  	log.Info("Established connection to remote server for build events")
   102  	// Stream back resource usage as well
   103  	go streamResources(state, client)
   104  	return nil
   105  }
   106  
   107  // streamEvent adds an event to our internal stream.
   108  func streamEvent(state *core.BuildState, event *pb.BuildEventResponse) {
   109  	e := fromProto(event)
   110  	// Put a version of this target into our graph, it will help a lot of things later.
   111  	if t := state.Graph.Target(e.Label); t == nil {
   112  		t = state.Graph.AddTarget(core.NewBuildTarget(e.Label))
   113  		t.Labels = event.Labels
   114  	}
   115  	state.Results <- e
   116  	state.SetTaskNumbers(event.NumActive, event.NumDone)
   117  }
   118  
   119  // streamResources receives system resource usage information from the server and copies
   120  // them into the build state.
   121  func streamResources(state *core.BuildState, client pb.PlzEventsClient) {
   122  	stream, err := client.ResourceUsage(context.Background(), &pb.ResourceUsageRequest{})
   123  	if err != nil {
   124  		log.Error("Error receiving resource usage: %s", err)
   125  		return
   126  	}
   127  	for {
   128  		resources, err := stream.Recv()
   129  		if err == io.EOF {
   130  			break
   131  		} else if err != nil {
   132  			log.Error("Error receiving resource usage: %s", err)
   133  			break
   134  		}
   135  		state.Stats = resourceFromProto(resources)
   136  	}
   137  }
   138  
   139  // runOutput is just a wrapper around output.MonitorState for convenience in testing.
   140  func runOutput(state *core.BuildState) bool {
   141  	success := output.MonitorState(state, state.Config.Please.NumThreads, state.Verbosity >= 4, false, false, state.NeedTests, false, false, "")
   142  	output.PrintDisconnectionMessage(success, remoteClosed, remoteDisconnected)
   143  	return success
   144  }