@@ -30,6 +30,7 @@ work, go to:
30
30
import (
31
31
"bytes"
32
32
"context"
33
+ "encoding/json"
33
34
"errors"
34
35
"fmt"
35
36
"io"
@@ -45,50 +46,110 @@ import (
45
46
"golang.org/x/build/buildlet"
46
47
"golang.org/x/build/dashboard"
47
48
"golang.org/x/build/revdial"
49
+ "golang.org/x/build/types"
48
50
)
49
51
50
52
const minBuildletVersion = 1
51
53
52
- var reversePool = & reverseBuildletPool {
53
- available : make (chan token , 1 ),
54
- }
54
+ var reversePool = new (reverseBuildletPool )
55
55
56
56
type token struct {}
57
57
58
58
type reverseBuildletPool struct {
59
- available chan token // best-effort tickle when any buildlet becomes free
60
-
61
- mu sync.Mutex // guards buildlets and their fields
59
+ mu sync.Mutex // guards all fields, including fields of *reverseBuildlet
60
+ // TODO: switch to a map[hostType][]buildlets or map of set.
62
61
buildlets []* reverseBuildlet
62
+ wakeChan map [string ]chan token // hostType => best-effort wake-up chan when buildlet free
63
+ waiters map [string ]int // hostType => number waiters blocked in GetBuildlet
64
+ }
65
+
66
+ func (p * reverseBuildletPool ) ServeReverseStatusJSON (w http.ResponseWriter , r * http.Request ) {
67
+ w .Header ().Set ("Content-Type" , "application/json" )
68
+ status := p .buildReverseStatusJSON ()
69
+ j , _ := json .MarshalIndent (status , "" , "\t " )
70
+ w .Write (j )
63
71
}
64
72
65
- var errInUse = errors .New ("all buildlets are in use" )
73
+ func (p * reverseBuildletPool ) buildReverseStatusJSON () * types.ReverseBuilderStatus {
74
+ status := & types.ReverseBuilderStatus {}
66
75
67
- func (p * reverseBuildletPool ) tryToGrab (hostType string ) (* buildlet.Client , error ) {
68
76
p .mu .Lock ()
69
77
defer p .mu .Unlock ()
70
- candidates := 0
71
78
for _ , b := range p .buildlets {
72
- isCandidate := b .hostType == hostType
73
- if isCandidate {
74
- candidates ++
79
+ hs := status .Host (b .hostType )
80
+ if hs .Machines == nil {
81
+ hs .Machines = make (map [string ]* types.ReverseBuilder )
82
+ }
83
+ hs .Connected ++
84
+ bs := & types.ReverseBuilder {
85
+ Name : b .hostname ,
86
+ HostType : b .hostType ,
87
+ ConnectedSec : time .Since (b .regTime ).Seconds (),
88
+ Version : b .version ,
89
+ }
90
+ if b .inUse && ! b .inHealthCheck {
91
+ hs .Busy ++
92
+ bs .Busy = true
93
+ bs .BusySec = time .Since (b .inUseTime ).Seconds ()
94
+ } else {
95
+ hs .Idle ++
96
+ bs .IdleSec = time .Since (b .inUseTime ).Seconds ()
75
97
}
76
- if isCandidate && ! b .inUse {
77
- // Found an unused match.
78
- b .inUse = true
79
- b .inUseTime = time .Now ()
80
- return b .client , nil
98
+
99
+ hs .Machines [b .hostname ] = bs
100
+ }
101
+ for hostType , waiters := range p .waiters {
102
+ status .Host (hostType ).Waiters = waiters
103
+ }
104
+ for hostType , hc := range dashboard .Hosts {
105
+ if hc .ExpectNum > 0 {
106
+ status .Host (hostType ).Expect = hc .ExpectNum
81
107
}
82
108
}
83
- if candidates == 0 {
84
- return nil , fmt .Errorf ("no buildlets registered for host type %q" , hostType )
109
+ return status
110
+ }
111
+
112
+ // tryToGrab returns non-nil bc on success if a buildlet is free.
113
+ //
114
+ // Otherwise it returns how many were busy, which might be 0 if none
115
+ // were (yet?) registered. The busy valid is only valid if bc == nil.
116
+ func (p * reverseBuildletPool ) tryToGrab (hostType string ) (bc * buildlet.Client , busy int ) {
117
+ p .mu .Lock ()
118
+ defer p .mu .Unlock ()
119
+ for _ , b := range p .buildlets {
120
+ if b .hostType != hostType {
121
+ continue
122
+ }
123
+ if b .inUse {
124
+ busy ++
125
+ continue
126
+ }
127
+ // Found an unused match.
128
+ b .inUse = true
129
+ b .inUseTime = time .Now ()
130
+ return b .client , 0
131
+ }
132
+ return nil , busy
133
+ }
134
+
135
+ func (p * reverseBuildletPool ) getWakeChan (hostType string ) chan token {
136
+ p .mu .Lock ()
137
+ defer p .mu .Unlock ()
138
+ if p .wakeChan == nil {
139
+ p .wakeChan = make (map [string ]chan token )
140
+ }
141
+ c , ok := p .wakeChan [hostType ]
142
+ if ! ok {
143
+ c = make (chan token )
144
+ p .wakeChan [hostType ] = c
85
145
}
86
- return nil , errInUse
146
+ return c
87
147
}
88
148
89
- func (p * reverseBuildletPool ) noteBuildletAvailable () {
149
+ func (p * reverseBuildletPool ) noteBuildletAvailable (hostType string ) {
150
+ wake := p .getWakeChan (hostType )
90
151
select {
91
- case p . available <- token {}:
152
+ case wake <- token {}:
92
153
default :
93
154
}
94
155
}
@@ -168,7 +229,7 @@ func (p *reverseBuildletPool) healthCheckBuildlet(b *reverseBuildlet) bool {
168
229
b .inUse = false
169
230
b .inHealthCheck = false
170
231
b .inUseTime = time .Now ()
171
- p .noteBuildletAvailable ()
232
+ go p .noteBuildletAvailable (b . hostType )
172
233
return true
173
234
}
174
235
@@ -188,45 +249,53 @@ func highPriChan(hostType string) chan *buildlet.Client {
188
249
return c
189
250
}
190
251
252
+ func (p * reverseBuildletPool ) updateWaiterCounter (hostType string , delta int ) {
253
+ p .mu .Lock ()
254
+ defer p .mu .Unlock ()
255
+ if p .waiters == nil {
256
+ p .waiters = make (map [string ]int )
257
+ }
258
+ p .waiters [hostType ] += delta
259
+ }
260
+
191
261
func (p * reverseBuildletPool ) GetBuildlet (ctx context.Context , hostType string , lg logger ) (* buildlet.Client , error ) {
262
+ p .updateWaiterCounter (hostType , 1 )
263
+ defer p .updateWaiterCounter (hostType , - 1 )
192
264
seenErrInUse := false
193
265
isHighPriority , _ := ctx .Value (highPriorityOpt {}).(bool )
194
266
sp := lg .createSpan ("wait_static_builder" , hostType )
195
267
for {
196
- b , err := p .tryToGrab (hostType )
197
- if err == errInUse {
198
- if ! seenErrInUse {
199
- lg .logEventTime ("waiting_machine_in_use" )
200
- seenErrInUse = true
201
- }
202
- var highPri chan * buildlet.Client
203
- if isHighPriority {
204
- highPri = highPriChan (hostType )
205
- }
268
+ bc , busy := p .tryToGrab (hostType )
269
+ if bc != nil {
206
270
select {
207
- case <- ctx . Done () :
208
- return nil , sp . done ( ctx . Err ())
209
- case bc := <- highPri :
271
+ case highPriChan ( hostType ) <- bc :
272
+ // Somebody else was more important.
273
+ default :
210
274
sp .done (nil )
211
275
return p .cleanedBuildlet (bc , lg )
276
+ }
277
+ }
278
+ if busy > 0 && ! seenErrInUse {
279
+ lg .logEventTime ("waiting_machine_in_use" )
280
+ seenErrInUse = true
281
+ }
282
+ var highPri chan * buildlet.Client
283
+ if isHighPriority {
284
+ highPri = highPriChan (hostType )
285
+ }
286
+ select {
287
+ case <- ctx .Done ():
288
+ return nil , sp .done (ctx .Err ())
289
+ case bc := <- highPri :
290
+ sp .done (nil )
291
+ return p .cleanedBuildlet (bc , lg )
292
+
293
+ case <- time .After (10 * time .Second ):
212
294
// As multiple goroutines can be listening for
213
295
// the available signal, it must be treated as
214
296
// a best effort signal. So periodically try
215
- // to grab a buildlet again:
216
- case <- time .After (10 * time .Second ):
217
- case <- p .available :
218
- }
219
- } else if err != nil {
220
- sp .done (err )
221
- return nil , err
222
- } else {
223
- select {
224
- case highPriChan (hostType ) <- b :
225
- // Somebody else was more important.
226
- default :
227
- sp .done (nil )
228
- return p .cleanedBuildlet (b , lg )
229
- }
297
+ // to grab a buildlet again.
298
+ case <- p .getWakeChan (hostType ):
230
299
}
231
300
}
232
301
}
@@ -329,7 +398,7 @@ func (p *reverseBuildletPool) CanBuild(hostType string) bool {
329
398
330
399
func (p * reverseBuildletPool ) addBuildlet (b * reverseBuildlet ) {
331
400
p .mu .Lock ()
332
- defer p .noteBuildletAvailable ()
401
+ defer p .noteBuildletAvailable (b . hostType )
333
402
defer p .mu .Unlock ()
334
403
p .buildlets = append (p .buildlets , b )
335
404
go p .healthCheckBuildletLoop (b )
0 commit comments