Skip to content

Commit e22c7da

Browse files
committed
Add support for timeout:<int> tags to populate ExecutionInfo
Timeout may either be specified as a tag on format tags = ["tag:<int-seconds>"] or as execution_requirements on any action on dict format execution_requirements = { "timeout": "<int-seconds>" } Change-Id: Ida3bf173a291e7d949046bc4df3d1d4a02d8328d
1 parent eea808c commit e22c7da

File tree

6 files changed

+107
-14
lines changed

6 files changed

+107
-14
lines changed

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,31 @@ public String parseIfMatches(String tag) throws ValidationException {
118118
}
119119

120120
/** If specified, the timeout of this action in seconds. Must be decimal integer. */
121-
public static final String TIMEOUT = "timeout";
121+
public static final ParseableRequirement TIMEOUT =
122+
ParseableRequirement.create(
123+
"timeout:<int>",
124+
Pattern.compile("timeout:(.+)"),
125+
s -> {
126+
Preconditions.checkNotNull(s);
127+
128+
int value;
129+
try {
130+
value = Integer.parseInt(s);
131+
} catch (NumberFormatException e) {
132+
return "can't be parsed as an integer";
133+
}
134+
135+
// De-and-reserialize & compare to only allow canonical integer formats.
136+
if (!Integer.toString(value).equals(s)) {
137+
return "must be in canonical format (e.g. '4' instead of '+04')";
138+
}
139+
140+
if (value < 1) {
141+
return "can't be zero or negative";
142+
}
143+
144+
return null;
145+
});
122146

123147
/** If an action would not successfully run other than on Darwin. */
124148
public static final String REQUIRES_DARWIN = "requires-darwin";

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

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414

1515
package com.google.devtools.build.lib.actions;
1616

