Docker-DCO-1.1-Signed-off-by: Solomon Hykes <solomon@docker.com> (github: shykes)
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,119 @@ |
| 0 |
+package spawn |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/engine" |
|
| 5 |
+ "github.com/dotcloud/docker/pkg/beam" |
|
| 6 |
+ "github.com/dotcloud/docker/utils" |
|
| 7 |
+ "os" |
|
| 8 |
+ "os/exec" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+var initCalled bool |
|
| 12 |
+ |
|
| 13 |
+// Init checks if the current process has been created by Spawn. |
|
| 14 |
+// |
|
| 15 |
+// If no, it returns nil and the original program can continue |
|
| 16 |
+// unmodified. |
|
| 17 |
+// |
|
| 18 |
+// If no, it hijacks the process to run as a child worker controlled |
|
| 19 |
+// by its parent over a beam connection, with f exposed as a remote |
|
| 20 |
+// service. In this case Init never returns. |
|
| 21 |
+// |
|
| 22 |
+// The hijacking process takes place as follows: |
|
| 23 |
+// - Open file descriptor 3 as a beam endpoint. If this fails, |
|
| 24 |
+// terminate the current process. |
|
| 25 |
+// - Start a new engine. |
|
| 26 |
+// - Call f.Install on the engine. Any handlers registered |
|
| 27 |
+// will be available for remote invocation by the parent. |
|
| 28 |
+// - Listen for beam messages from the parent and pass them to |
|
| 29 |
+// the handlers. |
|
| 30 |
+// - When the beam endpoint is closed by the parent, terminate |
|
| 31 |
+// the current process. |
|
| 32 |
+// |
|
| 33 |
+// NOTE: Init must be called at the beginning of the same program |
|
| 34 |
+// calling Spawn. This is because Spawn approximates a "fork" by |
|
| 35 |
+// re-executing the current binary - where it expects spawn.Init |
|
| 36 |
+// to intercept the control flow and execute the worker code. |
|
| 37 |
+func Init(f engine.Installer) error {
|
|
| 38 |
+ initCalled = true |
|
| 39 |
+ if os.Getenv("ENGINESPAWN") != "1" {
|
|
| 40 |
+ return nil |
|
| 41 |
+ } |
|
| 42 |
+ fmt.Printf("[%d child]\n", os.Getpid())
|
|
| 43 |
+ // Hijack the process |
|
| 44 |
+ childErr := func() error {
|
|
| 45 |
+ fd3 := os.NewFile(3, "beam-introspect") |
|
| 46 |
+ introsp, err := beam.FileConn(fd3) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return fmt.Errorf("beam introspection error: %v", err)
|
|
| 49 |
+ } |
|
| 50 |
+ fd3.Close() |
|
| 51 |
+ defer introsp.Close() |
|
| 52 |
+ eng := engine.NewReceiver(introsp) |
|
| 53 |
+ if err := f.Install(eng.Engine); err != nil {
|
|
| 54 |
+ return err |
|
| 55 |
+ } |
|
| 56 |
+ if err := eng.Run(); err != nil {
|
|
| 57 |
+ return err |
|
| 58 |
+ } |
|
| 59 |
+ return nil |
|
| 60 |
+ }() |
|
| 61 |
+ if childErr != nil {
|
|
| 62 |
+ os.Exit(1) |
|
| 63 |
+ } |
|
| 64 |
+ os.Exit(0) |
|
| 65 |
+ return nil // Never reached |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// Spawn starts a new Engine in a child process and returns |
|
| 69 |
+// a proxy Engine through which it can be controlled. |
|
| 70 |
+// |
|
| 71 |
+// The commands available on the child engine are determined |
|
| 72 |
+// by an earlier call to Init. It is important that Init be |
|
| 73 |
+// called at the very beginning of the current program - this |
|
| 74 |
+// allows it to be called as a re-execution hook in the child |
|
| 75 |
+// process. |
|
| 76 |
+// |
|
| 77 |
+// Long story short, if you want to expose `myservice` in a child |
|
| 78 |
+// process, do this: |
|
| 79 |
+// |
|
| 80 |
+// func main() {
|
|
| 81 |
+// spawn.Init(myservice) |
|
| 82 |
+// [..] |
|
| 83 |
+// child, err := spawn.Spawn() |
|
| 84 |
+// [..] |
|
| 85 |
+// child.Job("dosomething").Run()
|
|
| 86 |
+// } |
|
| 87 |
+func Spawn() (*engine.Engine, error) {
|
|
| 88 |
+ if !initCalled {
|
|
| 89 |
+ return nil, fmt.Errorf("spawn.Init must be called at the top of the main() function")
|
|
| 90 |
+ } |
|
| 91 |
+ cmd := exec.Command(utils.SelfPath()) |
|
| 92 |
+ cmd.Env = append(cmd.Env, "ENGINESPAWN=1") |
|
| 93 |
+ local, remote, err := beam.SocketPair() |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return nil, err |
|
| 96 |
+ } |
|
| 97 |
+ child, err := beam.FileConn(local) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ local.Close() |
|
| 100 |
+ remote.Close() |
|
| 101 |
+ return nil, err |
|
| 102 |
+ } |
|
| 103 |
+ local.Close() |
|
| 104 |
+ cmd.ExtraFiles = append(cmd.ExtraFiles, remote) |
|
| 105 |
+ // FIXME: the beam/engine glue has no way to inform the caller |
|
| 106 |
+ // of the child's termination. The next call will simply return |
|
| 107 |
+ // an error. |
|
| 108 |
+ if err := cmd.Start(); err != nil {
|
|
| 109 |
+ child.Close() |
|
| 110 |
+ return nil, err |
|
| 111 |
+ } |
|
| 112 |
+ eng := engine.New() |
|
| 113 |
+ if err := engine.NewSender(child).Install(eng); err != nil {
|
|
| 114 |
+ child.Close() |
|
| 115 |
+ return nil, err |
|
| 116 |
+ } |
|
| 117 |
+ return eng, nil |
|
| 118 |
+} |
| 0 | 119 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,61 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/engine" |
|
| 5 |
+ "github.com/dotcloud/docker/engine/spawn" |
|
| 6 |
+ "log" |
|
| 7 |
+ "os" |
|
| 8 |
+ "os/exec" |
|
| 9 |
+ "strings" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func main() {
|
|
| 13 |
+ fmt.Printf("[%d] MAIN\n", os.Getpid())
|
|
| 14 |
+ spawn.Init(&Worker{})
|
|
| 15 |
+ fmt.Printf("[%d parent] spawning\n", os.Getpid())
|
|
| 16 |
+ eng, err := spawn.Spawn() |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ log.Fatal(err) |
|
| 19 |
+ } |
|
| 20 |
+ fmt.Printf("[parent] spawned\n")
|
|
| 21 |
+ job := eng.Job(os.Args[1], os.Args[2:]...) |
|
| 22 |
+ job.Stdout.Add(os.Stdout) |
|
| 23 |
+ job.Stderr.Add(os.Stderr) |
|
| 24 |
+ job.Run() |
|
| 25 |
+ // FIXME: use the job's status code |
|
| 26 |
+ os.Exit(0) |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+type Worker struct {
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (w *Worker) Install(eng *engine.Engine) error {
|
|
| 33 |
+ eng.Register("exec", w.Exec)
|
|
| 34 |
+ eng.Register("cd", w.Cd)
|
|
| 35 |
+ eng.Register("echo", w.Echo)
|
|
| 36 |
+ return nil |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (w *Worker) Exec(job *engine.Job) engine.Status {
|
|
| 40 |
+ fmt.Printf("--> %v\n", job.Args)
|
|
| 41 |
+ cmd := exec.Command(job.Args[0], job.Args[1:]...) |
|
| 42 |
+ cmd.Stdout = job.Stdout |
|
| 43 |
+ cmd.Stderr = os.Stderr |
|
| 44 |
+ if err := cmd.Run(); err != nil {
|
|
| 45 |
+ return job.Errorf("%v\n", err)
|
|
| 46 |
+ } |
|
| 47 |
+ return engine.StatusOK |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func (w *Worker) Cd(job *engine.Job) engine.Status {
|
|
| 51 |
+ if err := os.Chdir(job.Args[0]); err != nil {
|
|
| 52 |
+ return job.Errorf("%v\n", err)
|
|
| 53 |
+ } |
|
| 54 |
+ return engine.StatusOK |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+func (w *Worker) Echo(job *engine.Job) engine.Status {
|
|
| 58 |
+ fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) |
|
| 59 |
+ return engine.StatusOK |
|
| 60 |
+} |