assets.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. runnersDir := os.Getenv("OLLAMA_RUNNERS_DIR")
  25. // On Windows we do not carry the payloads inside the main executable
  26. if runtime.GOOS == "windows" && runnersDir == "" {
  27. appExe, err := os.Executable()
  28. if err != nil {
  29. slog.Error("failed to lookup executable path", "error", err)
  30. return "", err
  31. }
  32. cwd, err := os.Getwd()
  33. if err != nil {
  34. slog.Error("failed to lookup working directory", "error", err)
  35. return "", err
  36. }
  37. var paths []string
  38. for _, root := range []string{filepath.Dir(appExe), cwd} {
  39. paths = append(paths,
  40. filepath.Join(root),
  41. filepath.Join(root, "windows-"+runtime.GOARCH),
  42. filepath.Join(root, "dist", "windows-"+runtime.GOARCH),
  43. )
  44. }
  45. // Try a few variations to improve developer experience when building from source in the local tree
  46. for _, p := range paths {
  47. candidate := filepath.Join(p, "ollama_runners")
  48. _, err := os.Stat(candidate)
  49. if err == nil {
  50. runnersDir = candidate
  51. break
  52. }
  53. }
  54. if runnersDir == "" {
  55. err = fmt.Errorf("unable to locate llm runner directory. Set OLLAMA_RUNNERS_DIR to the location of 'ollama_runners'")
  56. slog.Error("incomplete distribution", "error", err)
  57. return "", err
  58. }
  59. }
  60. if runnersDir != "" {
  61. payloadsDir = runnersDir
  62. return payloadsDir, nil
  63. }
  64. // The remainder only applies on non-windows where we still carry payloads in the main executable
  65. cleanupTmpDirs()
  66. tmpDir := os.Getenv("OLLAMA_TMPDIR")
  67. if tmpDir == "" {
  68. tmpDir, err = os.MkdirTemp("", "ollama")
  69. if err != nil {
  70. return "", fmt.Errorf("failed to generate tmp dir: %w", err)
  71. }
  72. } else {
  73. err = os.MkdirAll(tmpDir, 0755)
  74. if err != nil {
  75. return "", fmt.Errorf("failed to generate tmp dir %s: %w", tmpDir, err)
  76. }
  77. }
  78. // Track our pid so we can clean up orphaned tmpdirs
  79. pidFilePath := filepath.Join(tmpDir, "ollama.pid")
  80. pidFile, err := os.OpenFile(pidFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.ModePerm)
  81. if err != nil {
  82. return "", err
  83. }
  84. if _, err := pidFile.Write([]byte(fmt.Sprint(os.Getpid()))); err != nil {
  85. return "", err
  86. }
  87. // We create a distinct subdirectory for payloads within the tmpdir
  88. // This will typically look like /tmp/ollama3208993108/runners on linux
  89. payloadsDir = filepath.Join(tmpDir, "runners")
  90. }
  91. return payloadsDir, nil
  92. }
  93. // Best effort to clean up prior tmpdirs
  94. func cleanupTmpDirs() {
  95. dirs, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*"))
  96. if err != nil {
  97. return
  98. }
  99. for _, d := range dirs {
  100. info, err := os.Stat(d)
  101. if err != nil || !info.IsDir() {
  102. continue
  103. }
  104. raw, err := os.ReadFile(filepath.Join(d, "ollama.pid"))
  105. if err == nil {
  106. pid, err := strconv.Atoi(string(raw))
  107. if err == nil {
  108. if proc, err := os.FindProcess(int(pid)); err == nil && !errors.Is(proc.Signal(syscall.Signal(0)), os.ErrProcessDone) {
  109. // Another running ollama, ignore this tmpdir
  110. continue
  111. }
  112. }
  113. } else {
  114. slog.Debug("failed to open ollama.pid", "path", d, "error", err)
  115. }
  116. err = os.RemoveAll(d)
  117. if err != nil {
  118. slog.Debug("unable to cleanup stale tmpdir", "path", d, "error", err)
  119. }
  120. }
  121. }
  122. func Cleanup() {
  123. lock.Lock()
  124. defer lock.Unlock()
  125. runnersDir := os.Getenv("OLLAMA_RUNNERS_DIR")
  126. if payloadsDir != "" && runnersDir == "" && runtime.GOOS != "windows" {
  127. // We want to fully clean up the tmpdir parent of the payloads dir
  128. tmpDir := filepath.Clean(filepath.Join(payloadsDir, ".."))
  129. slog.Debug("cleaning up", "dir", tmpDir)
  130. err := os.RemoveAll(tmpDir)
  131. if err != nil {
  132. // On windows, if we remove too quickly the llama.dll may still be in-use and fail to remove
  133. time.Sleep(1000 * time.Millisecond)
  134. err = os.RemoveAll(tmpDir)
  135. if err != nil {
  136. slog.Warn("failed to clean up", "dir", tmpDir, "err", err)
  137. }
  138. }
  139. }
  140. }
  141. func UpdatePath(dir string) {
  142. if runtime.GOOS == "windows" {
  143. tmpDir := filepath.Dir(dir)
  144. pathComponents := strings.Split(os.Getenv("PATH"), ";")
  145. i := 0
  146. for _, comp := range pathComponents {
  147. if strings.EqualFold(comp, dir) {
  148. return
  149. }
  150. // Remove any other prior paths to our temp dir
  151. if !strings.HasPrefix(strings.ToLower(comp), strings.ToLower(tmpDir)) {
  152. pathComponents[i] = comp
  153. i++
  154. }
  155. }
  156. newPath := strings.Join(append([]string{dir}, pathComponents...), ";")
  157. slog.Info("updating", "PATH", newPath)
  158. os.Setenv("PATH", newPath)
  159. }
  160. // linux and darwin rely on rpath
  161. }