Skip to content

Commit

Permalink
WIP and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
burke committed Aug 24, 2012
1 parent 47bf120 commit 432732f
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 35 deletions.
3 changes: 1 addition & 2 deletions cmd/zeus/zeus.go
Expand Up @@ -2,9 +2,8 @@ package main

import (
"github.com/burke/zeus/zeus"
"fmt"
)

func main () {
fmt.Println(zeus.Run())
zeus.Run()
}
44 changes: 44 additions & 0 deletions 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.
9 changes: 9 additions & 0 deletions 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
79 changes: 46 additions & 33 deletions zeus/zeus.go
Expand Up @@ -6,63 +6,76 @@ import (
"encoding/json"
"os/exec"
"fmt"
"errors"
)

type StageBootInfo struct {
Pid int
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
}

0 comments on commit 432732f

Please sign in to comment.