123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- 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
- }
|