run.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Package local_manager handles the local plugin runtime management
  2. package local_manager
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "sync"
  9. "github.com/langgenius/dify-plugin-daemon/internal/process"
  10. "github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
  11. "github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
  12. "github.com/langgenius/dify-plugin-daemon/internal/utils/log"
  13. "github.com/langgenius/dify-plugin-daemon/internal/utils/routine"
  14. )
  15. // gc performs garbage collection for the LocalPluginRuntime
  16. func (r *LocalPluginRuntime) gc() {
  17. if r.io_identity != "" {
  18. RemoveStdio(r.io_identity)
  19. }
  20. if r.wait_chan != nil {
  21. close(r.wait_chan)
  22. r.wait_chan = nil
  23. }
  24. }
  25. // Type returns the runtime type of the plugin
  26. func (r *LocalPluginRuntime) Type() plugin_entities.PluginRuntimeType {
  27. return plugin_entities.PLUGIN_RUNTIME_TYPE_LOCAL
  28. }
  29. // getCmd prepares the exec.Cmd for the plugin based on its language
  30. func (r *LocalPluginRuntime) getCmd() (*exec.Cmd, error) {
  31. if r.Config.Meta.Runner.Language == constants.Python {
  32. cmd := exec.Command(r.python_interpreter_path, "-m", r.Config.Meta.Runner.Entrypoint)
  33. cmd.Dir = r.State.WorkingPath
  34. return cmd, nil
  35. }
  36. return nil, fmt.Errorf("unsupported language: %s", r.Config.Meta.Runner.Language)
  37. }
  38. // StartPlugin starts the plugin and manages its lifecycle
  39. func (r *LocalPluginRuntime) StartPlugin() error {
  40. defer log.Info("plugin %s stopped", r.Config.Identity())
  41. defer func() {
  42. r.wait_chan_lock.Lock()
  43. for _, c := range r.wait_stopped_chan {
  44. select {
  45. case c <- true:
  46. default:
  47. }
  48. }
  49. r.wait_chan_lock.Unlock()
  50. }()
  51. // reset wait chan
  52. r.wait_chan = make(chan bool)
  53. // reset wait launched chan
  54. // start plugin
  55. e, err := r.getCmd()
  56. if err != nil {
  57. return err
  58. }
  59. e.Dir = r.State.WorkingPath
  60. // add env INSTALL_METHOD=local
  61. e.Env = append(e.Env, "INSTALL_METHOD=local", "PATH="+os.Getenv("PATH"))
  62. // NOTE: subprocess will be taken care of by subprocess manager
  63. // ensure all subprocess are killed when parent process exits, especially on Golang debugger
  64. process.WrapProcess(e)
  65. // get writer
  66. stdin, err := e.StdinPipe()
  67. if err != nil {
  68. r.SetRestarting()
  69. return fmt.Errorf("get stdin pipe failed: %s", err.Error())
  70. }
  71. defer stdin.Close()
  72. // get stdout
  73. stdout, err := e.StdoutPipe()
  74. if err != nil {
  75. r.SetRestarting()
  76. return fmt.Errorf("get stdout pipe failed: %s", err.Error())
  77. }
  78. defer stdout.Close()
  79. // get stderr
  80. stderr, err := e.StderrPipe()
  81. if err != nil {
  82. r.SetRestarting()
  83. return fmt.Errorf("get stderr pipe failed: %s", err.Error())
  84. }
  85. defer stderr.Close()
  86. if err := e.Start(); err != nil {
  87. r.SetRestarting()
  88. return fmt.Errorf("start plugin failed: %s", err.Error())
  89. }
  90. // add to subprocess manager
  91. process.NewProcess(e)
  92. defer process.RemoveProcess(e)
  93. defer func() {
  94. // wait for plugin to exit
  95. err = e.Wait()
  96. if err != nil {
  97. r.SetRestarting()
  98. log.Error("plugin %s exited with error: %s", r.Config.Identity(), err.Error())
  99. }
  100. r.gc()
  101. }()
  102. defer e.Process.Kill()
  103. log.Info("plugin %s started", r.Config.Identity())
  104. // setup stdio
  105. stdio := PutStdioIo(r.Config.Identity(), stdin, stdout, stderr)
  106. r.io_identity = stdio.GetID()
  107. defer stdio.Stop()
  108. wg := sync.WaitGroup{}
  109. wg.Add(2)
  110. // listen to plugin stdout
  111. routine.Submit(func() {
  112. defer wg.Done()
  113. stdio.StartStdout(func() {})
  114. })
  115. // listen to plugin stderr
  116. routine.Submit(func() {
  117. defer wg.Done()
  118. stdio.StartStderr()
  119. })
  120. // send started event
  121. r.wait_chan_lock.Lock()
  122. for _, c := range r.wait_started_chan {
  123. select {
  124. case c <- true:
  125. default:
  126. }
  127. }
  128. r.wait_chan_lock.Unlock()
  129. // wait for plugin to exit
  130. err = stdio.Wait()
  131. if err != nil {
  132. return err
  133. }
  134. wg.Wait()
  135. // plugin has exited
  136. r.SetPending()
  137. return nil
  138. }
  139. // Wait returns a channel that will be closed when the plugin stops
  140. func (r *LocalPluginRuntime) Wait() (<-chan bool, error) {
  141. if r.wait_chan == nil {
  142. return nil, errors.New("plugin not started")
  143. }
  144. return r.wait_chan, nil
  145. }
  146. // WaitStarted returns a channel that will receive true when the plugin starts
  147. func (r *LocalPluginRuntime) WaitStarted() <-chan bool {
  148. c := make(chan bool)
  149. r.wait_chan_lock.Lock()
  150. r.wait_started_chan = append(r.wait_started_chan, c)
  151. r.wait_chan_lock.Unlock()
  152. return c
  153. }
  154. // WaitStopped returns a channel that will receive true when the plugin stops
  155. func (r *LocalPluginRuntime) WaitStopped() <-chan bool {
  156. c := make(chan bool)
  157. r.wait_chan_lock.Lock()
  158. r.wait_stopped_chan = append(r.wait_stopped_chan, c)
  159. r.wait_chan_lock.Unlock()
  160. return c
  161. }