assets.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package gpu
  2. import (
  3. "errors"
  4. "fmt"
  5. "log/slog"
  6. "os"
  7. "path/filepath"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "github.com/ollama/ollama/envconfig"
  15. )
  16. var (
  17. lock sync.Mutex
  18. payloadsDir = ""
  19. )
  20. func PayloadsDir() (string, error) {
  21. lock.Lock()
  22. defer lock.Unlock()
  23. var err error
  24. if payloadsDir == "" {
  25. runnersDir := envconfig.RunnersDir()
  26. if runnersDir != "" {
  27. payloadsDir = runnersDir
  28. return payloadsDir, nil
  29. }
  30. // The remainder only applies on non-windows where we still carry payloads in the main executable
  31. cleanupTmpDirs()
  32. tmpDir := envconfig.TmpDir()
  33. if tmpDir == "" {
  34. tmpDir, err = os.MkdirTemp("", "ollama")
  35. if err != nil {
  36. return "", fmt.Errorf("failed to generate tmp dir: %w", err)
  37. }
  38. } else {
  39. err = os.MkdirAll(tmpDir, 0o755)
  40. if err != nil {
  41. return "", fmt.Errorf("failed to generate tmp dir %s: %w", tmpDir, err)
  42. }
  43. }
  44. // Track our pid so we can clean up orphaned tmpdirs
  45. n := filepath.Join(tmpDir, "ollama.pid")
  46. if err := os.WriteFile(n, []byte(strconv.Itoa(os.Getpid())), 0o644); err != nil {
  47. return "", fmt.Errorf("failed to write pid file %s: %w", n, err)
  48. }
  49. // We create a distinct subdirectory for payloads within the tmpdir
  50. // This will typically look like /tmp/ollama3208993108/runners on linux
  51. payloadsDir = filepath.Join(tmpDir, "runners")
  52. }
  53. return payloadsDir, nil
  54. }
  55. // Best effort to clean up prior tmpdirs
  56. func cleanupTmpDirs() {
  57. matches, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*", "ollama.pid"))
  58. if err != nil {
  59. return
  60. }
  61. for _, match := range matches {
  62. raw, err := os.ReadFile(match)
  63. if errors.Is(err, os.ErrNotExist) {
  64. slog.Debug("not a ollama runtime directory, skipping", "path", match)
  65. continue
  66. } else if err != nil {
  67. slog.Warn("could not read ollama.pid, skipping", "path", match, "error", err)
  68. continue
  69. }
  70. pid, err := strconv.Atoi(string(raw))
  71. if err != nil {
  72. slog.Warn("invalid pid, skipping", "path", match, "error", err)
  73. continue
  74. }
  75. p, err := os.FindProcess(pid)
  76. if err == nil && !errors.Is(p.Signal(syscall.Signal(0)), os.ErrProcessDone) {
  77. slog.Warn("process still running, skipping", "pid", pid, "path", match)
  78. continue
  79. }
  80. if err := os.Remove(match); err != nil {
  81. slog.Warn("could not cleanup stale pidfile", "path", match, "error", err)
  82. }
  83. runners := filepath.Join(filepath.Dir(match), "runners")
  84. if err := os.RemoveAll(runners); err != nil {
  85. slog.Warn("could not cleanup stale runners", "path", runners, "error", err)
  86. }
  87. if err := os.Remove(filepath.Dir(match)); err != nil {
  88. slog.Warn("could not cleanup stale tmpdir", "path", filepath.Dir(match), "error", err)
  89. }
  90. }
  91. }
  92. func Cleanup() {
  93. lock.Lock()
  94. defer lock.Unlock()
  95. runnersDir := envconfig.RunnersDir()
  96. if payloadsDir != "" && runnersDir == "" && runtime.GOOS != "windows" {
  97. // We want to fully clean up the tmpdir parent of the payloads dir
  98. tmpDir := filepath.Clean(filepath.Join(payloadsDir, ".."))
  99. slog.Debug("cleaning up", "dir", tmpDir)
  100. err := os.RemoveAll(tmpDir)
  101. if err != nil {
  102. // On windows, if we remove too quickly the llama.dll may still be in-use and fail to remove
  103. time.Sleep(1000 * time.Millisecond)
  104. err = os.RemoveAll(tmpDir)
  105. if err != nil {
  106. slog.Warn("failed to clean up", "dir", tmpDir, "err", err)
  107. }
  108. }
  109. }
  110. }
  111. func UpdatePath(dir string) {
  112. if runtime.GOOS == "windows" {
  113. tmpDir := filepath.Dir(dir)
  114. pathComponents := strings.Split(os.Getenv("PATH"), ";")
  115. i := 0
  116. for _, comp := range pathComponents {
  117. if strings.EqualFold(comp, dir) {
  118. return
  119. }
  120. // Remove any other prior paths to our temp dir
  121. if !strings.HasPrefix(strings.ToLower(comp), strings.ToLower(tmpDir)) {
  122. pathComponents[i] = comp
  123. i++
  124. }
  125. }
  126. newPath := strings.Join(append([]string{dir}, pathComponents...), ";")
  127. slog.Info("updating", "PATH", newPath)
  128. os.Setenv("PATH", newPath)
  129. }
  130. // linux and darwin rely on rpath
  131. }