Skip to content

Commit a3a3447

Browse files
committed
Merge pull request #14 from ndeloof/master
Define an extension point for time-out strategy
2 parents 3191975 + 47e927c commit a3a3447

25 files changed

+456
-291
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package hudson.plugins.build_timeout;
2+
3+
import hudson.model.AbstractBuild;
4+
import hudson.model.Describable;
5+
import hudson.model.Run;
6+
7+
/**
8+
* @author <a href="mailto:[email protected]">Nicolas De Loof</a>
9+
*/
10+
public abstract class BuildTimeOutStrategy implements Describable<BuildTimeOutStrategy> {
11+
12+
public static final long MINUTES = 60*1000L;
13+
14+
/**
15+
* Define the delay (in milliseconds) to wait for the build to complete before interrupting.
16+
* @param run
17+
*/
18+
public abstract long getTimeOut(Run run);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package hudson.plugins.build_timeout;
2+
3+
import hudson.model.Descriptor;
4+
5+
/**
6+
* @author <a href="mailto:[email protected]">Nicolas De Loof</a>
7+
*/
8+
public abstract class BuildTimeOutStrategyDescriptor extends Descriptor<BuildTimeOutStrategy> {
9+
10+
}

src/main/java/hudson/plugins/build_timeout/BuildTimeoutWrapper.java

+34-156
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import hudson.model.Result;
1212
import hudson.model.Run;
1313
import hudson.model.queue.Executables;
14+
import hudson.plugins.build_timeout.impl.AbsoluteTimeOutStrategy;
15+
import hudson.plugins.build_timeout.impl.ElasticTimeOutStrategy;
16+
import hudson.plugins.build_timeout.impl.LikelyStuckTimeOutStrategy;
1417
import hudson.tasks.BuildWrapper;
1518
import hudson.tasks.BuildWrapperDescriptor;
1619
import hudson.triggers.SafeTimerTask;
@@ -22,6 +25,8 @@
2225
import java.io.IOException;
2326
import java.util.List;
2427
import java.util.Set;
28+
29+
import jenkins.model.Jenkins;
2530
import net.sf.json.JSONObject;
2631
import org.kohsuke.stapler.DataBoundConstructor;
2732
import org.kohsuke.stapler.StaplerRequest;
@@ -33,60 +38,27 @@
3338
*/
3439
public class BuildTimeoutWrapper extends BuildWrapper {
3540

36-
protected static final int NUMBER_OF_BUILDS_TO_AVERAGE = 3;
3741
public static long MINIMUM_TIMEOUT_MILLISECONDS = Long.getLong(BuildTimeoutWrapper.class.getName()+ ".MINIMUM_TIMEOUT_MILLISECONDS", 3 * 60 * 1000);
3842

39-
40-
public static final String ABSOLUTE = "absolute";
41-
public static final String ELASTIC = "elastic";
42-
public static final String STUCK = "likelyStuck";
43-
44-
/**
45-
* If the build took longer than this amount of minutes,
46-
* it will be terminated.
47-
*/
48-
public int timeoutMinutes;
43+
44+
private /* final */ BuildTimeOutStrategy strategy;
4945

5046
/**
5147
* Fail the build rather than aborting it
5248
*/
5349
public boolean failBuild;
5450

55-
5651
/**
5752
* Writing the build description when timeout occurred.
5853
*/
5954
public boolean writingDescription;
6055

61-
/**
62-
* The percentage of the mean of the duration of the last n successful builds
63-
* to wait before killing the build.
64-
*
65-
* IE, if the last n successful builds averaged a 10 minute duration,
66-
* then 200% of that would be 20 minutes.
67-
*/
68-
public int timeoutPercentage;
69-
70-
/**
71-
* Values can be "elastic" or "absolute"
72-
*/
73-
public String timeoutType;
74-
75-
/**
76-
* The timeout to use if there are no valid builds in the build
77-
* history (ie, no successful or unstable builds)
78-
*/
79-
public Integer timeoutMinutesElasticDefault;
8056

8157
@DataBoundConstructor
82-
public BuildTimeoutWrapper(int timeoutMinutes, boolean failBuild, boolean writingDescription,
83-
int timeoutPercentage, int timeoutMinutesElasticDefault, String timeoutType) {
84-
this.timeoutMinutes = Math.max(3,timeoutMinutes);
58+
public BuildTimeoutWrapper(BuildTimeOutStrategy strategy, boolean failBuild, boolean writingDescription) {
59+
this.strategy = strategy;
8560
this.failBuild = failBuild;
8661
this.writingDescription = writingDescription;
87-
this.timeoutPercentage = timeoutPercentage;
88-
this.timeoutMinutesElasticDefault = Math.max(3, timeoutMinutesElasticDefault);
89-
this.timeoutType = timeoutType;
9062
}
9163

9264
@Override
@@ -131,47 +103,11 @@ public void doRun() {
131103

132104
private final TimeoutTimerTask task;
133105

134-
private final long effectiveTimeout;
106+
private final long effectiveTimeout = strategy.getTimeOut(build);
135107

136108
public EnvironmentImpl() {
137-
long timeout;
138-
if (ELASTIC.equals(timeoutType)) {
139-
timeout = getEffectiveTimeout(timeoutMinutes * 60L * 1000L, timeoutPercentage,
140-
timeoutMinutesElasticDefault * 60*1000, timeoutType, build.getProject().getBuilds());
141-
} else if (STUCK.equals(timeoutType)) {
142-
timeout = getLikelyStuckTime();
143-
} else {
144-
timeout = timeoutMinutes * 60L * 1000L;
145-
}
146-
147-
this.effectiveTimeout = timeout;
148109
task = new TimeoutTimerTask(build, listener);
149-
Trigger.timer.schedule(task, timeout);
150-
}
151-
152-
/**
153-
* Get the time considered it stuck.
154-
*
155-
* @return 10 times as much as eta if eta is available, else 24 hours.
156-
* @see Executor#isLikelyStuck()
157-
*/
158-
private long getLikelyStuckTime() {
159-
Executor executor = build.getExecutor();
160-
if (executor == null) {
161-
return TimeUnit2.HOURS.toMillis(24);
162-
}
163-
164-
Queue.Executable executable = executor.getCurrentExecutable();
165-
if (executable == null) {
166-
return TimeUnit2.HOURS.toMillis(24);
167-
}
168-
169-
long eta = Executables.getEstimatedDurationFor(executable);
170-
if (eta >= 0) {
171-
return eta * 10;
172-
} else {
173-
return TimeUnit2.HOURS.toMillis(24);
174-
}
110+
Trigger.timer.schedule(task, effectiveTimeout);
175111
}
176112

177113
@Override
@@ -184,44 +120,15 @@ public boolean tearDown(AbstractBuild build, BuildListener listener) throws IOEx
184120
return new EnvironmentImpl();
185121
}
186122

187-
public static long getEffectiveTimeout(long timeoutMilliseconds, int timeoutPercentage, int timeoutMillsecondsElasticDefault,
188-
String timeoutType, List<Run> builds) {
189-
190-
if (ELASTIC.equals(timeoutType)) {
191-
double elasticTimeout = getElasticTimeout(timeoutPercentage, builds);
192-
if (elasticTimeout == 0) {
193-
return Math.max(MINIMUM_TIMEOUT_MILLISECONDS, timeoutMillsecondsElasticDefault);
194-
} else {
195-
return (long) Math.max(MINIMUM_TIMEOUT_MILLISECONDS, elasticTimeout);
196-
}
197-
} else {
198-
return (long) Math.max(MINIMUM_TIMEOUT_MILLISECONDS, timeoutMilliseconds);
199-
}
200-
}
201-
202-
private static double getElasticTimeout(int timeoutPercentage, List<Run> builds) {
203-
return timeoutPercentage * .01D * (timeoutPercentage > 0 ? averageDuration(builds) : 0);
204-
}
205-
206-
private static double averageDuration(List <Run> builds) {
207-
int nonFailingBuilds = 0;
208-
int durationSum= 0;
209-
210-
for (int i = 0; i < builds.size() && nonFailingBuilds < NUMBER_OF_BUILDS_TO_AVERAGE; i++) {
211-
Run run = builds.get(i);
212-
if (run.getResult() != null &&
213-
run.getResult().isBetterOrEqualTo(Result.UNSTABLE)) {
214-
durationSum += run.getDuration();
215-
nonFailingBuilds++;
216-
}
217-
}
218-
219-
return nonFailingBuilds > 0 ? durationSum / nonFailingBuilds : 0;
220-
}
221-
222123
protected Object readResolve() {
223-
if (timeoutType == null) {
224-
timeoutType = ABSOLUTE;
124+
if ("elastic".equalsIgnoreCase(timeoutType)) {
125+
strategy = new ElasticTimeOutStrategy(timeoutPercentage,
126+
timeoutMinutesElasticDefault != null ? timeoutMinutesElasticDefault.intValue() : 60,
127+
3);
128+
} else if ("likelyStuck".equalsIgnoreCase(timeoutType)) {
129+
strategy = new LikelyStuckTimeOutStrategy();
130+
} else if (strategy == null) {
131+
strategy = new AbsoluteTimeOutStrategy(timeoutMinutes);
225132
}
226133
return this;
227134
}
@@ -246,52 +153,23 @@ public String getDisplayName() {
246153
public boolean isApplicable(AbstractProject<?, ?> item) {
247154
return true;
248155
}
156+
}
249157

250-
public int[] getPercentages() {
251-
return new int[] {150,200,250,300,350,400};
252-
}
253-
254-
@Override
255-
public BuildWrapper newInstance(StaplerRequest req, JSONObject formData)
256-
throws hudson.model.Descriptor.FormException {
257-
JSONObject timeoutObject = formData.getJSONObject("timeoutType");
158+
public BuildTimeOutStrategy getStrategy() {
159+
return strategy;
160+
}
258161

259-
// we would ideally do this on the form itself (to show the default)
260-
//but there is a show/hide bug when using radioOptions inside an optionBlock
261-
if (timeoutObject.isNullObject() || timeoutObject.isEmpty()) {
262-
formData.put("timeoutType", ABSOLUTE);
263-
} else {
264-
// Jenkins 1.427
265-
// {"timeoutType": {
266-
// "value": "elastic", "timeoutPercentage": "150",
267-
// "timeoutMinutesElasticDefault": "3333333"}}
268-
// Jenkins 1.420
269-
// {"timeoutMinutes": "3",
270-
// "timeoutType": {"value": "elastic"},
271-
// "timeoutPercentage": "150", "timeoutMinutesElasticDefault": "3333333",
272-
// "failBuild": false, "writingDescription": false}
273-
// => to keep comaptibility
274-
// "timeoutType": "elastic", "timeoutPercentage": "150",
275-
// "timeoutMinutesElasticDefault": "3333333"...
276-
String timeoutType = timeoutObject.getString("value");
277-
timeoutObject.remove("value");
278-
for (String key : (Set<String>) timeoutObject.keySet()) {
279-
formData.put(key, timeoutObject.get(key));
280-
}
281-
formData.put("timeoutType", timeoutType);
282-
}
162+
public List<BuildTimeOutStrategyDescriptor> getStrategies() {
163+
return Jenkins.getInstance().getDescriptorList(BuildTimeOutStrategy.class);
164+
}
283165

284-
return super.newInstance(req, formData);
285-
}
166+
// --- legacy attributes, kept for backward compatibility
286167

287-
public ListBoxModel doFillTimeoutPercentageItems() {
288-
ListBoxModel m = new ListBoxModel();
289-
for (int option : getPercentages()) {
290-
String s = String.valueOf(option);
291-
m.add(s + "%", s);
292-
}
293-
return m;
294-
}
295-
296-
}
168+
public transient int timeoutMinutes;
169+
170+
public transient int timeoutPercentage;
171+
172+
public transient String timeoutType;
173+
174+
public transient Integer timeoutMinutesElasticDefault;
297175
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package hudson.plugins.build_timeout.impl;
2+
3+
import static hudson.plugins.build_timeout.BuildTimeoutWrapper.MINIMUM_TIMEOUT_MILLISECONDS;
4+
5+
import hudson.Extension;
6+
import hudson.model.Descriptor;
7+
import hudson.model.Run;
8+
import hudson.plugins.build_timeout.BuildTimeOutStrategy;
9+
import hudson.plugins.build_timeout.BuildTimeOutStrategyDescriptor;
10+
import org.kohsuke.stapler.DataBoundConstructor;
11+
12+
/**
13+
* If the build took longer than <tt>timeoutMinutes</tt> amount of minutes, it will be terminated.
14+
*/
15+
public class AbsoluteTimeOutStrategy extends BuildTimeOutStrategy {
16+
17+
public final int timeoutMinutes;
18+
19+
@DataBoundConstructor
20+
public AbsoluteTimeOutStrategy(int timeoutMinutes) {
21+
this.timeoutMinutes = Math.max((int) (MINIMUM_TIMEOUT_MILLISECONDS / MINUTES), timeoutMinutes);
22+
}
23+
24+
@Override
25+
public long getTimeOut(Run run) {
26+
return MINUTES * timeoutMinutes;
27+
}
28+
29+
public Descriptor<BuildTimeOutStrategy> getDescriptor() {
30+
return DESCRIPTOR;
31+
}
32+
33+
@Extension
34+
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
35+
36+
public static class DescriptorImpl extends BuildTimeOutStrategyDescriptor {
37+
38+
@Override
39+
public String getDisplayName() {
40+
return "Absolute";
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)