server.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package lifecycle
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "time"
  12. "github.com/ollama/ollama/api"
  13. )
  14. func getCLIFullPath(command string) string {
  15. cmdPath := ""
  16. appExe, err := os.Executable()
  17. if err == nil {
  18. cmdPath = filepath.Join(filepath.Dir(appExe), command)
  19. _, err := os.Stat(cmdPath)
  20. if err == nil {
  21. return cmdPath
  22. }
  23. }
  24. cmdPath, err = exec.LookPath(command)
  25. if err == nil {
  26. _, err := os.Stat(cmdPath)
  27. if err == nil {
  28. return cmdPath
  29. }
  30. }
  31. pwd, err := os.Getwd()
  32. if err == nil {
  33. cmdPath = filepath.Join(pwd, command)
  34. _, err = os.Stat(cmdPath)
  35. if err == nil {
  36. return cmdPath
  37. }
  38. }
  39. return command
  40. }
  41. func SpawnServer(ctx context.Context, command string) (chan int, error) {
  42. done := make(chan int)
  43. logDir := filepath.Dir(ServerLogFile)
  44. _, err := os.Stat(logDir)
  45. if errors.Is(err, os.ErrNotExist) {
  46. if err := os.MkdirAll(logDir, 0o755); err != nil {
  47. return done, fmt.Errorf("create ollama server log dir %s: %v", logDir, err)
  48. }
  49. }
  50. cmd := getCmd(ctx, getCLIFullPath(command))
  51. // send stdout and stderr to a file
  52. stdout, err := cmd.StdoutPipe()
  53. if err != nil {
  54. return done, fmt.Errorf("failed to spawn server stdout pipe %s", err)
  55. }
  56. stderr, err := cmd.StderrPipe()
  57. if err != nil {
  58. return done, fmt.Errorf("failed to spawn server stderr pipe %s", err)
  59. }
  60. stdin, err := cmd.StdinPipe()
  61. if err != nil {
  62. return done, fmt.Errorf("failed to spawn server stdin pipe %s", err)
  63. }
  64. // TODO - rotation
  65. logFile, err := os.OpenFile(ServerLogFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
  66. if err != nil {
  67. return done, fmt.Errorf("failed to create server log %w", err)
  68. }
  69. go func() {
  70. defer logFile.Close()
  71. io.Copy(logFile, stdout) //nolint:errcheck
  72. }()
  73. go func() {
  74. defer logFile.Close()
  75. io.Copy(logFile, stderr) //nolint:errcheck
  76. }()
  77. // run the command and wait for it to finish
  78. if err := cmd.Start(); err != nil {
  79. return done, fmt.Errorf("failed to start server %w", err)
  80. }
  81. if cmd.Process != nil {
  82. slog.Info(fmt.Sprintf("started ollama server with pid %d", cmd.Process.Pid))
  83. }
  84. slog.Info(fmt.Sprintf("ollama server logs %s", ServerLogFile))
  85. go func() {
  86. // Keep the server running unless we're shuttind down the app
  87. crashCount := 0
  88. for {
  89. cmd.Wait() //nolint:errcheck
  90. stdin.Close()
  91. var code int
  92. if cmd.ProcessState != nil {
  93. code = cmd.ProcessState.ExitCode()
  94. }
  95. select {
  96. case <-ctx.Done():
  97. slog.Debug(fmt.Sprintf("server shutdown with exit code %d", code))
  98. done <- code
  99. return
  100. default:
  101. crashCount++
  102. slog.Warn(fmt.Sprintf("server crash %d - exit code %d - respawning", crashCount, code))
  103. time.Sleep(500 * time.Millisecond)
  104. if err := cmd.Start(); err != nil {
  105. slog.Error(fmt.Sprintf("failed to restart server %s", err))
  106. // Keep trying, but back off if we keep failing
  107. time.Sleep(time.Duration(crashCount) * time.Second)
  108. }
  109. }
  110. }
  111. }()
  112. return done, nil
  113. }
  114. func IsServerRunning(ctx context.Context) bool {
  115. client, err := api.ClientFromEnvironment()
  116. if err != nil {
  117. slog.Info("unable to connect to server")
  118. return false
  119. }
  120. err = client.Heartbeat(ctx)
  121. if err != nil {
  122. slog.Debug(fmt.Sprintf("heartbeat from server: %s", err))
  123. slog.Info("unable to connect to server")
  124. return false
  125. }
  126. return true
  127. }