github.com/tiagovtristao/plz@v13.4.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  	"github.com/thought-machine/please/src/core"
    19  	pb "github.com/thought-machine/please/src/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  	// TODO(peterebden): TLS support
    43  	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    44  	if err != nil {
    45  		log.Fatalf("%s", err)
    46  	}
    47  	addr := lis.Addr().String()
    48  	s := grpc.NewServer()
    49  	server := &eventServer{State: state}
    50  	results, _ := state.RemoteResults()
    51  	go server.MultiplexEvents(results)
    52  	pb.RegisterPlzEventsServer(s, server)
    53  	go s.Serve(lis)
    54  	log.Notice("Serving events over gRPC on :%s", addr)
    55  	return addr, func() {
    56  		stopServer(s)
    57  	}
    58  }
    59  
    60  // An eventServer handles the RPC requests to connected clients.
    61  type eventServer struct {
    62  	State   *core.BuildState
    63  	Clients []chan *pb.BuildEventResponse
    64  }
    65  
    66  // ServerConfig implements the RPC interface.
    67  func (e *eventServer) ServerConfig(ctx context.Context, r *pb.ServerConfigRequest) (*pb.ServerConfigResponse, error) {
    68  	targets := make([]*pb.BuildLabel, len(e.State.OriginalTargets))
    69  	for i, t := range e.State.OriginalTargets {
    70  		targets[i] = toProtoBuildLabel(t)
    71  	}
    72  	_, results := e.State.RemoteResults()
    73  	return &pb.ServerConfigResponse{
    74  		NumThreads:      int32(e.State.Config.Please.NumThreads),
    75  		OriginalTargets: targets,
    76  		Tests:           e.State.NeedTests,
    77  		Coverage:        e.State.NeedCoverage,
    78  		LastEvents:      toProtos(results, e.State.NumActive(), e.State.NumDone()),
    79  		StartTime:       e.State.StartTime.UnixNano(),
    80  	}, nil
    81  }
    82  
    83  // BuildEvents implements the RPC interface.
    84  func (e *eventServer) BuildEvents(r *pb.BuildEventRequest, s pb.PlzEvents_BuildEventsServer) error {
    85  	if p, ok := peer.FromContext(s.Context()); ok {
    86  		log.Notice("Remote client connected from %s to receive events", p.Addr)
    87  	}
    88  	c := make(chan *pb.BuildEventResponse, buffering)
    89  	e.Clients = append(e.Clients, c)
    90  	// Client is now connected to the stream and will receive all events from here on.
    91  	for event := range c {
    92  		if err := s.Send(event); err != nil {
    93  			// Something's stuffed, disconnect the client from our event streams
    94  			log.Notice("Remote client disconnected (%s)", err)
    95  			for i, client := range e.Clients {
    96  				if client == c {
    97  					copy(e.Clients[i:], e.Clients[i+1:])
    98  					last := len(e.Clients) - 1
    99  					e.Clients[last] = nil
   100  					e.Clients = e.Clients[:last]
   101  				}
   102  			}
   103  			return err
   104  		}
   105  	}
   106  	log.Notice("Events finished, terminating remote session")
   107  	return nil
   108  }
   109  
   110  // ResourceUsage implements the RPC interface.
   111  func (e *eventServer) ResourceUsage(r *pb.ResourceUsageRequest, s pb.PlzEvents_ResourceUsageServer) error {
   112  	// This doesn't necessarily have to match the update frequency, but it seems sensible to do so
   113  	// since the clients won't get any benefit of anything more frequent.
   114  	ticker := time.NewTicker(resourceUpdateFrequency)
   115  	defer ticker.Stop()
   116  	for range ticker.C {
   117  		if err := s.Send(resourceToProto(e.State.Stats)); err != nil {
   118  			return err
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  // MultiplexEvents receives events from core and distributes them to receiving clients
   125  func (e *eventServer) MultiplexEvents(ch <-chan *core.BuildResult) {
   126  	for r := range ch {
   127  		p := toProto(r)
   128  		// Target labels don't exist on the internal build events, retrieve them here.
   129  		if t := e.State.Graph.Target(r.Label); t != nil {
   130  			p.Labels = t.Labels
   131  		}
   132  		// Similarly these fields come off the state, they're not stored historically for each event.
   133  		p.NumActive = int64(e.State.NumActive())
   134  		p.NumDone = int64(e.State.NumDone())
   135  		for _, c := range e.Clients {
   136  			c <- p
   137  		}
   138  	}
   139  	log.Info("Reached end of event stream, shutting down connected clients")
   140  	for _, c := range e.Clients {
   141  		close(c) // This terminates communication with whichever client is on the end of it.
   142  	}
   143  	log.Info("Closed channels to all connected clients")
   144  }
   145  
   146  // stopServer implements a graceful server stop with a timeout, followed by a non-graceful (ungainly?) shutdown.
   147  // Essentially GracefulStop can block forever and we don't want to allow clients to do that to us.
   148  func stopServer(s *grpc.Server) {
   149  	ch := make(chan bool, 1)
   150  	go func() {
   151  		s.GracefulStop()
   152  		ch <- true
   153  	}()
   154  	select {
   155  	case <-ch:
   156  	case <-time.After(disconnectTimeout):
   157  		log.Warning("Remote client hasn't disconnected in alloted time, rapid shutdown initiated")
   158  		s.Stop()
   159  	}
   160  }