2727import com .google .devtools .build .lib .worker .WorkerPool ;
2828import java .io .IOException ;
2929import java .util .Deque ;
30+ import java .util .HashMap ;
31+ import java .util .HashSet ;
3032import java .util .Iterator ;
3133import java .util .LinkedList ;
34+ import java .util .Map ;
35+ import java .util .NoSuchElementException ;
36+ import java .util .Set ;
3237import java .util .concurrent .CountDownLatch ;
3338import javax .annotation .Nullable ;
3439
@@ -183,14 +188,16 @@ public double getUsedCPU() {
183188 // definition in the ResourceSet class.
184189 private double usedRam ;
185190
191+ // Used amount of extra resources. Corresponds to the extra resource
192+ // definition in the ResourceSet class.
193+ private Map <String , Float > usedExtraResources ;
194+
186195 // Used local test count. Corresponds to the local test count definition in the ResourceSet class.
187196 private int usedLocalTestCount ;
188197
189198 /** If set, local-only actions are given priority over dynamically run actions. */
190199 private boolean prioritizeLocalActions ;
191200
192- private ResourceManager () {}
193-
194201 @ VisibleForTesting
195202 public static ResourceManager instanceForTestingOnly () {
196203 return new ResourceManager ();
@@ -204,6 +211,7 @@ public static ResourceManager instanceForTestingOnly() {
204211 public synchronized void resetResourceUsage () {
205212 usedCpu = 0 ;
206213 usedRam = 0 ;
214+ usedExtraResources = new HashMap <>();
207215 usedLocalTestCount = 0 ;
208216 for (Pair <ResourceSet , LatchWithWorker > request : localRequests ) {
209217 request .second .latch .countDown ();
@@ -298,6 +306,20 @@ private Worker incrementResources(ResourceSet resources)
298306 throws IOException , InterruptedException {
299307 usedCpu += resources .getCpuUsage ();
300308 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+
301323 usedLocalTestCount += resources .getLocalTestCount ();
302324
303325 if (resources .getWorkerKey () != null ) {
@@ -310,6 +332,7 @@ private Worker incrementResources(ResourceSet resources)
310332 public synchronized boolean inUse () {
311333 return usedCpu != 0.0
312334 || usedRam != 0.0
335+ || !usedExtraResources .isEmpty ()
313336 || usedLocalTestCount != 0
314337 || !localRequests .isEmpty ()
315338 || !dynamicWorkerRequests .isEmpty ()
@@ -369,7 +392,7 @@ public void acquireResourceOwnership() {
369392 * wait.
370393 */
371394 private synchronized LatchWithWorker acquire (ResourceSet resources , ResourcePriority priority )
372- throws IOException , InterruptedException {
395+ throws IOException , InterruptedException , NoSuchElementException {
373396 if (areResourcesAvailable (resources )) {
374397 Worker worker = incrementResources (resources );
375398 return new LatchWithWorker (/* latch= */ null , worker );
@@ -417,6 +440,7 @@ private boolean release(ResourceSet resources, @Nullable Worker worker)
417440 private synchronized void releaseResourcesOnly (ResourceSet resources ) {
418441 usedCpu -= resources .getCpuUsage ();
419442 usedRam -= resources .getMemoryMb ();
443+
420444 usedLocalTestCount -= resources .getLocalTestCount ();
421445
422446 // 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) {
428452 if (usedRam < epsilon ) {
429453 usedRam = 0 ;
430454 }
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+ }
431468 }
432469
433470 private synchronized boolean processAllWaitingThreads () throws IOException , InterruptedException {
@@ -466,9 +503,35 @@ private synchronized void processWaitingThreads(Deque<Pair<ResourceSet, LatchWit
466503 }
467504 }
468505
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+
469532 // Method will return true if all requested resources are considered to be available.
470533 @ VisibleForTesting
471- boolean areResourcesAvailable (ResourceSet resources ) {
534+ boolean areResourcesAvailable (ResourceSet resources ) throws NoSuchElementException {
472535 Preconditions .checkNotNull (availableResources );
473536 // Comparison below is robust, since any calculation errors will be fixed
474537 // by the release() method.
@@ -484,7 +547,15 @@ boolean areResourcesAvailable(ResourceSet resources) {
484547 workerKey == null
485548 || (activeWorkers < availableWorkers && workerPool .couldBeBorrowed (workerKey ));
486549
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 ) {
488559 return true ;
489560 }
490561 // Use only MIN_NECESSARY_???_RATIO of the resource value to check for
@@ -515,7 +586,12 @@ boolean areResourcesAvailable(ResourceSet resources) {
515586 localTestCount == 0
516587 || usedLocalTestCount == 0
517588 || 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 ;
519595 }
520596
521597 @ VisibleForTesting
0 commit comments