package spawn import ( "fmt" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/beam" "github.com/dotcloud/docker/utils" "os" "os/exec" ) var initCalled bool // Init checks if the current process has been created by Spawn. // // If no, it returns nil and the original program can continue // unmodified. // // If no, it hijacks the process to run as a child worker controlled // by its parent over a beam connection, with f exposed as a remote // service. In this case Init never returns. // // The hijacking process takes place as follows: // - Open file descriptor 3 as a beam endpoint. If this fails, // terminate the current process. // - Start a new engine. // - Call f.Install on the engine. Any handlers registered // will be available for remote invocation by the parent. // - Listen for beam messages from the parent and pass them to // the handlers. // - When the beam endpoint is closed by the parent, terminate // the current process. // // NOTE: Init must be called at the beginning of the same program // calling Spawn. This is because Spawn approximates a "fork" by // re-executing the current binary - where it expects spawn.Init // to intercept the control flow and execute the worker code. func Init(f engine.Installer) error { initCalled = true if os.Getenv("ENGINESPAWN") != "1" { return nil } fmt.Printf("[%d child]\n", os.Getpid()) // Hijack the process childErr := func() error { fd3 := os.NewFile(3, "beam-introspect") introsp, err := beam.FileConn(fd3) if err != nil { return fmt.Errorf("beam introspection error: %v", err) } fd3.Close() defer introsp.Close() eng := engine.NewReceiver(introsp) if err := f.Install(eng.Engine); err != nil { return err } if err := eng.Run(); err != nil { return err } return nil }() if childErr != nil { os.Exit(1) } os.Exit(0) return nil // Never reached } // 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 }