Skip to content

Commit f8d61eb

Browse files
author
Drew Macrae
committed
fixup Implement extra resources for test actions
Addressed comments on bazelbuild#13996 Fixed issues in tests and built and tested with lowRISC/opentitan#16436 Signed-off-by: Drew Macrae <[email protected]>
1 parent dd7f980 commit f8d61eb

File tree

8 files changed

+94
-43
lines changed

8 files changed

+94
-43
lines changed

src/main/java/com/google/devtools/build/lib/actions/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ java_library(
128128
"//third_party:flogger",
129129
"//third_party:guava",
130130
"//third_party:jsr305",
131-
"//third_party:rxjava3",
132131
"//third_party/protobuf:protobuf_java",
133132
],
134133
)
@@ -312,7 +311,6 @@ java_library(
312311
"//third_party:flogger",
313312
"//third_party:guava",
314313
"//third_party:jsr305",
315-
"//third_party:rxjava3",
316314
],
317315
)
318316

src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,27 +157,22 @@ public String parseIfMatches(String tag) throws ValidationException {
157157
/** How many extra resources an action requires for execution. */
158158
public static final ParseableRequirement RESOURCES =
159159
ParseableRequirement.create(
160-
"resources:<str>:<int>",
160+
"resources:<str>:<float>",
161161
Pattern.compile("resources:(.+:.+)"),
162162
s -> {
163163
Preconditions.checkNotNull(s);
164164

165165
int splitIndex = s.indexOf(":");
166166
String resourceCount = s.substring(splitIndex+1);
167-
int value;
167+
float value;
168168
try {
169-
value = Integer.parseInt(resourceCount);
169+
value = Float.parseFloat(resourceCount);
170170
} catch (NumberFormatException e) {
171-
return "can't be parsed as an integer";
172-
}
173-
174-
// De-and-reserialize & compare to only allow canonical integer formats.
175-
if (!Integer.toString(value).equals(resourceCount)) {
176-
return "must be in canonical format (e.g. '4' instead of '+04')";
171+
return "can't be parsed as a float";
177172
}
178173

179-
if (value < 1) {
180-
return "can't be zero or negative";
174+
if (value < 0) {
175+
return "can't be negative";
181176
}
182177

183178
return null;

src/main/java/com/google/devtools/build/lib/actions/ResourceManager.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Iterator;
3333
import java.util.LinkedList;
3434
import java.util.Map;
35+
import java.util.NoSuchElementException;
3536
import java.util.Set;
3637
import java.util.concurrent.CountDownLatch;
3738
import javax.annotation.Nullable;
@@ -380,7 +381,7 @@ public void acquireResourceOwnership() {
380381
* wait.
381382
*/
382383
private synchronized LatchWithWorker acquire(ResourceSet resources, ResourcePriority priority)
383-
throws IOException, InterruptedException {
384+
throws IOException, InterruptedException, NoSuchElementException {
384385
if (areResourcesAvailable(resources)) {
385386
Worker worker = incrementResources(resources);
386387
return new LatchWithWorker(/* latch= */ null, worker);
@@ -497,15 +498,28 @@ private synchronized void processWaitingThreads(Deque<Pair<ResourceSet, LatchWit
497498
}
498499
}
499500

501+
/**
502+
* Throws an exception if requested extra resource isn't being tracked
503+
*/
504+
private void assertExtraResourcesTracked(ResourceSet resources)
505+
throws NoSuchElementException {
506+
for (Map.Entry<String, Float> resource : resources.getExtraResourceUsage().entrySet()) {
507+
String key = (String)resource.getKey();
508+
if (!availableResources.getExtraResourceUsage().containsKey(key)) {
509+
throw new NoSuchElementException("Resource "+key+" is not tracked in this resource set.");
510+
}
511+
}
512+
}
513+
500514
/**
501515
* Return true iff all requested extra resources are considered to be available.
502516
*/
503-
private boolean areExtraResourcesAvailable(ResourceSet resources) {
517+
private boolean areExtraResourcesAvailable(ResourceSet resources) throws NoSuchElementException {
504518
for (Map.Entry<String, Float> resource : resources.getExtraResourceUsage().entrySet()) {
505519
String key = (String)resource.getKey();
506520
float used = (float)usedExtraResources.getOrDefault(key, 0f);
507521
float requested = resource.getValue();
508-
float available = (float)availableResources.getExtraResourceUsage().getOrDefault(key, 0f);
522+
float available = availableResources.getExtraResourceUsage().get(key);
509523
float epsilon = 0.0001f; // Account for possible rounding errors.
510524
if (requested != 0.0 && used != 0.0 && requested + used > available + epsilon) {
511525
return false;
@@ -516,7 +530,7 @@ private boolean areExtraResourcesAvailable(ResourceSet resources) {
516530

517531
// Method will return true if all requested resources are considered to be available.
518532
@VisibleForTesting
519-
boolean areResourcesAvailable(ResourceSet resources) {
533+
boolean areResourcesAvailable(ResourceSet resources) throws NoSuchElementException {
520534
Preconditions.checkNotNull(availableResources);
521535
// Comparison below is robust, since any calculation errors will be fixed
522536
// by the release() method.
@@ -532,6 +546,10 @@ boolean areResourcesAvailable(ResourceSet resources) {
532546
workerKey == null
533547
|| (activeWorkers < availableWorkers && workerPool.couldBeBorrowed(workerKey));
534548

549+
// We test for tracking of extra resources whenever acquired and throw an
550+
// exception before acquiring any untracked resource.
551+
assertExtraResourcesTracked(resources);
552+
535553
if (usedCpu == 0.0 && usedRam == 0.0 && usedExtraResources.isEmpty() && usedLocalTestCount == 0 && workerIsAvailable) {
536554
return true;
537555
}

src/main/java/com/google/devtools/build/lib/actions/ResourceSet.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.google.devtools.build.lib.worker.WorkerKey;
2424
import com.google.devtools.common.options.Converter;
2525
import com.google.devtools.common.options.OptionsParsingException;
26-
import io.reactivex.rxjava3.annotations.NonNull;
2726
import java.util.Iterator;
2827
import java.util.NoSuchElementException;
2928
import javax.annotation.Nullable;
@@ -58,11 +57,12 @@ public class ResourceSet implements ResourceSetOrBuilder {
5857
/** The workerKey of used worker. Null if no worker is used. */
5958
@Nullable private final WorkerKey workerKey;
6059

61-
private ResourceSet(double memoryMb, double cpuUsage, int localTestCount, @Nullable WorkerKey workerKey) {
60+
private ResourceSet(
61+
double memoryMb, double cpuUsage, int localTestCount, @Nullable WorkerKey workerKey) {
6262
this(memoryMb, cpuUsage, ImmutableMap.of(), localTestCount, workerKey);
6363
}
6464

65-
private ResourceSet(double memoryMb, double cpuUsage, @NonNull ImmutableMap<String, Float> extraResourceUsage, int localTestCount, @Nullable WorkerKey workerKey) {
65+
private ResourceSet(double memoryMb, double cpuUsage, @Nullable ImmutableMap<String, Float> extraResourceUsage, int localTestCount, @Nullable WorkerKey workerKey) {
6666
this.memoryMb = memoryMb;
6767
this.cpuUsage = cpuUsage;
6868
this.extraResourceUsage = extraResourceUsage;
@@ -102,7 +102,7 @@ public static ResourceSet createWithLocalTestCount(int localTestCount) {
102102
* represent available resources.
103103
*/
104104
public static ResourceSet create(double memoryMb, double cpuUsage, int localTestCount) {
105-
return ResourceSet.create(memoryMb, cpuUsage, ImmutableMap.of(), localTestCount, /* wolkerKey= */ null);
105+
return ResourceSet.createWithWorkerKey(memoryMb, cpuUsage, ImmutableMap.of(), localTestCount, /* wolkerKey= */ null);
106106
}
107107

108108
/**
@@ -112,7 +112,11 @@ public static ResourceSet create(double memoryMb, double cpuUsage, int localTest
112112
* ResourceSets that represent available resources.
113113
*/
114114
public static ResourceSet create(double memoryMb, double cpuUsage, ImmutableMap<String, Float> extraResourceUsage, int localTestCount) {
115-
return createWithWorkerKey(memoryMb, cpuUsage, extraResourceUseage, localTestCount, /* workerKey= */ null);
115+
return createWithWorkerKey(memoryMb, cpuUsage, extraResourceUsage, localTestCount, /* workerKey= */ null);
116+
}
117+
118+
public static ResourceSet createWithWorkerKey(double memoryMb, double cpuUsage, int localTestCount, WorkerKey workerKey) {
119+
return ResourceSet.createWithWorkerKey(memoryMb, cpuUsage, /* extraResourceUsage= */ImmutableMap.of(), localTestCount, workerKey);
116120
}
117121

118122
public static ResourceSet createWithWorkerKey(

src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,11 @@ public boolean shouldMaterializeParamFiles() {
334334
help =
335335
"Set the number of extra resources available to Bazel. "
336336
+ "Takes in a string-float pair. Can be used multiple times to specify multiple "
337-
+ "types of extra resources. Bazel will limit concurrently running test actions "
338-
+ "based on the available extra resources and the extra resources required "
339-
+ "by the test actions. Tests can declare the amount of extra resources they need "
337+
+ "types of extra resources. Bazel will limit concurrently running actions "
338+
+ "based on the available extra resources and the extra resources required. "
339+
+ "Tests can declare the amount of extra resources they need "
340340
+ "by using a tag of the \"resources:<resoucename>:<amount>\" format. "
341-
+ "Available CPU, RAM and test job resources cannot be set with this flag.",
341+
+ "Available CPU, RAM and resources cannot be set with this flag.",
342342
converter = Converters.StringToFloatAssignmentConverter.class)
343343
public List<Map.Entry<String, Float>> localExtraResources;
344344

src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ WorkResponse execInWorkerWorkerAsResource(
407407
ResourceSet.createWithWorkerKey(
408408
spawn.getLocalResources().getMemoryMb(),
409409
spawn.getLocalResources().getCpuUsage(),
410+
spawn.getLocalResources().getExtraResourceUsage(),
410411
spawn.getLocalResources().getLocalTestCount(),
411412
key);
412413

src/main/java/com/google/devtools/common/options/Converters.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ public String getTypeDescription() {
475475
/**
476476
* A converter for for assignments from a string value to a float value.
477477
*/
478-
public static class StringToFloatAssignmentConverter implements Converter<Map.Entry<String, Float>> {
478+
public static class StringToFloatAssignmentConverter extends Converter.Contextless<Map.Entry<String, Float>> {
479479
private static final AssignmentConverter baseConverter = new AssignmentConverter();
480480

481481
@Override

src/test/java/com/google/devtools/build/lib/actions/ResourceManagerTest.java

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.util.concurrent.CyclicBarrier;
4747
import java.util.concurrent.TimeUnit;
4848
import java.util.concurrent.atomic.AtomicInteger;
49+
import java.util.NoSuchElementException;
4950
import javax.annotation.Nullable;
5051
import org.apache.commons.pool2.PooledObject;
5152
import org.junit.Before;
@@ -114,21 +115,42 @@ private ResourceHandle acquire(double ram, double cpu, int tests)
114115
return acquire(ram, cpu, tests, ResourcePriority.LOCAL);
115116
}
116117

117-
private ResourceHandle acquire(double ram, double cpu, int tests, ImmutableMap<String, Float> extraResources, String mnemonic)
118+
private ResourceHandle acquire(double ram, double cpu, int tests, String mnemonic)
118119
throws InterruptedException, IOException {
119120

120121
return rm.acquireResources(
121122
resourceOwner,
122-
ResourceSet.createWithWorkerKey(ram, cpu, tests, extraResources, createWorkerKey(mnemonic)),
123+
ResourceSet.createWithWorkerKey(ram, cpu, tests, createWorkerKey(mnemonic)),
124+
ResourcePriority.LOCAL);
125+
}
126+
127+
private ResourceHandle acquire(double ram, double cpu, ImmutableMap<String, Float> extraResources, int tests, ResourcePriority priority)
128+
throws InterruptedException, IOException, NoSuchElementException {
129+
return rm.acquireResources(resourceOwner, ResourceSet.create(ram, cpu, extraResources, tests), priority);
130+
}
131+
132+
private ResourceHandle acquire(double ram, double cpu, ImmutableMap<String, Float> extraResources, int tests)
133+
throws InterruptedException, IOException, NoSuchElementException {
134+
return acquire(ram, cpu, extraResources, tests, ResourcePriority.LOCAL);
135+
}
136+
137+
private ResourceHandle acquire(double ram, double cpu, ImmutableMap<String, Float> extraResources, int tests, String mnemonic)
138+
throws InterruptedException, IOException, NoSuchElementException {
139+
140+
return rm.acquireResources(
141+
resourceOwner,
142+
ResourceSet.createWithWorkerKey(ram, cpu, extraResources, tests, createWorkerKey(mnemonic)),
123143
ResourcePriority.LOCAL);
124144
}
125145

126146
private void release(double ram, double cpu, int tests) throws IOException, InterruptedException {
127-
rm.releaseResources(resourceOwner, ResourceSet.create(ram, cpu, tests), /* worker=*/ null);
147+
rm.releaseResources(resourceOwner, ResourceSet.create(ram, cpu, tests), /* worker= */ null);
128148
}
129149

130-
private void release(double ram, double cpu, int tests, ImmutableMap<String, Float> extraResources) {
131-
rm.releaseResources(resourceOwner, ResourceSet.create(ram, cpu, extraResources, tests));
150+
private void release(double ram, double cpu, ImmutableMap<String, Float> extraResources, int tests)
151+
throws InterruptedException, IOException {
152+
rm.releaseResources(resourceOwner, ResourceSet.create(ram, cpu, extraResources, tests),
153+
/* worker= */ null);
132154
}
133155

134156
private void validate(int count) {
@@ -179,11 +201,11 @@ public void testOverBudgetRequests() throws Exception {
179201
release(0, 0, bigTests);
180202
assertThat(rm.inUse()).isFalse();
181203

182-
// Ditto, for extra resources (even if they don't exist in the available resource set):
183-
ImmutableMap<String, Float> bigExtraResources = ImmutableMap.of("gpu", 10.0f, "fancyresource", 10.0f, "nonexisting", 10.0f);
184-
acquire(0, 0, 0, bigExtraResources);
204+
// Ditto, for extra resources:
205+
ImmutableMap<String, Float> bigExtraResources = ImmutableMap.of("gpu", 10.0f, "fancyresource", 10.0f);
206+
acquire(0, 0, bigExtraResources, 0);
185207
assertThat(rm.inUse()).isTrue();
186-
release(0, 0, 0, bigExtraResources);
208+
release(0, 0, bigExtraResources, 0);
187209
assertThat(rm.inUse()).isFalse();
188210
}
189211

@@ -271,20 +293,21 @@ public void testThatExtraResourcesCannotBeOverallocated() throws Exception {
271293
assertThat(rm.inUse()).isFalse();
272294

273295
// Given a partially acquired extra resources:
274-
acquire(0, 0, 1, ImmutableMap.of("gpu", 1.0f));
296+
acquire(0, 0, ImmutableMap.of("gpu", 1.0f), 1);
275297

276298
// When a request for extra resources is made that would overallocate,
277299
// Then the request fails:
278-
TestThread thread1 = new TestThread(() -> assertThat(acquireNonblocking(0, 0, 0, ImmutableMap.of("gpu", 1.1f))).isNull());
300+
TestThread thread1 = new TestThread(() -> acquire(0, 0, ImmutableMap.of("gpu", 1.1f), 0));
279301
thread1.start();
280-
thread1.joinAndAssertState(10000);
302+
AssertionError e = assertThrows(AssertionError.class, () -> thread1.joinAndAssertState(1000));
303+
assertThat(e).hasCauseThat().hasMessageThat().contains("is still alive");
281304
}
282305

283306
@Test
284307
public void testHasResources() throws Exception {
285308
assertThat(rm.inUse()).isFalse();
286309
assertThat(rm.threadHasResources()).isFalse();
287-
acquire(1, 0.1, 1, ImmutableMap.of("gpu", 1.0f));
310+
acquire(1, 0.1, ImmutableMap.of("gpu", 1.0f), 1);
288311
assertThat(rm.threadHasResources()).isTrue();
289312

290313
// We have resources in this thread - make sure other threads
@@ -305,15 +328,15 @@ public void testHasResources() throws Exception {
305328
assertThat(rm.threadHasResources()).isTrue();
306329
release(0, 0, 1);
307330
assertThat(rm.threadHasResources()).isFalse();
308-
acquire(0, 0, 0, ImmutableMap.of("gpu", 1.0f));
331+
acquire(0, 0, ImmutableMap.of("gpu", 1.0f), 0);
309332
assertThat(rm.threadHasResources()).isTrue();
310-
release(0, 0, 0, ImmutableMap.of("gpu", 1.0f));
333+
release(0, 0, ImmutableMap.of("gpu", 1.0f), 0);
311334
assertThat(rm.threadHasResources()).isFalse();
312335
});
313336
thread1.start();
314337
thread1.joinAndAssertState(10000);
315338

316-
release(1, 0.1, 1, ImmutableMap.of("gpu", 1.0f));
339+
release(1, 0.1, ImmutableMap.of("gpu", 1.0f), 1);
317340
assertThat(rm.threadHasResources()).isFalse();
318341
assertThat(rm.inUse()).isFalse();
319342
}
@@ -704,6 +727,18 @@ private CyclicBarrier startAcquireReleaseThread(ResourcePriority priority) {
704727
return sync;
705728
}
706729

730+
@Test
731+
public void testNonexistingResource() throws Exception {
732+
// If we try to use nonexisting resource we should return an error
733+
TestThread thread1 =
734+
new TestThread(
735+
() -> {
736+
assertThrows(NoSuchElementException.class, () -> acquire(0, 0, ImmutableMap.of("nonexisting", 1.0f), 0));
737+
});
738+
thread1.start();
739+
thread1.joinAndAssertState(1000);
740+
}
741+
707742
@Test
708743
public void testAcquireWithWorker_acquireAndRelease() throws Exception {
709744
int memory = 100;

0 commit comments

Comments
 (0)