27
27
import com .google .devtools .build .lib .worker .WorkerPool ;
28
28
import java .io .IOException ;
29
29
import java .util .Deque ;
30
+ import java .util .HashMap ;
31
+ import java .util .HashSet ;
30
32
import java .util .Iterator ;
31
33
import java .util .LinkedList ;
34
+ import java .util .Map ;
35
+ import java .util .NoSuchElementException ;
36
+ import java .util .Set ;
32
37
import java .util .concurrent .CountDownLatch ;
33
38
import javax .annotation .Nullable ;
34
39
@@ -171,14 +176,16 @@ public static ResourceManager instance() {
171
176
// definition in the ResourceSet class.
172
177
private double usedRam ;
173
178
179
+ // Used amount of extra resources. Corresponds to the extra resource
180
+ // definition in the ResourceSet class.
181
+ private Map <String , Float > usedExtraResources ;
182
+
174
183
// Used local test count. Corresponds to the local test count definition in the ResourceSet class.
175
184
private int usedLocalTestCount ;
176
185
177
186
/** If set, local-only actions are given priority over dynamically run actions. */
178
187
private boolean prioritizeLocalActions ;
179
188
180
- private ResourceManager () {}
181
-
182
189
@ VisibleForTesting
183
190
public static ResourceManager instanceForTestingOnly () {
184
191
return new ResourceManager ();
@@ -192,6 +199,7 @@ public static ResourceManager instanceForTestingOnly() {
192
199
public synchronized void resetResourceUsage () {
193
200
usedCpu = 0 ;
194
201
usedRam = 0 ;
202
+ usedExtraResources = new HashMap <>();
195
203
usedLocalTestCount = 0 ;
196
204
for (Pair <ResourceSet , LatchWithWorker > request : localRequests ) {
197
205
request .second .latch .countDown ();
@@ -286,6 +294,20 @@ private Worker incrementResources(ResourceSet resources)
286
294
throws IOException , InterruptedException {
287
295
usedCpu += resources .getCpuUsage ();
288
296
usedRam += resources .getMemoryMb ();
297
+
298
+ resources
299
+ .getExtraResourceUsage ()
300
+ .entrySet ()
301
+ .forEach (
302
+ resource -> {
303
+ String key = (String ) resource .getKey ();
304
+ float value = resource .getValue ();
305
+ if (usedExtraResources .containsKey (key )) {
306
+ value += (float ) usedExtraResources .get (key );
307
+ }
308
+ usedExtraResources .put (key , value );
309
+ });
310
+
289
311
usedLocalTestCount += resources .getLocalTestCount ();
290
312
291
313
if (resources .getWorkerKey () != null ) {
@@ -298,6 +320,7 @@ private Worker incrementResources(ResourceSet resources)
298
320
public synchronized boolean inUse () {
299
321
return usedCpu != 0.0
300
322
|| usedRam != 0.0
323
+ || !usedExtraResources .isEmpty ()
301
324
|| usedLocalTestCount != 0
302
325
|| !localRequests .isEmpty ()
303
326
|| !dynamicWorkerRequests .isEmpty ()
@@ -357,7 +380,7 @@ public void acquireResourceOwnership() {
357
380
* wait.
358
381
*/
359
382
private synchronized LatchWithWorker acquire (ResourceSet resources , ResourcePriority priority )
360
- throws IOException , InterruptedException {
383
+ throws IOException , InterruptedException , NoSuchElementException {
361
384
if (areResourcesAvailable (resources )) {
362
385
Worker worker = incrementResources (resources );
363
386
return new LatchWithWorker (/* latch= */ null , worker );
@@ -405,6 +428,7 @@ private boolean release(ResourceSet resources, @Nullable Worker worker)
405
428
private synchronized void releaseResourcesOnly (ResourceSet resources ) {
406
429
usedCpu -= resources .getCpuUsage ();
407
430
usedRam -= resources .getMemoryMb ();
431
+
408
432
usedLocalTestCount -= resources .getLocalTestCount ();
409
433
410
434
// TODO(bazel-team): (2010) rounding error can accumulate and value below can end up being
@@ -416,6 +440,19 @@ private synchronized void releaseResourcesOnly(ResourceSet resources) {
416
440
if (usedRam < epsilon ) {
417
441
usedRam = 0 ;
418
442
}
443
+
444
+ Set <String > toRemove = new HashSet <>();
445
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
446
+ String key = (String ) resource .getKey ();
447
+ float value = (float ) usedExtraResources .get (key ) - resource .getValue ();
448
+ usedExtraResources .put (key , value );
449
+ if (value < epsilon ) {
450
+ toRemove .add (key );
451
+ }
452
+ }
453
+ for (String key : toRemove ) {
454
+ usedExtraResources .remove (key );
455
+ }
419
456
}
420
457
421
458
private synchronized boolean processAllWaitingThreads () throws IOException , InterruptedException {
@@ -454,9 +491,35 @@ private synchronized void processWaitingThreads(Deque<Pair<ResourceSet, LatchWit
454
491
}
455
492
}
456
493
494
+ /** Throws an exception if requested extra resource isn't being tracked */
495
+ private void assertExtraResourcesTracked (ResourceSet resources ) throws NoSuchElementException {
496
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
497
+ String key = (String ) resource .getKey ();
498
+ if (!availableResources .getExtraResourceUsage ().containsKey (key )) {
499
+ throw new NoSuchElementException (
500
+ "Resource " + key + " is not tracked in this resource set." );
501
+ }
502
+ }
503
+ }
504
+
505
+ /** Return true iff all requested extra resources are considered to be available. */
506
+ private boolean areExtraResourcesAvailable (ResourceSet resources ) throws NoSuchElementException {
507
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
508
+ String key = (String ) resource .getKey ();
509
+ float used = (float ) usedExtraResources .getOrDefault (key , 0f );
510
+ float requested = resource .getValue ();
511
+ float available = availableResources .getExtraResourceUsage ().get (key );
512
+ float epsilon = 0.0001f ; // Account for possible rounding errors.
513
+ if (requested != 0.0 && used != 0.0 && requested + used > available + epsilon ) {
514
+ return false ;
515
+ }
516
+ }
517
+ return true ;
518
+ }
519
+
457
520
// Method will return true if all requested resources are considered to be available.
458
521
@ VisibleForTesting
459
- boolean areResourcesAvailable (ResourceSet resources ) {
522
+ boolean areResourcesAvailable (ResourceSet resources ) throws NoSuchElementException {
460
523
Preconditions .checkNotNull (availableResources );
461
524
// Comparison below is robust, since any calculation errors will be fixed
462
525
// by the release() method.
@@ -472,7 +535,15 @@ boolean areResourcesAvailable(ResourceSet resources) {
472
535
workerKey == null
473
536
|| (activeWorkers < availableWorkers && workerPool .couldBeBorrowed (workerKey ));
474
537
475
- if (usedCpu == 0.0 && usedRam == 0.0 && usedLocalTestCount == 0 && workerIsAvailable ) {
538
+ // We test for tracking of extra resources whenever acquired and throw an
539
+ // exception before acquiring any untracked resource.
540
+ assertExtraResourcesTracked (resources );
541
+
542
+ if (usedCpu == 0.0
543
+ && usedRam == 0.0
544
+ && usedExtraResources .isEmpty ()
545
+ && usedLocalTestCount == 0
546
+ && workerIsAvailable ) {
476
547
return true ;
477
548
}
478
549
// Use only MIN_NECESSARY_???_RATIO of the resource value to check for
@@ -503,7 +574,12 @@ boolean areResourcesAvailable(ResourceSet resources) {
503
574
localTestCount == 0
504
575
|| usedLocalTestCount == 0
505
576
|| usedLocalTestCount + localTestCount <= availableLocalTestCount ;
506
- return cpuIsAvailable && ramIsAvailable && localTestCountIsAvailable && workerIsAvailable ;
577
+ boolean extraResourcesIsAvailable = areExtraResourcesAvailable (resources );
578
+ return cpuIsAvailable
579
+ && ramIsAvailable
580
+ && extraResourcesIsAvailable
581
+ && localTestCountIsAvailable
582
+ && workerIsAvailable ;
507
583
}
508
584
509
585
@ VisibleForTesting
0 commit comments