From 432732f0b7a1255520879da14b95e895e5394f9d Mon Sep 17 00:00:00 2001 From: Burke Libbey Date: Fri, 24 Aug 2012 11:40:49 -0500 Subject: [PATCH] WIP and docs --- cmd/zeus/zeus.go | 3 +- docs/master_slave_handshake.md | 44 +++++++++++++++++++ docs/terminology.md | 9 ++++ zeus/zeus.go | 79 ++++++++++++++++++++-------------- 4 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 docs/master_slave_handshake.md create mode 100644 docs/terminology.md diff --git a/cmd/zeus/zeus.go b/cmd/zeus/zeus.go index 8be41425..dcd42470 100644 --- a/cmd/zeus/zeus.go +++ b/cmd/zeus/zeus.go @@ -2,9 +2,8 @@ package main import ( "github.com/burke/zeus/zeus" - "fmt" ) func main () { - fmt.Println(zeus.Run()) + zeus.Run() } diff --git a/docs/master_slave_handshake.md b/docs/master_slave_handshake.md new file mode 100644 index 00000000..db9e910f --- /dev/null +++ b/docs/master_slave_handshake.md @@ -0,0 +1,44 @@ +# Master/Slave Handshake + +#### 1. Socket + +The Slave is always started with an environment variable named `ZEUS_MASTER_FD`. The file descriptor at the given integer value is a socket to the Master process. + +The Slave should open a UNIX Domain Socket using the `ZEUS_MASTER_FD` File Descriptor (`globalMasterSock`). + +The Slave opens a new UNIX datagram Socketpair (`local`, `remote`) + +The Slave sends `remote` across `globalMasterSock`. + +#### 2. PID and Identifier + +The Slave determines whether it has been given an Identifier. If it is the first-booted slave, it was booted +by the Master, and will not have one. When a Slave forks, it is passed an Identifier by the Master that it +passes along to the newly-forked process. + +The Slave sends a JSON-encoded hash of "pid" and "identifier", with "pid" being the integer pid of the current process, +and "identifier" being the identifier, if there is one, or the empty string if none. + +#### 3. Action + +The Master now sends, along `remote`, a string containing the code representing the action for this Slave. + +The Slave evaluates this code. + +#### 4. Action Result + +If there were no runtime errors in evaluating the action, the Slave writes "OK" to `local`. + +If there were runtime errors, the slave returns a string representing the errors in an arbitrary and +hopefully helpful format. It should normally be identical to the console output format should the errors +have been raised and printed to stderr. + +#### 5. Loaded Files + +Any time after the action has been executed, the Slave may (and should) send, over `local`, a list of files +that have been newly-loaded in the course of evaluating the action. + +Languages are expected to implement this using clever tricks. + +Steps 1-4 happend sequentially and in-order, but Submitting files in Step 5 should not prevent the Slave from +handling further commands from the master. The Slave should be considered 'connected' after Step 4. diff --git a/docs/terminology.md b/docs/terminology.md new file mode 100644 index 00000000..ab0ca91d --- /dev/null +++ b/docs/terminology.md @@ -0,0 +1,9 @@ +# Terminology + +* a Client is a process initiated by the user requesting zeus to run a command. + +* the Master is the Go program which mediates all the interaction between the other processes + +* a Slave is a process managed by Zeus which is used to load dependencies for commands + +* a Command process is one forked from a Slave and connected to a Client diff --git a/zeus/zeus.go b/zeus/zeus.go index e88c5197..169ce758 100644 --- a/zeus/zeus.go +++ b/zeus/zeus.go @@ -6,6 +6,7 @@ import ( "encoding/json" "os/exec" "fmt" + "errors" ) type StageBootInfo struct { @@ -13,56 +14,68 @@ type StageBootInfo struct { Identifier string } -func readMessageFromRuby(fd int) (string, error) { - newFile := fdToFile(fd, "ruby-sock") - newSocket, _ := makeUnixSocket(newFile) - msg, _, e := readFromUnixSocket(newSocket) - if e != nil { - panic(e) - } +func runInitialCommand(sock *os.File) { + cmd := exec.Command("/Users/burke/.rbenv/shims/ruby", "/Users/burke/go/src/github.com/burke/zeus/a.rb") + cmd.Env = append(os.Environ(), fmt.Sprintf("ZEUS_MASTER_FD=%d", sock.Fd())) + cmd.ExtraFiles = []*os.File{sock} - var bootInfo StageBootInfo - err := json.Unmarshal([]byte(msg), &bootInfo) + go cmd.Run() +} + +func Run () (error) { + masterSockLocal, masterSockRemote, err := socketpair(syscall.SOCK_DGRAM) if err != nil { - return "", err + return err } - newSocket.Write([]byte("File.open('omg.log','a'){|f|f.puts 'HAHA BUSINESS'}")) - - msg2, _, _ := readFromUnixSocket(newSocket) - if msg2 == "OK" { - newSocket.Write([]byte("default_bundle")) + masterUSockLocal, err := makeUnixSocket(masterSockLocal) + if err != nil { + return err } - return "ya", nil -} + runInitialCommand(masterSockRemote) -func Run () (string) { - lf, rf, err := socketpair(syscall.SOCK_DGRAM) + // Having just started the process, we expect an IO, which we convert to a UNIX domain socket + _, fd, err := readFromUnixSocket(masterUSockLocal) if err != nil { - panic(err) + return err } - - local, err := makeUnixSocket(lf) + if fd < 0 { + return errors.New("expected file descriptor, but got none") + } + clientFile := fdToFile(fd, "boot-sock") + clientSocket, err := makeUnixSocket(clientFile) if err != nil { - panic(err) + return err } - cmd := exec.Command("/Users/burke/.rbenv/shims/ruby", "/Users/burke/go/src/github.com/burke/zeus/a.rb") - cmd.Env = append(os.Environ(), fmt.Sprintf("ZEUS_MASTER_FD=%d", rf.Fd())) - cmd.ExtraFiles = []*os.File{rf} + // We now expect the client to use this fd they send us to send a JSON-encoded representation of their PID and identifier... + msg, _, err := readFromUnixSocket(clientSocket) + if err != nil { + return err + } + var bootInfo StageBootInfo + err = json.Unmarshal([]byte(msg), &bootInfo) + if err != nil { + return err + } - go cmd.Run() + // Now that we have that, we look up and send the action for that identifier: + clientSocket.Write([]byte("File.open('omg.log','a'){|f|f.puts 'HAHA BUSINESS TIME'}")) - msg, fd, err := readFromUnixSocket(local) + // It will respond with its status + msg, _, err = readFromUnixSocket(clientSocket) if err != nil { - panic(err) + return err } - if fd >= 0 { - str, _ := readMessageFromRuby(fd) - return str + if msg == "OK" { + clientSocket.Write([]byte("default_bundle")) + } else { + return errors.New(msg) } - return msg + // And now we could instruct it to fork: + + return nil }