assets.go 3.87 KB
Newer Older
Daniel Hiltgen's avatar
Daniel Hiltgen committed
1
2
3
package gpu

import (
Daniel Hiltgen's avatar
Daniel Hiltgen committed
4
	"errors"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
5
6
7
8
9
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"runtime"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
10
	"strconv"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
11
	"strings"
12
	"sync"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
13
	"syscall"
14
	"time"
15

16
	"github.com/ollama/ollama/envconfig"
17
)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
18

19
20
21
var (
	lock        sync.Mutex
	payloadsDir = ""
Daniel Hiltgen's avatar
Daniel Hiltgen committed
22
23
)

24
25
26
func PayloadsDir() (string, error) {
	lock.Lock()
	defer lock.Unlock()
Daniel Hiltgen's avatar
Daniel Hiltgen committed
27
	var err error
28
	if payloadsDir == "" {
Michael Yang's avatar
string  
Michael Yang committed
29
		runnersDir := envconfig.RunnersDir()
30

31
32
33
34
35
36
		if runnersDir != "" {
			payloadsDir = runnersDir
			return payloadsDir, nil
		}

		// The remainder only applies on non-windows where we still carry payloads in the main executable
Daniel Hiltgen's avatar
Daniel Hiltgen committed
37
		cleanupTmpDirs()
Michael Yang's avatar
string  
Michael Yang committed
38
		tmpDir := envconfig.TmpDir()
Daniel Hiltgen's avatar
Daniel Hiltgen committed
39
40
41
42
43
44
		if tmpDir == "" {
			tmpDir, err = os.MkdirTemp("", "ollama")
			if err != nil {
				return "", fmt.Errorf("failed to generate tmp dir: %w", err)
			}
		} else {
Michael Yang's avatar
lint  
Michael Yang committed
45
			err = os.MkdirAll(tmpDir, 0o755)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
46
47
48
			if err != nil {
				return "", fmt.Errorf("failed to generate tmp dir %s: %w", tmpDir, err)
			}
49
		}
Daniel Hiltgen's avatar
Daniel Hiltgen committed
50
51

		// Track our pid so we can clean up orphaned tmpdirs
Michael Yang's avatar
Michael Yang committed
52
53
54
		n := filepath.Join(tmpDir, "ollama.pid")
		if err := os.WriteFile(n, []byte(strconv.Itoa(os.Getpid())), 0o644); err != nil {
			return "", fmt.Errorf("failed to write pid file %s: %w", n, err)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
55
56
		}

57
58
59
		// We create a distinct subdirectory for payloads within the tmpdir
		// This will typically look like /tmp/ollama3208993108/runners on linux
		payloadsDir = filepath.Join(tmpDir, "runners")
Daniel Hiltgen's avatar
Daniel Hiltgen committed
60
	}
61
62
63
	return payloadsDir, nil
}

Daniel Hiltgen's avatar
Daniel Hiltgen committed
64
65
// Best effort to clean up prior tmpdirs
func cleanupTmpDirs() {
66
	matches, err := filepath.Glob(filepath.Join(os.TempDir(), "ollama*", "ollama.pid"))
Daniel Hiltgen's avatar
Daniel Hiltgen committed
67
68
69
	if err != nil {
		return
	}
70
71
72
73
74

	for _, match := range matches {
		raw, err := os.ReadFile(match)
		if errors.Is(err, os.ErrNotExist) {
			slog.Debug("not a ollama runtime directory, skipping", "path", match)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
75
			continue
76
77
		} else if err != nil {
			slog.Warn("could not read ollama.pid, skipping", "path", match, "error", err)
78
			continue
Daniel Hiltgen's avatar
Daniel Hiltgen committed
79
		}
Josh Yan's avatar
Josh Yan committed
80
81

		pid, err := strconv.Atoi(string(raw))
Josh Yan's avatar
Josh Yan committed
82
		if err != nil {
83
			slog.Warn("invalid pid, skipping", "path", match, "error", err)
Josh Yan's avatar
Josh Yan committed
84
			continue
Josh Yan's avatar
Josh Yan committed
85
86
		}

87
88
89
		p, err := os.FindProcess(pid)
		if err == nil && !errors.Is(p.Signal(syscall.Signal(0)), os.ErrProcessDone) {
			slog.Warn("process still running, skipping", "pid", pid, "path", match)
Josh Yan's avatar
Josh Yan committed
90
91
92
			continue
		}

93
94
95
96
97
98
99
100
101
102
103
		if err := os.Remove(match); err != nil {
			slog.Warn("could not cleanup stale pidfile", "path", match, "error", err)
		}

		runners := filepath.Join(filepath.Dir(match), "runners")
		if err := os.RemoveAll(runners); err != nil {
			slog.Warn("could not cleanup stale runners", "path", runners, "error", err)
		}

		if err := os.Remove(filepath.Dir(match)); err != nil {
			slog.Warn("could not cleanup stale tmpdir", "path", filepath.Dir(match), "error", err)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
104
105
106
107
		}
	}
}

108
109
110
func Cleanup() {
	lock.Lock()
	defer lock.Unlock()
Michael Yang's avatar
string  
Michael Yang committed
111
	runnersDir := envconfig.RunnersDir()
112
	if payloadsDir != "" && runnersDir == "" && runtime.GOOS != "windows" {
113
114
115
116
		// We want to fully clean up the tmpdir parent of the payloads dir
		tmpDir := filepath.Clean(filepath.Join(payloadsDir, ".."))
		slog.Debug("cleaning up", "dir", tmpDir)
		err := os.RemoveAll(tmpDir)
117
		if err != nil {
118
119
120
121
122
123
			// On windows, if we remove too quickly the llama.dll may still be in-use and fail to remove
			time.Sleep(1000 * time.Millisecond)
			err = os.RemoveAll(tmpDir)
			if err != nil {
				slog.Warn("failed to clean up", "dir", tmpDir, "err", err)
			}
Daniel Hiltgen's avatar
Daniel Hiltgen committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
		}
	}
}

func UpdatePath(dir string) {
	if runtime.GOOS == "windows" {
		tmpDir := filepath.Dir(dir)
		pathComponents := strings.Split(os.Getenv("PATH"), ";")
		i := 0
		for _, comp := range pathComponents {
			if strings.EqualFold(comp, dir) {
				return
			}
			// Remove any other prior paths to our temp dir
			if !strings.HasPrefix(strings.ToLower(comp), strings.ToLower(tmpDir)) {
				pathComponents[i] = comp
				i++
			}
		}
		newPath := strings.Join(append([]string{dir}, pathComponents...), ";")
Daniel Hiltgen's avatar
Daniel Hiltgen committed
144
		slog.Info("updating", "PATH", newPath)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
145
146
147
148
		os.Setenv("PATH", newPath)
	}
	// linux and darwin rely on rpath
}