assets.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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. )
  15. var (
  16. lock sync.Mutex
  17. payloadsDir = ""
  18. )
  19. func PayloadsDir() (string, error) {
  20. lock.Lock()
  21. defer lock.Unlock()
  22. var err error
  23. if payloadsDir == "" {
  24. cleanupTmpDirs()
  25. tmpDir := os.Getenv("OLLAMA_TMPDIR")
  26. if tmpDir == "" {
  27. tmpDir, err = os.MkdirTemp("", "ollama")
  28. if err != nil {
  29. return "", fmt.Errorf("failed to generate tmp dir: %w", err)
  30. }
  31. } else {
  32. err = os.MkdirAll(tmpDir, 0755)
  33. if err != nil {
  34. return "", fmt.Errorf("failed to generate tmp dir %s: %w", tmpDir, err)
  35. }
  36. }
  37. // Track our pid so we can clean up orphaned tmpdirs
  38. pidFilePath := filepath.Join(tmpDir, "ollama.pid")
  39. pidFile, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm)
  40. if err != nil {
  41. return "", err
  42. }
  43. if _, err := pidFile.Write([]byte(fmt.Sprint(os.Getpid()))); err != nil {
  44. return "", err
  45. }
  46. // We create a distinct subdirectory for payloads within the tmpdir
  47. // This will typically look like /tmp/ollama3208993108/runners on linux
  48. payloadsDir = filepath.Join(tmpDir, "runners")
  49. }
  50. return payloadsDir, nil
  51. }
  52. // Best effort to clean up prior tmpdirs
  53. func cleanupTmpDirs() {
  54. dirs, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*"))
  55. if err != nil {
  56. return
  57. }
  58. for _, d := range dirs {
  59. info, err := os.Stat(d)
  60. if err != nil || !info.IsDir() {
  61. continue
  62. }
  63. raw, err := os.ReadFile(filepath.Join(d, "ollama.pid"))
  64. if err == nil {
  65. pid, err := strconv.Atoi(string(raw))
  66. if err == nil {
  67. if proc, err := os.FindProcess(int(pid)); err == nil && !errors.Is(proc.Signal(syscall.Signal(0)), os.ErrProcessDone) {
  68. // Another running ollama, ignore this tmpdir
  69. continue
  70. }
  71. }
  72. } else {
  73. slog.Debug("failed to open ollama.pid", "path", d, "error", err)
  74. }
  75. err = os.RemoveAll(d)
  76. if err != nil {
  77. slog.Debug(fmt.Sprintf("unable to cleanup stale tmpdir %s: %s", d, err))
  78. }
  79. }
  80. }
  81. func Cleanup() {
  82. lock.Lock()
  83. defer lock.Unlock()
  84. if payloadsDir != "" {
  85. // We want to fully clean up the tmpdir parent of the payloads dir
  86. tmpDir := filepath.Clean(filepath.Join(payloadsDir, ".."))
  87. slog.Debug("cleaning up", "dir", tmpDir)
  88. err := os.RemoveAll(tmpDir)
  89. if err != nil {
  90. // On windows, if we remove too quickly the llama.dll may still be in-use and fail to remove
  91. time.Sleep(1000 * time.Millisecond)
  92. err = os.RemoveAll(tmpDir)
  93. if err != nil {
  94. slog.Warn("failed to clean up", "dir", tmpDir, "err", err)
  95. }
  96. }
  97. }
  98. }
  99. func UpdatePath(dir string) {
  100. if runtime.GOOS == "windows" {
  101. tmpDir := filepath.Dir(dir)
  102. pathComponents := strings.Split(os.Getenv("PATH"), ";")
  103. i := 0
  104. for _, comp := range pathComponents {
  105. if strings.EqualFold(comp, dir) {
  106. return
  107. }
  108. // Remove any other prior paths to our temp dir
  109. if !strings.HasPrefix(strings.ToLower(comp), strings.ToLower(tmpDir)) {
  110. pathComponents[i] = comp
  111. i++
  112. }
  113. }
  114. newPath := strings.Join(append([]string{dir}, pathComponents...), ";")
  115. slog.Info(fmt.Sprintf("Updating PATH to %s", newPath))
  116. os.Setenv("PATH", newPath)
  117. }
  118. // linux and darwin rely on rpath
  119. }