17+
import com.google.devtools.build.lib.actions.ExecutionRequirements.ParseableRequirement.ValidationException;
18+
import com.google.devtools.build.lib.actions.ExecutionRequirements.ParseableRequirement;
1719
import com.google.devtools.build.lib.server.FailureDetails;
1820
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
1921
import com.google.devtools.build.lib.server.FailureDetails.Spawn.Code;
22+
2023
import java.io.IOException;
2124
import java.time.Duration;
2225
import java.util.Map;
@@ -152,21 +155,72 @@ public static Duration getTimeout(Spawn spawn) throws ExecException {
152155
* defaultTimeout, or {@code Duration.ZERO} if that is null.
153156
*/
154157
public static Duration getTimeout(Spawn spawn, Duration defaultTimeout) throws ExecException {
155-
String timeoutStr = spawn.getExecutionInfo().get(ExecutionRequirements.TIMEOUT);
156-
if (timeoutStr == null) {
157-
return defaultTimeout == null ? Duration.ZERO : defaultTimeout;
158+
Duration timeout = null;
159+
160+
for (Map.Entry<String, String> entry: spawn.getExecutionInfo().entrySet()) {
161+
String value = getTimeoutValueFromExecutionInfoKeyOrValue(spawn,entry);
162+
if (value != null) {
163+
if (timeout != null) {
164+
throw new UserExecException(
165+
FailureDetail.newBuilder()
166+
.setMessage("multiple timeout values specified")
167+
.setSpawn(FailureDetails.Spawn.newBuilder().setCode(Code.DUPLICATE_TIMEOUT_TAGS))
168+
.build());
169+
}
170+
timeout = parseTimeoutValue(value);
171+
}
172+
}
173+
174+
if (timeout == null) {
175+
timeout = defaultTimeout == null ? Duration.ZERO : defaultTimeout;
158176
}
177+
return timeout;
178+
}
179+
180+
/**
181+
* Retrieves the timeout value from the execution info key or value.
182+
* Timeout may either be specified as a tag on format timeout:<int>
183+
* or as a key-value pair when specified as an execution_requirement.
184+
*/
185+
private static String getTimeoutValueFromExecutionInfoKeyOrValue(Spawn spawn, Map.Entry<String, String> entry) throws ExecException {
186+
ParseableRequirement requirement = ExecutionRequirements.TIMEOUT;
187+
String value = null;
188+
189+
if (entry.getKey().equals("timeout")) {
190+
value = entry.getValue();
191+
} else {
192+
try {
193+
value = requirement.parseIfMatches(entry.getKey());
194+
} catch (ValidationException e) {
195+
throw new UserExecException(
196+
e,
197+
FailureDetail.newBuilder()
198+
.setMessage(
199+
String.format(
200+
"%s has a '%s' tag, but its value '%s' didn't pass validation: %s",
201+
spawn.getTargetLabel(), requirement.userFriendlyName(), e.getTagValue(), e.getMessage()))
202+
.setSpawn(FailureDetails.Spawn.newBuilder().setCode(Code.INVALID_TIMEOUT_TAG))
203+
.build());
204+
}
205+
}
206+
return value;
207+
}
208+
209+
/**
210+
* Parses a timeout value from a string and returns it as a {@link Duration}.
211+
*/
212+
private static Duration parseTimeoutValue(String value) throws ExecException {
159213
try {
160-
return Duration.ofSeconds(Integer.parseInt(timeoutStr));
214+
return Duration.ofSeconds(Integer.parseInt(value));
161215
} catch (NumberFormatException e) {
162-
throw new UserExecException(
163-
e,
164-
FailureDetail.newBuilder()
165-
.setMessage("could not parse timeout")
166-
.setSpawn(FailureDetails.Spawn.newBuilder().setCode(Code.INVALID_TIMEOUT))
167-
.build());
216+
throw new UserExecException(
217+
e,
218+
FailureDetail.newBuilder()
219+
.setMessage("could not parse timeout")
220+
.setSpawn(FailureDetails.Spawn.newBuilder().setCode(Code.INVALID_TIMEOUT))
221+
.build());
168222
}
169-
}
223+
}
170224

171225
/**
172226
* Returns whether a local {@link Spawn} runner implementation should prefetch the inputs before

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public TestRunnerSpawn createTestRunnerSpawn(
119119
// for this.
120120
executionInfo.put(ExecutionRequirements.NO_CACHE, "");
121121
}
122-
executionInfo.put(ExecutionRequirements.TIMEOUT, "" + getTimeout(action).toSeconds());
122+
executionInfo.put("timeout", "" + getTimeout(action).toSeconds());
123123

124124
SimpleSpawn.LocalResourcesSupplier localResourcesSupplier =
125125
() ->

src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ private static boolean legalExecInfoKeys(String tag) {
5858
|| tag.startsWith("disable-")
5959
|| tag.startsWith("cpu:")
6060
|| tag.equals(ExecutionRequirements.LOCAL)
61-
|| tag.equals(ExecutionRequirements.TIMEOUT)
61+
|| tag.startsWith("timeout")
6262
|| tag.equals(ExecutionRequirements.WORKER_KEY_MNEMONIC)
6363
|| tag.startsWith("resources:");
6464
}

src/main/protobuf/failure_details.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ message Spawn {
222222
COMMAND_LINE_EXPANSION_FAILURE = 7 [(metadata) = { exit_code: 1 }];
223223
EXEC_IO_EXCEPTION = 8 [(metadata) = { exit_code: 36 }];
224224
INVALID_TIMEOUT = 9 [(metadata) = { exit_code: 1 }];
225+
DUPLICATE_TIMEOUT_TAGS = 16 [(metadata) = { exit_code: 1 }];
226+
INVALID_TIMEOUT_TAG = 17 [(metadata) = { exit_code: 1 }];
225227
INVALID_REMOTE_EXECUTION_PROPERTIES = 10 [(metadata) = { exit_code: 1 }];
226228
NO_USABLE_STRATEGY_FOUND = 11 [(metadata) = { exit_code: 1 }];
227229
// TODO(b/138456686): this code should be deprecated when SpawnResult is

src/test/java/com/google/devtools/build/lib/packages/TargetUtilsTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,19 @@ public void testExecutionInfo_withPrefixCpu() throws Exception {
223223
assertThat(execInfo).containsExactly("cpu:123", "");
224224
}
225225

226+
@Test
227+
public void testExecutionInfo_withPrefixTimeout() throws Exception {
228+
scratch.file(
229+
"tests/BUILD",
230+
"load('//test_defs:foo_binary.bzl', 'foo_binary')",
231+
"foo_binary(name = 'with-prefix-timeout', srcs=['sh.sh'], tags=['timeout:123', 'wrong-tag'])");
232+
233+
Rule withCpuPrefix = (Rule) getTarget("//tests:with-prefix-timeout");
234+
235+
Map<String, String> execInfo = TargetUtils.getExecutionInfo(withCpuPrefix);
236+
assertThat(execInfo).containsExactly("timeout:123", "");
237+
}
238+
226239
@Test
227240
public void testExecutionInfo_withLocalTag() throws Exception {
228241
scratch.file(

0 commit comments

Comments
 (0)