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
@@ -183,14 +188,16 @@ public double getUsedCPU() {
183
188
// definition in the ResourceSet class.
184
189
private double usedRam ;
185
190
191
+ // Used amount of extra resources. Corresponds to the extra resource
192
+ // definition in the ResourceSet class.
193
+ private Map <String , Float > usedExtraResources ;
194
+
186
195
// Used local test count. Corresponds to the local test count definition in the ResourceSet class.
187
196
private int usedLocalTestCount ;
188
197
189
198
/** If set, local-only actions are given priority over dynamically run actions. */
190
199
private boolean prioritizeLocalActions ;
191
200
192
- private ResourceManager () {}
193
-
194
201
@ VisibleForTesting
195
202
public static ResourceManager instanceForTestingOnly () {
196
203
return new ResourceManager ();
@@ -204,6 +211,7 @@ public static ResourceManager instanceForTestingOnly() {
204
211
public synchronized void resetResourceUsage () {
205
212
usedCpu = 0 ;
206
213
usedRam = 0 ;
214
+ usedExtraResources = new HashMap <>();
207
215
usedLocalTestCount = 0 ;
208
216
for (Pair <ResourceSet , LatchWithWorker > request : localRequests ) {
209
217
request .second .latch .countDown ();
@@ -298,6 +306,20 @@ private Worker incrementResources(ResourceSet resources)
298
306
throws IOException , InterruptedException {
299
307
usedCpu += resources .getCpuUsage ();
300
308
usedRam += resources .getMemoryMb ();
309
+
310
+ resources
311
+ .getExtraResourceUsage ()
312
+ .entrySet ()
313
+ .forEach (
314
+ resource -> {
315
+ String key = (String ) resource .getKey ();
316
+ float value = resource .getValue ();
317
+ if (usedExtraResources .containsKey (key )) {
318
+ value += (float ) usedExtraResources .get (key );
319
+ }
320
+ usedExtraResources .put (key , value );
321
+ });
322
+
301
323
usedLocalTestCount += resources .getLocalTestCount ();
302
324
303
325
if (resources .getWorkerKey () != null ) {
@@ -310,6 +332,7 @@ private Worker incrementResources(ResourceSet resources)
310
332
public synchronized boolean inUse () {
311
333
return usedCpu != 0.0
312
334
|| usedRam != 0.0
335
+ || !usedExtraResources .isEmpty ()
313
336
|| usedLocalTestCount != 0
314
337
|| !localRequests .isEmpty ()
315
338
|| !dynamicWorkerRequests .isEmpty ()
@@ -369,7 +392,7 @@ public void acquireResourceOwnership() {
369
392
* wait.
370
393
*/
371
394
private synchronized LatchWithWorker acquire (ResourceSet resources , ResourcePriority priority )
372
- throws IOException , InterruptedException {
395
+ throws IOException , InterruptedException , NoSuchElementException {
373
396
if (areResourcesAvailable (resources )) {
374
397
Worker worker = incrementResources (resources );
375
398
return new LatchWithWorker (/* latch= */ null , worker );
@@ -417,6 +440,7 @@ private boolean release(ResourceSet resources, @Nullable Worker worker)
417
440
private synchronized void releaseResourcesOnly (ResourceSet resources ) {
418
441
usedCpu -= resources .getCpuUsage ();
419
442
usedRam -= resources .getMemoryMb ();
443
+
420
444
usedLocalTestCount -= resources .getLocalTestCount ();
421
445
422
446
// TODO(bazel-team): (2010) rounding error can accumulate and value below can end up being
@@ -428,6 +452,19 @@ private synchronized void releaseResourcesOnly(ResourceSet resources) {
428
452
if (usedRam < epsilon ) {
429
453
usedRam = 0 ;
430
454
}
455
+
456
+ Set <String > toRemove = new HashSet <>();
457
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
458
+ String key = (String ) resource .getKey ();
459
+ float value = (float ) usedExtraResources .get (key ) - resource .getValue ();
460
+ usedExtraResources .put (key , value );
461
+ if (value < epsilon ) {
462
+ toRemove .add (key );
463
+ }
464
+ }
465
+ for (String key : toRemove ) {
466
+ usedExtraResources .remove (key );
467
+ }
431
468
}
432
469
433
470
private synchronized boolean processAllWaitingThreads () throws IOException , InterruptedException {
@@ -466,9 +503,35 @@ private synchronized void processWaitingThreads(Deque<Pair<ResourceSet, LatchWit
466
503
}
467
504
}
468
505
506
+ /** Throws an exception if requested extra resource isn't being tracked */
507
+ private void assertExtraResourcesTracked (ResourceSet resources ) throws NoSuchElementException {
508
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
509
+ String key = (String ) resource .getKey ();
510
+ if (!availableResources .getExtraResourceUsage ().containsKey (key )) {
511
+ throw new NoSuchElementException (
512
+ "Resource " + key + " is not tracked in this resource set." );
513
+ }
514
+ }
515
+ }
516
+
517
+ /** Return true iff all requested extra resources are considered to be available. */
518
+ private boolean areExtraResourcesAvailable (ResourceSet resources ) throws NoSuchElementException {
519
+ for (Map .Entry <String , Float > resource : resources .getExtraResourceUsage ().entrySet ()) {
520
+ String key = (String ) resource .getKey ();
521
+ float used = (float ) usedExtraResources .getOrDefault (key , 0f );
522
+ float requested = resource .getValue ();
523
+ float available = availableResources .getExtraResourceUsage ().get (key );
524
+ float epsilon = 0.0001f ; // Account for possible rounding errors.
525
+ if (requested != 0.0 && used != 0.0 && requested + used > available + epsilon ) {
526
+ return false ;
527
+ }
528
+ }
529
+ return true ;
530
+ }
531
+
469
532
// Method will return true if all requested resources are considered to be available.
470
533
@ VisibleForTesting
471
- boolean areResourcesAvailable (ResourceSet resources ) {
534
+ boolean areResourcesAvailable (ResourceSet resources ) throws NoSuchElementException {
472
535
Preconditions .checkNotNull (availableResources );
473
536
// Comparison below is robust, since any calculation errors will be fixed
474
537
// by the release() method.
@@ -484,7 +547,15 @@ boolean areResourcesAvailable(ResourceSet resources) {
484
547
workerKey == null
485
548
|| (activeWorkers < availableWorkers && workerPool .couldBeBorrowed (workerKey ));
486
549
487
- if (usedCpu == 0.0 && usedRam == 0.0 && usedLocalTestCount == 0 && workerIsAvailable ) {
550
+ // We test for tracking of extra resources whenever acquired and throw an
551
+ // exception before acquiring any untracked resource.
552
+ assertExtraResourcesTracked (resources );
553
+
554
+ if (usedCpu == 0.0
555
+ && usedRam == 0.0
556
+ && usedExtraResources .isEmpty ()
557
+ && usedLocalTestCount == 0
558
+ && workerIsAvailable ) {
488
559
return true ;
489
560
}
490
561
// Use only MIN_NECESSARY_???_RATIO of the resource value to check for
@@ -515,7 +586,12 @@ boolean areResourcesAvailable(ResourceSet resources) {
515
586
localTestCount == 0
516
587
|| usedLocalTestCount == 0
517
588
|| usedLocalTestCount + localTestCount <= availableLocalTestCount ;
518
- return cpuIsAvailable && ramIsAvailable && localTestCountIsAvailable && workerIsAvailable ;
589
+ boolean extraResourcesIsAvailable = areExtraResourcesAvailable (resources );
590
+ return cpuIsAvailable
591
+ && ramIsAvailable
592
+ && extraResourcesIsAvailable
593
+ && localTestCountIsAvailable
594
+ && workerIsAvailable ;
519
595
}
520
596
521
597
@ VisibleForTesting
0 commit comments