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 }