run.go 4.4 KB

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