func (c *ServiceClient) attemptSend(timeout chan bool, attempts chan sendAttempt, ri *skynet.RequestInfo, fn string, in interface{}) { // first find an available instance var r pools.Resource var err error var sp *servicePool for r == nil { if len(c.instances) < 1 { attempts <- sendAttempt{err: errors.New("No instances found")} return } sp = <-c.servicePool log.Println(log.TRACE, "Sending request to: "+sp.service.UUID) // then, get a connection to that instance r, err = sp.pool.Acquire() defer sp.pool.Release(r) if err != nil { if r != nil { r.Close() } failed := FailedConnection{err} log.Printf(log.ERROR, "%T: %+v", failed, failed) c.instanceFailureChan <- *sp.service } } if err != nil { log.Printf(log.ERROR, "Error: %v", err) attempts <- sendAttempt{err: err} return } sr := r.(ServiceResource) result, serviceErr, err := c.sendToInstance(sr, ri, fn, in) if err != nil { // some communication error happened, shut down this connection and remove it from the pool failed := FailedConnection{err} log.Printf(log.ERROR, "%T: %+v", failed, failed) c.instanceFailureChan <- *sp.service sr.Close() return } attempts <- sendAttempt{ result: result, err: serviceErr, } }
// ServiceClient.sendToInstance() tries to make an RPC request on a particular connection to an instance func (c *ServiceClient) sendToInstance(sr ServiceResource, requestInfo *skynet.RequestInfo, funcName string, in interface{}) ( result []byte, serviceErr, err error) { sin := skynet.ServiceRPCIn{ RequestInfo: requestInfo, Method: funcName, ClientID: sr.clientID, } sin.In, err = bson.Marshal(in) if err != nil { err = fmt.Errorf("Error calling bson.Marshal: %v", err) return } sout := skynet.ServiceRPCOut{} err = sr.rpcClient.Call(sr.service.Name+".Forward", sin, &sout) if err != nil { sr.Close() // Log failure log.Printf(log.ERROR, "Error calling sr.rpcClient.Call: "+err.Error()) } if sout.ErrString != "" { serviceErr = serviceError{sout.ErrString} } result = sout.Out return }
func (s *Service) listen(addr skynet.BindAddr, bindWait *sync.WaitGroup) { var err error s.rpcListener, err = addr.Listen() if err != nil { panic(err) } log.Printf(log.INFO, "%+v\n", ServiceListening{ Addr: &addr, ServiceInfo: s.ServiceInfo, }) // We may have changed port due to conflict, ensure config has the correct port now a, _ := skynet.BindAddrFromString(addr.String()) s.ServiceAddr.IPAddress = a.IPAddress s.ServiceAddr.Port = a.Port bindWait.Done() for { conn, err := s.rpcListener.AcceptTCP() if s.shuttingDown { break } if err != nil && !s.shuttingDown { log.Println(log.ERROR, "AcceptTCP failed", err) continue } s.connectionChan <- conn } }
func (s *Service) register() { // this version must be run from the mux() goroutine if s.Registered { return } err := skynet.GetServiceManager().Register(s.ServiceInfo.UUID) if err != nil { log.Println(log.ERROR, "Failed to register service: "+err.Error()) } s.Registered = true log.Printf(log.INFO, "%+v\n", ServiceRegistered{s.ServiceInfo}) s.Delegate.Registered(s) // Call user defined callback }
func watchSignals(c chan os.Signal, s *Service) { signal.Notify(c, syscall.SIGINT, syscall.SIGKILL, syscall.SIGSEGV, syscall.SIGSTOP, syscall.SIGTERM) for { select { case sig := <-c: switch sig.(syscall.Signal) { // Trap signals for clean shutdown case syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGSEGV, syscall.SIGSTOP, syscall.SIGTERM: log.Printf(log.INFO, "%+v", KillSignal{sig.(syscall.Signal)}) s.Shutdown() return } } } }
func (s *Service) serveAdminRequests() { rId := os.Stderr.Fd() + 2 wId := os.Stderr.Fd() + 3 pipeReader := os.NewFile(uintptr(rId), "") pipeWriter := os.NewFile(uintptr(wId), "") s.pipe = daemon.NewPipe(pipeReader, pipeWriter) b := make([]byte, daemon.MAX_PIPE_BYTES) for { n, err := s.pipe.Read(b) if err != nil { if err != io.EOF { log.Printf(log.ERROR, "Error reading from admin pipe "+err.Error()) } else { // We received EOF, ensure we shutdown (if daemon died we could be orphaned) s.Shutdown() } return } cmd := string(b[:n]) log.Println(log.TRACE, "Received "+cmd+" from daemon") switch cmd { case "SHUTDOWN": s.Shutdown() s.pipe.Write([]byte("ACK")) break case "REGISTER": s.Register() s.pipe.Write([]byte("ACK")) case "UNREGISTER": s.Unregister() s.pipe.Write([]byte("ACK")) case "LOG DEBUG", "LOG TRACE", "LOG INFO", "LOG WARN", "LOG ERROR", "LOG FATAL", "LOG PANIC": parts := strings.Split(cmd, " ") log.SetLogLevel(log.LevelFromString(parts[1])) log.Println(log.INFO, "Setting log level to "+parts[1]) s.pipe.Write([]byte("ACK")) } } }
func (s *SkynetDaemon) StartSubService(requestInfo *skynet.RequestInfo, in daemon.StartSubServiceRequest, out *daemon.StartSubServiceResponse) (err error) { out.UUID = config.NewUUID() log.Printf(log.TRACE, "%+v", SubserviceStart{ BinaryName: in.BinaryName, Args: in.Args, }) ss, err := NewSubService(in.BinaryName, in.Args, out.UUID, in.Registered) if err != nil { return } s.serviceLock.Lock() s.Services[out.UUID] = ss s.serviceLock.Unlock() start, startErr := ss.Start() if startErr != nil { return errors.New("Service failed to start: " + startErr.Error()) } else if !start { return errors.New("Service failed to start") } tc := time.Tick(RerunWait * 2) go func() { // Wait for startup timer to see if we're still running // We want to avoid keeping a state of a large list of services that failed to start <-tc if ss.IsRunning() { s.saveState() } }() return }
func NewServiceRPC(s *Service) (srpc *ServiceRPC) { srpc = &ServiceRPC{ service: s, methods: make(map[string]reflect.Value), } // scan through methods looking for a method (RequestInfo, // something, something) error typ := reflect.TypeOf(srpc.service.Delegate) for i := 0; i < typ.NumMethod(); i++ { m := typ.Method(i) if reservedMethodNames[m.Name] { continue } // this is the check to see if something is exported if m.PkgPath != "" { continue } f := m.Func ftyp := f.Type() // must have four parameters: (receiver, RequestInfo, // somethingIn, somethingOut) if ftyp.NumIn() != 4 { goto problem } // don't have to check for the receiver // check the second parameter if ftyp.In(1) != RequestInfoPtrType { goto problem } // the somethingIn can be anything // somethingOut must be a pointer or a map switch ftyp.In(3).Kind() { case reflect.Ptr, reflect.Map: default: goto problem } // must have one return value that is an error if ftyp.NumOut() != 1 { goto problem } if ftyp.Out(0) != ErrorType { goto problem } // we've got a method! srpc.methods[m.Name] = f srpc.MethodNames = append(srpc.MethodNames, m.Name) continue problem: log.Printf(log.WARN, "Bad RPC method for %T: %q %v\n", s.Delegate, m.Name, f) } return }
// ServiceRPC.Forward is the entry point for RPC calls. It wraps actual RPC calls // and provides a slot for the RequestInfo. The parameters to the actual RPC // calls are transmitted in a []byte, and are then marshalled/unmarshalled on // either end. func (srpc *ServiceRPC) Forward(in skynet.ServiceRPCIn, out *skynet.ServiceRPCOut) (err error) { srpc.service.activeRequests.Add(1) defer srpc.service.activeRequests.Done() go stats.MethodCalled(in.Method) clientInfo, ok := srpc.service.getClientInfo(in.ClientID) if !ok { err = errors.New("did not provide the ClientID") return } in.RequestInfo.ConnectionAddress = clientInfo.Address.String() if in.RequestInfo.OriginAddress == "" || !srpc.service.IsTrusted(clientInfo.Address) { in.RequestInfo.OriginAddress = in.RequestInfo.ConnectionAddress } mc := MethodCall{ MethodName: in.Method, RequestInfo: in.RequestInfo, } log.Printf(log.INFO, "%+v", mc) m, ok := srpc.methods[in.Method] if !ok { err = errors.New(fmt.Sprintf("No such method %q", in.Method)) return } inValuePtr := reflect.New(m.Type().In(2)) err = bson.Unmarshal(in.In, inValuePtr.Interface()) if err != nil { return } // Allocate the out parameter of the RPC call. outType := m.Type().In(3) var outValue reflect.Value switch outType.Kind() { case reflect.Ptr: outValue = reflect.New(m.Type().In(3).Elem()) case reflect.Map: outValue = reflect.MakeMap(outType) default: panic("illegal out param type") } startTime := time.Now().UnixNano() params := []reflect.Value{ reflect.ValueOf(srpc.service.Delegate), reflect.ValueOf(in.RequestInfo), inValuePtr.Elem(), outValue, } returns := m.Call(params) duration := time.Now().UnixNano() - startTime mcp := MethodCompletion{ MethodName: in.Method, RequestInfo: in.RequestInfo, Duration: duration, } log.Printf(log.INFO, "%+v", mcp) out.Out, err = bson.Marshal(outValue.Interface()) if err != nil { return } var rerr error = nil erri := returns[0].Interface() if erri != nil { rerr, _ = erri.(error) out.ErrString = rerr.Error() } go stats.MethodCompleted(in.Method, duration, rerr) return }