spawn.go 3.3 KB

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