run.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. // init initializes the LocalPluginRuntime
  26. func (r *LocalPluginRuntime) init() {
  27. // reset wait chan
  28. r.wait_chan = make(chan bool)
  29. // reset wait launched chan
  30. r.wait_launched_chan_once = sync.Once{}
  31. r.wait_launched_chan = make(chan error)
  32. r.SetLaunching()
  33. }
  34. // Type returns the runtime type of the plugin
  35. func (r *LocalPluginRuntime) Type() plugin_entities.PluginRuntimeType {
  36. return plugin_entities.PLUGIN_RUNTIME_TYPE_LOCAL
  37. }
  38. // getCmd prepares the exec.Cmd for the plugin based on its language
  39. func (r *LocalPluginRuntime) getCmd() (*exec.Cmd, error) {
  40. if r.Config.Meta.Runner.Language == constants.Python {
  41. cmd := exec.Command(r.python_interpreter_path, "-m", r.Config.Meta.Runner.Entrypoint)
  42. cmd.Dir = r.State.WorkingPath
  43. return cmd, nil
  44. }
  45. return nil, fmt.Errorf("unsupported language: %s", r.Config.Meta.Runner.Language)
  46. }
  47. // StartPlugin starts the plugin and manages its lifecycle
  48. func (r *LocalPluginRuntime) StartPlugin() error {
  49. defer log.Info("plugin %s stopped", r.Config.Identity())
  50. defer func() {
  51. r.wait_chan_lock.Lock()
  52. for _, c := range r.wait_stopped_chan {
  53. select {
  54. case c <- true:
  55. default:
  56. }
  57. }
  58. r.wait_chan_lock.Unlock()
  59. }()
  60. r.init()
  61. // start plugin
  62. e, err := r.getCmd()
  63. if err != nil {
  64. r.wait_launched_chan_once.Do(func() {
  65. select {
  66. case r.wait_launched_chan <- err:
  67. default:
  68. }
  69. close(r.wait_launched_chan)
  70. })
  71. return err
  72. }
  73. e.Dir = r.State.WorkingPath
  74. // add env INSTALL_METHOD=local
  75. e.Env = append(e.Env, "INSTALL_METHOD=local", "PATH="+os.Getenv("PATH"))
  76. // NOTE: subprocess will be taken care of by subprocess manager
  77. // ensure all subprocess are killed when parent process exits, especially on Golang debugger
  78. process.WrapProcess(e)
  79. // notify launched, notify error if any
  80. notify_launched := func(err error) {
  81. r.wait_launched_chan_once.Do(func() {
  82. select {
  83. case r.wait_launched_chan <- err:
  84. default:
  85. }
  86. close(r.wait_launched_chan)
  87. })
  88. }
  89. // get writer
  90. stdin, err := e.StdinPipe()
  91. if err != nil {
  92. r.SetRestarting()
  93. err = fmt.Errorf("get stdin pipe failed: %s", err.Error())
  94. notify_launched(err)
  95. return err
  96. }
  97. defer stdin.Close()
  98. // get stdout
  99. stdout, err := e.StdoutPipe()
  100. if err != nil {
  101. r.SetRestarting()
  102. err = fmt.Errorf("get stdout pipe failed: %s", err.Error())
  103. notify_launched(err)
  104. return err
  105. }
  106. defer stdout.Close()
  107. // get stderr
  108. stderr, err := e.StderrPipe()
  109. if err != nil {
  110. r.SetRestarting()
  111. err = fmt.Errorf("get stderr pipe failed: %s", err.Error())
  112. notify_launched(err)
  113. return err
  114. }
  115. defer stderr.Close()
  116. if err := e.Start(); err != nil {
  117. r.SetRestarting()
  118. err = fmt.Errorf("start plugin failed: %s", err.Error())
  119. notify_launched(err)
  120. return err
  121. }
  122. // add to subprocess manager
  123. process.NewProcess(e)
  124. defer process.RemoveProcess(e)
  125. defer func() {
  126. // wait for plugin to exit
  127. err = e.Wait()
  128. if err != nil {
  129. r.SetRestarting()
  130. log.Error("plugin %s exited with error: %s", r.Config.Identity(), err.Error())
  131. }
  132. // close wait launched chan
  133. r.wait_launched_chan_once.Do(func() {
  134. close(r.wait_launched_chan)
  135. })
  136. r.gc()
  137. }()
  138. defer e.Process.Kill()
  139. log.Info("plugin %s started", r.Config.Identity())
  140. // setup stdio
  141. stdio := PutStdioIo(r.Config.Identity(), stdin, stdout, stderr)
  142. r.io_identity = stdio.GetID()
  143. defer stdio.Stop()
  144. wg := sync.WaitGroup{}
  145. wg.Add(2)
  146. // listen to plugin stdout
  147. routine.Submit(func() {
  148. defer wg.Done()
  149. stdio.StartStdout(func() {
  150. // get heartbeat, notify launched
  151. r.wait_launched_chan_once.Do(func() {
  152. close(r.wait_launched_chan)
  153. })
  154. })
  155. })
  156. // listen to plugin stderr
  157. routine.Submit(func() {
  158. defer wg.Done()
  159. stdio.StartStderr()
  160. })
  161. // send started event
  162. r.wait_chan_lock.Lock()
  163. for _, c := range r.wait_started_chan {
  164. select {
  165. case c <- true:
  166. default:
  167. }
  168. }
  169. r.wait_chan_lock.Unlock()
  170. // wait for plugin to exit
  171. err = stdio.Wait()
  172. if err != nil {
  173. return err
  174. }
  175. wg.Wait()
  176. // plugin has exited
  177. r.SetPending()
  178. return nil
  179. }
  180. // Wait returns a channel that will be closed when the plugin stops
  181. func (r *LocalPluginRuntime) Wait() (<-chan bool, error) {
  182. if r.wait_chan == nil {
  183. return nil, errors.New("plugin not started")
  184. }
  185. return r.wait_chan, nil
  186. }
  187. // WaitStarted returns a channel that will receive true when the plugin starts
  188. func (r *LocalPluginRuntime) WaitStarted() <-chan bool {
  189. c := make(chan bool)
  190. r.wait_chan_lock.Lock()
  191. r.wait_started_chan = append(r.wait_started_chan, c)
  192. r.wait_chan_lock.Unlock()
  193. return c
  194. }
  195. // WaitStopped returns a channel that will receive true when the plugin stops
  196. func (r *LocalPluginRuntime) WaitStopped() <-chan bool {
  197. c := make(chan bool)
  198. r.wait_chan_lock.Lock()
  199. r.wait_stopped_chan = append(r.wait_stopped_chan, c)
  200. r.wait_chan_lock.Unlock()
  201. return c
  202. }
  203. // WaitLaunched returns a channel that will receive an error if the plugin fails to launch
  204. func (r *LocalPluginRuntime) WaitLaunched() <-chan error {
  205. return r.wait_launched_chan
  206. }