// Spawn starts a new Engine in a child process and returns // a proxy Engine through which it can be controlled. // // The commands available on the child engine are determined // by an earlier call to Init. It is important that Init be // called at the very beginning of the current program - this // allows it to be called as a re-execution hook in the child // process. // // Long story short, if you want to expose `myservice` in a child // process, do this: // // func main() { // spawn.Init(myservice) // [..] // child, err := spawn.Spawn() // [..] // child.Job("dosomething").Run() // } func Spawn() (*engine.Engine, error) { if !initCalled { return nil, fmt.Errorf("spawn.Init must be called at the top of the main() function") } cmd := exec.Command(utils.SelfPath()) cmd.Env = append(cmd.Env, "ENGINESPAWN=1") local, remote, err := beam.SocketPair() if err != nil { return nil, err } child, err := beam.FileConn(local) if err != nil { local.Close() remote.Close() return nil, err } local.Close() cmd.ExtraFiles = append(cmd.ExtraFiles, remote) // FIXME: the beam/engine glue has no way to inform the caller // of the child's termination. The next call will simply return // an error. if err := cmd.Start(); err != nil { child.Close() return nil, err } eng := engine.New() if err := engine.NewSender(child).Install(eng); err != nil { child.Close() return nil, err } return eng, nil }
func CmdExec(args []string, stdout, stderr io.Writer, in beam.Receiver, out beam.Sender) { cmd := exec.Command(args[1], args[2:]...) cmd.Stdout = stdout cmd.Stderr = stderr //cmd.Stdin = os.Stdin local, remote, err := beam.SocketPair() if err != nil { fmt.Fprintf(stderr, "%v\n", err) return } child, err := beam.FileConn(local) if err != nil { local.Close() remote.Close() fmt.Fprintf(stderr, "%v\n", err) return } local.Close() cmd.ExtraFiles = append(cmd.ExtraFiles, remote) var tasks sync.WaitGroup tasks.Add(1) go func() { defer Debugf("done copying to child\n") defer tasks.Done() defer child.CloseWrite() beam.Copy(child, in) }() tasks.Add(1) go func() { defer Debugf("done copying from child %d\n") defer tasks.Done() r := beam.NewRouter(out) r.NewRoute().All().Handler(func(p []byte, a *os.File) error { return out.Send(data.Message(p).Set("pid", fmt.Sprintf("%d", cmd.Process.Pid)).Bytes(), a) }) beam.Copy(r, child) }() execErr := cmd.Run() // We can close both ends of the socket without worrying about data stuck in the buffer, // because unix socket writes are fully synchronous. child.Close() tasks.Wait() var status string if execErr != nil { status = execErr.Error() } else { status = "ok" } out.Send(data.Empty().Set("status", status).Set("cmd", args...).Bytes(), nil) }
func ReceiveFromConn(connections chan net.Conn, dst beam.Sender) error { for conn := range connections { err := func() error { Logf("parsing message from network...\n") defer Logf("done parsing message from network\n") buf := make([]byte, 4098) n, err := conn.Read(buf) if n == 0 { conn.Close() if err == io.EOF { return nil } else { return err } } Logf("decoding message from '%s'\n", buf[:n]) header, skip, err := data.DecodeString(string(buf[:n])) if err != nil { conn.Close() return err } pub, priv, err := beam.SocketPair() if err != nil { return err } Logf("decoded message: %s\n", data.Message(header).Pretty()) go func(skipped []byte, conn net.Conn, f *os.File) { // this closes both conn and f if len(skipped) > 0 { if _, err := f.Write(skipped); err != nil { Logf("ERROR: %v\n", err) f.Close() conn.Close() return } } bicopy(conn, f) }(buf[skip:n], conn, pub) if err := dst.Send([]byte(header), priv); err != nil { return err } return nil }() if err != nil { Logf("Error reading from connection: %v\n", err) } } return nil }