server.go 3.3 KB

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