@@ -10,6 +10,7 @@ import (
10
10
"strings"
11
11
"syscall"
12
12
13
+ "golang.org/x/sys/unix"
13
14
"golang.org/x/xerrors"
14
15
)
15
16
@@ -28,32 +29,37 @@ func CLI(args []string, environ []string) error {
28
29
return xerrors .Errorf ("malformed command %q" , args )
29
30
}
30
31
32
+ // Slice off 'coder agent-exec'
31
33
args = args [2 :]
32
34
33
35
pid := os .Getpid ()
34
36
35
- oomScore , ok := envVal (environ , EnvProcOOMScore )
37
+ var err error
38
+ nice , ok := envValInt (environ , EnvProcNiceScore )
36
39
if ! ok {
37
- return xerrors .Errorf ("missing %q" , EnvProcOOMScore )
40
+ // If an explicit nice score isn't set, we use the default.
41
+ nice , err = defaultNiceScore ()
42
+ if err != nil {
43
+ return xerrors .Errorf ("get default nice score: %w" , err )
44
+ }
45
+ fmt .Println ("nice score" , nice , "pid" , pid )
38
46
}
39
47
40
- niceScore , ok := envVal (environ , EnvProcNiceScore )
48
+ oomscore , ok := envValInt (environ , EnvProcOOMScore )
41
49
if ! ok {
42
- return xerrors .Errorf ("missing %q" , EnvProcNiceScore )
43
- }
44
-
45
- score , err := strconv .Atoi (niceScore )
46
- if err != nil {
47
- return xerrors .Errorf ("invalid nice score: %w" , err )
50
+ // If an explicit oom score isn't set, we use the default.
51
+ oomscore , err = defaultOOMScore ()
52
+ if err != nil {
53
+ return xerrors .Errorf ("get default oom score: %w" , err )
54
+ }
48
55
}
49
56
50
- err = syscall .Setpriority (syscall .PRIO_PROCESS , pid , score )
57
+ err = unix .Setpriority (unix .PRIO_PROCESS , pid , nice )
51
58
if err != nil {
52
59
return xerrors .Errorf ("set nice score: %w" , err )
53
60
}
54
61
55
- oomPath := fmt .Sprintf ("/proc/%d/oom_score_adj" , pid )
56
- err = os .WriteFile (oomPath , []byte (oomScore ), 0o600 )
62
+ err = writeOOMScoreAdj (pid , oomscore )
57
63
if err != nil {
58
64
return xerrors .Errorf ("set oom score: %w" , err )
59
65
}
@@ -63,17 +69,86 @@ func CLI(args []string, environ []string) error {
63
69
return xerrors .Errorf ("look path: %w" , err )
64
70
}
65
71
72
+ // Remove environments variables specifically set for the agent-exec command.
66
73
env := slices .DeleteFunc (environ , func (env string ) bool {
67
74
return strings .HasPrefix (env , EnvProcOOMScore ) || strings .HasPrefix (env , EnvProcNiceScore )
68
75
})
69
76
70
77
return syscall .Exec (path , args , env )
71
78
}
72
79
73
- func envVal (environ []string , key string ) (string , bool ) {
74
- for _ , env := range environ {
75
- if strings .HasPrefix (env , key + "=" ) {
76
- return strings .TrimPrefix (env , key + "=" ), true
80
+ func defaultNiceScore () (int , error ) {
81
+ score , err := unix .Getpriority (unix .PRIO_PROCESS , os .Getpid ())
82
+ if err != nil {
83
+ return 0 , xerrors .Errorf ("get nice score: %w" , err )
84
+ }
85
+ // Priority is niceness + 20.
86
+ score -= 20
87
+
88
+ score += 5
89
+ if score > 19 {
90
+ return 19 , nil
91
+ }
92
+ return score , nil
93
+ }
94
+
95
+ func defaultOOMScore () (int , error ) {
96
+ score , err := oomScoreAdj (os .Getpid ())
97
+ if err != nil {
98
+ return 0 , xerrors .Errorf ("get oom score: %w" , err )
99
+ }
100
+
101
+ // If the agent has a negative oom_score_adj, we set the child to 0
102
+ // so it's treated like every other process.
103
+ if score < 0 {
104
+ return 0 , nil
105
+ }
106
+
107
+ // If the agent is already almost at the maximum then set it to the max.
108
+ if score >= 998 {
109
+ return 1000 , nil
110
+ }
111
+
112
+ // If the agent oom_score_adj is >=0, we set the child to slightly
113
+ // less than the maximum. If users want a different score they set it
114
+ // directly.
115
+ return 998 , nil
116
+ }
117
+
118
+ func oomScoreAdj (pid int ) (int , error ) {
119
+ scoreStr , err := os .ReadFile (fmt .Sprintf ("/proc/%d/oom_score_adj" , pid ))
120
+ if err != nil {
121
+ return 0 , xerrors .Errorf ("read oom_score_adj: %w" , err )
122
+ }
123
+ return strconv .Atoi (strings .TrimSpace (string (scoreStr )))
124
+ }
125
+
126
+ func writeOOMScoreAdj (pid int , score int ) error {
127
+ return os .WriteFile (fmt .Sprintf ("/proc/%d/oom_score_adj" , pid ), []byte (fmt .Sprintf ("%d" , score )), 0o600 )
128
+ }
129
+
130
+ // envValInt searches for a key in a list of environment variables and parses it to an int.
131
+ // If the key is not found or cannot be parsed, returns 0 and false.
132
+ func envValInt (env []string , key string ) (int , bool ) {
133
+ val , ok := envVal (env , key )
134
+ if ! ok {
135
+ return 0 , false
136
+ }
137
+
138
+ i , err := strconv .Atoi (val )
139
+ if err != nil {
140
+ return 0 , false
141
+ }
142
+ return i , true
143
+ }
144
+
145
+ // envVal searches for a key in a list of environment variables and returns its value.
146
+ // If the key is not found, returns empty string and false.
147
+ func envVal (env []string , key string ) (string , bool ) {
148
+ prefix := key + "="
149
+ for _ , e := range env {
150
+ if strings .HasPrefix (e , prefix ) {
151
+ return strings .TrimPrefix (e , prefix ), true
77
152
}
78
153
}
79
154
return "" , false
0 commit comments