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 }