// In "SBooting", we have a pid and socket to the process we will use, // but it has not yet finished initializing (generally, running the code // specific to this slave). When we receive a message about the success or // failure of this operation, we transition to either crashed or ready. func (s *SlaveNode) doBootingState() string { // -> {SCrashed, SReady} // The slave will execute its action and respond with a status... // Note we don't hold the mutex while waiting for the action to execute. msg, err := s.socket.ReadMessage() if err != nil { s.L.Lock() defer s.L.Unlock() s.Error = err.Error() slog.ErrorString("[" + s.Name + "] " + err.Error()) return SCrashed } s.trace("received action message") s.L.Lock() defer s.L.Unlock() msg, err = messages.ParseActionResponseMessage(msg) if err != nil { slog.ErrorString("[" + s.Name + "] " + err.Error()) } if msg == "OK" { return SReady } // Clean up: if s.pid > 0 { syscall.Kill(s.pid, syscall.SIGKILL) } s.wipe() s.Error = msg return SCrashed }
// In "SBooting", we have a pid and socket to the process we will use, // but it has not yet finished initializing (generally, running the code // specific to this slave. When we receive a message about the success or // failure of this operation, we transition to either crashed or ready. func (s *SlaveNode) doBootingState() string { // -> {SCrashed, SReady} // The slave will execute its action and respond with a status... // Note we don't hold the mutex while waiting for the action to execute. msg, err := s.socket.ReadMessage() if err != nil { slog.Error(err) } s.trace("in booting state") s.L.Lock() defer s.L.Unlock() msg, err = messages.ParseActionResponseMessage(msg) if err != nil { slog.ErrorString("[" + s.Name + "] " + err.Error()) } if msg == "OK" { return SReady } // Drain the process's feature messages, if we have any, so // that reloads happen when any load-time problems get fixed: s.L.Unlock() s.handleMessages() s.L.Lock() // Clean up: if s.Pid > 0 { syscall.Kill(s.Pid, syscall.SIGKILL) } s.wipe() s.Error = msg return SCrashed }
func sendCommandLineArguments(usock *unixsocket.Usock, args []string) error { master, slave, err := unixsocket.Socketpair(syscall.SOCK_STREAM) if err != nil { return err } usock.WriteFD(int(slave.Fd())) if err != nil { return err } slave.Close() go func() { defer master.Close() argAsBytes := []byte{} for _, arg := range args[1:] { argAsBytes = append(argAsBytes, []byte(arg)...) argAsBytes = append(argAsBytes, byte(0)) } _, err = master.Write(argAsBytes) if err != nil { slog.ErrorString("Could not send arguments across: " + err.Error() + "\r") return } }() return nil }
func (s *SlaveNode) SlaveWasInitialized(pid int, usock *unixsocket.Usock) { s.L.Lock() s.wipe() s.Pid = pid s.socket = usock if s.state == sUnbooted { s.event <- true } else { if pid > 0 { syscall.Kill(pid, syscall.SIGKILL) } slog.ErrorString("Unexpected process for slave `" + s.Name + "` was killed") } s.L.Unlock() }
func (s *SlaveNode) SlaveWasInitialized(pid int, usock *unixsocket.Usock, featurePipeFd int) { file := os.NewFile(uintptr(featurePipeFd), "featurepipe") s.L.Lock() s.wipe() s.featurePipe = file s.Pid = pid s.socket = usock if s.State == SUnbooted { s.event <- true } else { if pid > 0 { syscall.Kill(pid, syscall.SIGKILL) } slog.ErrorString("Unexpected process for slave `" + s.Name + "` was killed") } s.L.Unlock() }
func (s *SlaveNode) SlaveWasInitialized(pid, parentPid int, usock *unixsocket.Usock, featurePipeFd int) { file := os.NewFile(uintptr(featurePipeFd), "featurepipe") s.L.Lock() if !s.ReportBootEvent() { if pid > 0 { syscall.Kill(pid, syscall.SIGKILL) } slog.ErrorString(fmt.Sprintf("Unexpected process %d with parent %d for slave %q was killed", pid, parentPid, s.Name)) } else { s.wipe() s.pid = pid s.socket = usock go s.handleMessages(file) s.trace("initialized slave %s with pid %d from parent %d", s.Name, pid, parentPid) } s.L.Unlock() }
func Run(args []string, input io.Reader, output *os.File) int { if os.Getenv("RAILS_ENV") != "" { println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.") println("As a safety precaution to protect you from nuking your development database,") println("Zeus will now cowardly refuse to proceed. Please unset RAILS_ENV and try again.") return 1 } isTerminal := ttyutils.IsTerminal(output.Fd()) var master, slave *os.File var err error if isTerminal { master, slave, err = pty.Open() } else { master, slave, err = unixsocket.Socketpair(syscall.SOCK_STREAM) } if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } defer master.Close() var oldState *ttyutils.Termios if isTerminal { oldState, err = ttyutils.MakeTerminalRaw(output.Fd()) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } defer ttyutils.RestoreTerminalState(output.Fd(), oldState) } // should this happen if we're running over a pipe? I think maybe not? ttyutils.MirrorWinsize(output, master) addr, err := net.ResolveUnixAddr("unixgram", unixsocket.ZeusSockName()) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } conn, err := net.DialUnix("unix", nil, addr) if err != nil { zerror.ErrorCantConnectToMaster() return 1 } usock := unixsocket.New(conn) msg := messages.CreateCommandAndArgumentsMessage(args, os.Getpid()) usock.WriteMessage(msg) err = sendCommandLineArguments(usock, args) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } usock.WriteFD(int(slave.Fd())) slave.Close() msg, err = usock.ReadMessage() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } parts := strings.Split(msg, "\000") commandPid, err := strconv.Atoi(parts[0]) defer func() { if commandPid > 0 { // Just in case. syscall.Kill(commandPid, 9) } }() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } if isTerminal { c := make(chan os.Signal, 1) handledSignals := append(append(terminatingSignals, syscall.SIGWINCH), syscall.SIGCONT) signal.Notify(c, handledSignals...) go func() { for sig := range c { if sig == syscall.SIGCONT { syscall.Kill(commandPid, syscall.SIGCONT) } else if sig == syscall.SIGWINCH { ttyutils.MirrorWinsize(output, master) syscall.Kill(commandPid, syscall.SIGWINCH) } else { // member of terminatingSignals ttyutils.RestoreTerminalState(output.Fd(), oldState) print("\r") syscall.Kill(commandPid, sig.(syscall.Signal)) os.Exit(1) } } }() } var exitStatus int = -1 if len(parts) > 2 { exitStatus, err = strconv.Atoi(parts[0]) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } } eof := make(chan bool) go func() { for { buf := make([]byte, 1024) n, err := master.Read(buf) if err == nil || (err == io.EOF && n > 0) { output.Write(buf[:n]) } else { eof <- true break } } }() go func() { buf := make([]byte, 8192) for { n, err := input.Read(buf) if err != nil { eof <- true break } if isTerminal { for i := 0; i < n; i++ { switch buf[i] { case sigInt: syscall.Kill(commandPid, syscall.SIGINT) case sigQuit: syscall.Kill(commandPid, syscall.SIGQUIT) case sigTstp: syscall.Kill(commandPid, syscall.SIGTSTP) syscall.Kill(os.Getpid(), syscall.SIGTSTP) } } } master.Write(buf[:n]) } }() <-eof if exitStatus == -1 { msg, err = usock.ReadMessage() if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } parts := strings.Split(msg, "\000") exitStatus, err = strconv.Atoi(parts[0]) if err != nil { slog.ErrorString(err.Error() + "\r") return 1 } } return exitStatus }