Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.33</version>
<version>3.2</version>
<relativePath />
</parent>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>2.17-SNAPSHOT</version>
<packaging>hpi</packaging>
<name>Pipeline: Supporting APIs</name>
<url>https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Supporting+APIs+Plugin</url>
<url>https://wiki.jenkins.io/display/JENKINS/Pipeline+Supporting+APIs+Plugin</url>
<licenses>
<license>
<name>MIT License</name>
Expand All @@ -63,6 +63,7 @@
</pluginRepositories>
<properties>
<jenkins.version>2.60.2</jenkins.version>
<java.level>8</java.level>
<no-test-jar>false</no-test-jar>
<git-plugin.version>3.0.5</git-plugin.version>
<workflow-scm-step-plugin.version>2.4</workflow-scm-step-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
package org.jenkinsci.plugins.workflow.support.concurrent;

import hudson.FilePath;
import hudson.Util;
import hudson.remoting.VirtualChannel;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -41,28 +41,60 @@ public class Timeout implements AutoCloseable {

private static final Logger LOGGER = Logger.getLogger(Timeout.class.getName());

private final ScheduledFuture<?> task;
private final Thread thread;
private volatile boolean completed;
private long endTime;
/*
private final String originalName;
*/

private Timeout(ScheduledFuture<?> task) {
this.task = task;
private Timeout(long time, TimeUnit unit) {
thread = Thread.currentThread();
LOGGER.log(Level.FINER, "Might interrupt {0} after {1} {2}", new Object[] {thread.getName(), time, unit});
/* see below:
originalName = thread.getName();
thread.setName(String.format("%s (Timeout@%h: %s)", originalName, this, Util.getTimeSpanString(unit.toMillis(time))));
*/
ping(time, unit);
}

@Override public void close() {
task.cancel(false);
completed = true;
/*
thread.setName(originalName);
*/
LOGGER.log(Level.FINER, "completed {0}", thread.getName());
}

public static Timeout limit(final long time, final TimeUnit unit) {
final Thread thread = Thread.currentThread();
return new Timeout(Timer.get().schedule(new Runnable() {
@Override public void run() {
if (LOGGER.isLoggable(Level.FINE)) {
Throwable t = new Throwable();
t.setStackTrace(thread.getStackTrace());
LOGGER.log(Level.FINE, "Interrupting " + thread + " after " + time + " " + unit, t);
}
thread.interrupt();
private void ping(final long time, final TimeUnit unit) {
Timer.get().schedule(() -> {
if (completed) {
LOGGER.log(Level.FINER, "{0} already finished, no need to interrupt", thread.getName());
return;
}
if (LOGGER.isLoggable(Level.FINE)) {
Throwable t = new Throwable();
t.setStackTrace(thread.getStackTrace());
LOGGER.log(Level.FINE, "Interrupting " + thread.getName() + " after " + time + " " + unit, t);
}
thread.interrupt();
if (endTime == 0) {
// First interruption.
endTime = System.nanoTime();
} else {
// Not dead yet?
String unresponsiveness = Util.getTimeSpanString((System.nanoTime() - endTime) / 1_000_000);
LOGGER.log(Level.INFO, "{0} unresponsive for {1}", new Object[] {thread.getName(), unresponsiveness});
/* TODO does not work; thread.getName() does not seem to return the current value when called from another thread, even w/ synchronized access, and running with -Xint
thread.setName(thread.getName().replaceFirst(String.format("(Timeout@%h: )[^)]+", this), "$1unresponsive for " + unresponsiveness));
*/
}
}, time, unit));
ping(5, TimeUnit.SECONDS);
}, time, unit);
}

public static Timeout limit(final long time, final TimeUnit unit) {
return new Timeout(time, unit);
}

// TODO JENKINS-32986 offer a variant that will escalate to Thread.stop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@
package org.jenkinsci.plugins.workflow.support.concurrent;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.LoggerRule;

public class TimeoutTest {

@Rule public LoggerRule logging = new LoggerRule().record(Timeout.class, Level.FINER);

@Test public void passed() throws Exception {
try (Timeout timeout = Timeout.limit(5, TimeUnit.SECONDS)) {
Expand All @@ -47,4 +52,35 @@ public class TimeoutTest {
Thread.sleep(1_000);
}

@Test public void hung() throws Exception {
/* see disabled code in Timeout:
final AtomicBoolean stop = new AtomicBoolean();
Thread t = Thread.currentThread();
Timer.get().submit(() -> {
while (!stop.get()) {
System.err.println(t.getName());
try {
Thread.sleep(1_000);
} catch (InterruptedException x) {
x.printStackTrace();
}
}
});
*/
try (Timeout timeout = Timeout.limit(1, TimeUnit.SECONDS)) {
for (int i = 0; i < 5; i++) {
try /* (WithThreadName naming = new WithThreadName(" cycle #" + i)) */ {
Thread.sleep(10_000);
fail("should have timed out");
} catch (InterruptedException x) {
// OK
}
}
}
Thread.sleep(6_000);
/*
stop.set(true);
*/
}

}