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

     1  // +build !bootstrap
     2  
     3  // Package follow implements remote connections to other plz processes.
     4  // Specifically it implements a gRPC server and client that can stream
     5  // build events.
     6  package follow
     7  
     8  import (
     9  	"fmt"
    10  	"net"
    11  	"time"
    12  
    13  	"golang.org/x/net/context"
    14  	"google.golang.org/grpc"
    15  	"google.golang.org/grpc/peer"
    16  	"gopkg.in/op/go-logging.v1"
    17  
    18  	"core"
    19  	pb "follow/proto/build_event"
    20  )
    21  
    22  var log = logging.MustGetLogger("remote")
    23  
    24  // disconnectTimeout is the grace period we give clients to disconnect before they are ditched.
    25  var disconnectTimeout = 1 * time.Second
    26  
    27  // buffering is the size of buffer we allocate in the server channels.
    28  // Larger values consume more memory but protect better against slow clients.
    29  const buffering = 1000
    30  
    31  // InitialiseServer sets up the gRPC server on the given port.
    32  // It dies on any errors.
    33  // The returned function should be called to shut down once the server is no longer required.
    34  func InitialiseServer(state *core.BuildState, port int) func() {
    35  	_, f := initialiseServer(state, port)
    36  	return f
    37  }
    38  
    39  // initialiseServer sets up the gRPC server on the given port.
    40  // It's split out from the above for testing purposes.
    41  func initialiseServer(state *core.BuildState, port int) (string, func()) {
    42  	// Set up the channel that we'll get messages off
    43  	state.RemoteResults = make(chan *core.BuildResult, buffering)
    44  	// TODO(peterebden): TLS support
    45  	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    46  	if err != nil {
    47  		log.Fatalf("%s", err)
    48  	}
    49  	addr := lis.Addr().String()
    50  	s := grpc.NewServer()
    51  	server := &eventServer{State: state}
    52  	go server.MultiplexEvents(state.RemoteResults)
    53  	pb.RegisterPlzEventsServer(s, server)
    54  	go s.Serve(lis)
    55  	log.Notice("Serving events over gRPC on :%s", addr)
    56  	return addr, func() {
    57  		close(state.RemoteResults)
    58  		stopServer(s)
    59  	}
    60  }
    61  
    62  // An eventServer handles the RPC requests to connected clients.
    63  type eventServer struct {
    64  	State   *core.BuildState
    65  	Clients []chan *pb.BuildEventResponse
    66  }
    67  
    68  // ServerConfig implements the RPC interface.
    69  func (e *eventServer) ServerConfig(ctx context.Context, r *pb.ServerConfigRequest) (*pb.ServerConfigResponse, error) {
    70  	targets := make([]*pb.BuildLabel, len(e.State.OriginalTargets))
    71  	for i, t := range e.State.OriginalTargets {
    72  		targets[i] = toProtoBuildLabel(t)
    73  	}
    74  	return &pb.ServerConfigResponse{
    75  		NumThreads:      int32(e.State.Config.Please.NumThreads),
    76  		OriginalTargets: targets,
    77  		Tests:           e.State.NeedTests,
    78  		Coverage:        e.State.NeedCoverage,
    79  		LastEvents:      toProtos(e.State.LastResults, e.State.NumActive(), e.State.NumDone()),
    80  		StartTime:       e.State.StartTime.UnixNano(),
    81  	}, nil
    82  }
    83  
    84  // BuildEvents implements the RPC interface.
    85  func (e *eventServer) BuildEvents(r *pb.BuildEventRequest, s pb.PlzEvents_BuildEventsServer) error {
    86  	if p, ok := peer.FromContext(s.Context()); ok {
    87  		log.Notice("Remote client connected from %s to receive events", p.Addr)
    88  	}
    89  	c := make(chan *pb.BuildEventResponse, buffering)
    90  	e.Clients = append(e.Clients, c)
    91  	// Client is now connected to the stream and will receive all events from here on.
    92  	for event := range c {
    93  		if err := s.Send(event); err != nil {
    94  			// Something's stuffed, disconnect the client from our event streams
    95  			log.Notice("Remote client disconnected (%s)", err)
    96  			for i, client := range e.Clients {
    97  				if client == c {
    98  					copy(e.Clients[i:], e.Clients[i+1:])
    99  					last := len(e.Clients) - 1
   100  					e.Clients[last] = nil
   101  					e.Clients = e.Clients[:last]
   102  				}
   103  			}
   104  			return err
   105  		}
   106  	}
   107  	log.Notice("Events finished, terminating remote session")
   108  	return nil
   109  }
   110  
   111  // ResourceUsage implements the RPC interface.
   112  func (e *eventServer) ResourceUsage(r *pb.ResourceUsageRequest, s pb.PlzEvents_ResourceUsageServer) error {
   113  	// This doesn't necessarily have to match the update frequency, but it seems sensible to do so
   114  	// since the clients won't get any benefit of anything more frequent.
   115  	ticker := time.NewTicker(resourceUpdateFrequency)
   116  	defer ticker.Stop()
   117  	for range ticker.C {
   118  		if err := s.Send(resourceToProto(e.State.Stats)); err != nil {
   119  			return err
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  // MultiplexEvents receives events from core and distributes them to receiving clients
   126  func (e *eventServer) MultiplexEvents(ch chan *core.BuildResult) {
   127  	for r := range ch {
   128  		p := toProto(r)
   129  		// Target labels don't exist on the internal build events, retrieve them here.
   130  		if t := e.State.Graph.Target(r.Label); t != nil {
   131  			p.Labels = t.Labels
   132  		}
   133  		// Similarly these fields come off the state, they're not stored historically for each event.
   134  		p.NumActive = int64(e.State.NumActive())
   135  		p.NumDone = int64(e.State.NumDone())
   136  		for _, c := range e.Clients {
   137  			c <- p
   138  		}
   139  	}
   140  	log.Info("Reached end of event stream, shutting down connected clients")
   141  	for _, c := range e.Clients {
   142  		close(c) // This terminates communication with whichever client is on the end of it.
   143  	}
   144  	log.Info("Closed channels to all connected clients")
   145  }
   146  
   147  // stopServer implements a graceful server stop with a timeout, followed by a non-graceful (ungainly?) shutdown.
   148  // Essentially GracefulStop can block forever and we don't want to allow clients to do that to us.
   149  func stopServer(s *grpc.Server) {
   150  	ch := make(chan bool, 1)
   151  	go func() {
   152  		s.GracefulStop()
   153  		ch <- true
   154  	}()
   155  	select {
   156  	case <-ch:
   157  	case <-time.After(disconnectTimeout):
   158  		log.Warning("Remote client hasn't disconnected in alloted time, rapid shutdown initiated")
   159  		s.Stop()
   160  	}
   161  }