run.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Package local_runtime handles the local plugin runtime management
  2. package local_runtime
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "os/exec"
  8. "sync"
  9. "github.com/langgenius/dify-plugin-daemon/internal/utils/log"
  10. "github.com/langgenius/dify-plugin-daemon/internal/utils/routine"
  11. "github.com/langgenius/dify-plugin-daemon/pkg/entities/constants"
  12. "github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
  13. )
  14. // gc performs garbage collection for the LocalPluginRuntime
  15. func (r *LocalPluginRuntime) gc() {
  16. if r.ioIdentity != "" {
  17. removeStdioHandler(r.ioIdentity)
  18. }
  19. if r.waitChan != nil {
  20. close(r.waitChan)
  21. r.waitChan = 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.pythonInterpreterPath, "-m", r.Config.Meta.Runner.Entrypoint)
  32. cmd.Dir = r.State.WorkingPath
  33. cmd.Env = cmd.Environ()
  34. if r.HttpsProxy != "" {
  35. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTPS_PROXY=%s", r.HttpsProxy))
  36. }
  37. if r.HttpProxy != "" {
  38. cmd.Env = append(cmd.Env, fmt.Sprintf("HTTP_PROXY=%s", r.HttpProxy))
  39. }
  40. return cmd, nil
  41. }
  42. return nil, fmt.Errorf("unsupported language: %s", r.Config.Meta.Runner.Language)
  43. }
  44. // StartPlugin starts the plugin and manages its lifecycle
  45. func (r *LocalPluginRuntime) StartPlugin() error {
  46. defer log.Info("plugin %s stopped", r.Config.Identity())
  47. defer func() {
  48. r.waitChanLock.Lock()
  49. for _, c := range r.waitStoppedChan {
  50. select {
  51. case c <- true:
  52. default:
  53. }
  54. }
  55. r.waitChanLock.Unlock()
  56. }()
  57. if r.isNotFirstStart {
  58. r.SetRestarting()
  59. } else {
  60. r.SetLaunching()
  61. r.isNotFirstStart = true
  62. }
  63. // reset wait chan
  64. r.waitChan = make(chan bool)
  65. // reset wait launched chan
  66. // start plugin
  67. e, err := r.getCmd()
  68. if err != nil {
  69. return err
  70. }
  71. e.Dir = r.State.WorkingPath
  72. // add env INSTALL_METHOD=local
  73. e.Env = append(e.Environ(), "INSTALL_METHOD=local", "PATH="+os.Getenv("PATH"))
  74. // get writer
  75. stdin, err := e.StdinPipe()
  76. if err != nil {
  77. return fmt.Errorf("get stdin pipe failed: %s", err.Error())
  78. }
  79. defer stdin.Close()
  80. // get stdout
  81. stdout, err := e.StdoutPipe()
  82. if err != nil {
  83. return fmt.Errorf("get stdout pipe failed: %s", err.Error())
  84. }
  85. defer stdout.Close()
  86. // get stderr
  87. stderr, err := e.StderrPipe()
  88. if err != nil {
  89. return fmt.Errorf("get stderr pipe failed: %s", err.Error())
  90. }
  91. defer stderr.Close()
  92. if err := e.Start(); err != nil {
  93. return fmt.Errorf("start plugin failed: %s", err.Error())
  94. }
  95. var stdio *stdioHolder
  96. defer func() {
  97. // wait for plugin to exit
  98. originalErr := e.Wait()
  99. if originalErr != nil {
  100. // get stdio
  101. var err error
  102. if stdio != nil {
  103. stdioErr := stdio.Error()
  104. if stdioErr != nil {
  105. err = errors.Join(originalErr, stdioErr)
  106. } else {
  107. err = originalErr
  108. }
  109. } else {
  110. err = originalErr
  111. }
  112. if err != nil {
  113. log.Error("plugin %s exited with error: %s", r.Config.Identity(), err.Error())
  114. } else {
  115. log.Error("plugin %s exited with unknown error", r.Config.Identity())
  116. }
  117. }
  118. r.gc()
  119. }()
  120. // ensure the plugin process is killed after the plugin exits
  121. defer e.Process.Kill()
  122. log.Info("plugin %s started", r.Config.Identity())
  123. // setup stdio
  124. stdio = registerStdioHandler(r.Config.Identity(), stdin, stdout, stderr)
  125. r.ioIdentity = stdio.GetID()
  126. defer stdio.Stop()
  127. wg := sync.WaitGroup{}
  128. wg.Add(2)
  129. // listen to plugin stdout
  130. routine.Submit(map[string]string{
  131. "module": "plugin_manager",
  132. "type": "local",
  133. "function": "StartStdout",
  134. }, func() {
  135. defer wg.Done()
  136. stdio.StartStdout(func() {})
  137. })
  138. // listen to plugin stderr
  139. routine.Submit(map[string]string{
  140. "module": "plugin_manager",
  141. "type": "local",
  142. "function": "StartStderr",
  143. }, func() {
  144. defer wg.Done()
  145. stdio.StartStderr()
  146. })
  147. // send started event
  148. r.waitChanLock.Lock()
  149. for _, c := range r.waitStartedChan {
  150. select {
  151. case c <- true:
  152. default:
  153. }
  154. }
  155. r.waitChanLock.Unlock()
  156. // wait for plugin to exit
  157. err = stdio.Wait()
  158. if err != nil {
  159. return errors.Join(err, stdio.Error())
  160. }
  161. wg.Wait()
  162. // plugin has exited
  163. return nil
  164. }
  165. // Wait returns a channel that will be closed when the plugin stops
  166. func (r *LocalPluginRuntime) Wait() (<-chan bool, error) {
  167. if r.waitChan == nil {
  168. return nil, errors.New("plugin not started")
  169. }
  170. return r.waitChan, nil
  171. }
  172. // WaitStarted returns a channel that will receive true when the plugin starts
  173. func (r *LocalPluginRuntime) WaitStarted() <-chan bool {
  174. c := make(chan bool)
  175. r.waitChanLock.Lock()
  176. r.waitStartedChan = append(r.waitStartedChan, c)
  177. r.waitChanLock.Unlock()
  178. return c
  179. }
  180. // WaitStopped returns a channel that will receive true when the plugin stops
  181. func (r *LocalPluginRuntime) WaitStopped() <-chan bool {
  182. c := make(chan bool)
  183. r.waitChanLock.Lock()
  184. r.waitStoppedChan = append(r.waitStoppedChan, c)
  185. r.waitChanLock.Unlock()
  186. return c
  187. }
  188. // Stop stops the plugin
  189. func (r *LocalPluginRuntime) Stop() {
  190. // inherit from PluginRuntime
  191. r.PluginRuntime.Stop()
  192. // get stdio
  193. stdio := getStdioHandler(r.ioIdentity)
  194. if stdio != nil {
  195. stdio.Stop()
  196. }
  197. }