diff --git a/changelog.html b/changelog.html index 58ade6d74f..edd8974079 100644 --- a/changelog.html +++ b/changelog.html @@ -55,7 +55,9 @@ Upcoming changes diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index cf926539c1..6bf1f387ca 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -70,6 +70,7 @@ import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; @@ -998,6 +999,27 @@ public final class FilePath implements Serializable { } } + /** + * Takes a {@link FilePath}+{@link FileCallable} pair and returns the equivalent {@link Callable}. + * When executing the resulting {@link Callable}, it executes {@link FileCallable#act(FileCallable)} + * on this {@link FilePath}. + * + * @since 1.522 + */ + public Callable asCallableWith(final FileCallable task) { + return new Callable() { + @Override + public V call() throws IOException { + try { + return act(task); + } catch (InterruptedException e) { + throw (IOException)new InterruptedIOException().initCause(e); + } + } + private static final long serialVersionUID = 1L; + }; + } + /** * Converts this file to the URI, relative to the machine * on which this file is available. diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index 59fe9f7530..9da535694a 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -35,6 +35,7 @@ import hudson.model.Queue.Task; import hudson.model.labels.LabelAtom; import hudson.model.queue.CauseOfBlockage; import hudson.node_monitors.NodeMonitor; +import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; import hudson.security.ACL; import hudson.security.AccessControlled; @@ -445,7 +446,22 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable * @throws InterruptedException * if the operation is aborted. */ - public abstract ClockDifference getClockDifference() throws IOException, InterruptedException; + public ClockDifference getClockDifference() throws IOException, InterruptedException { + VirtualChannel channel = getChannel(); + if(channel==null) + throw new IOException(getNodeName()+" is offline"); + + return channel.call(getClockDifferenceCallable()); + } + + /** + * Returns a {@link Callable} that when run on the channel, estimates the clock difference. + * + * @return + * always non-null. + * @sine 1.522 + */ + public abstract Callable getClockDifferenceCallable(); /** * Constants that control how Hudson allocates jobs to slaves. diff --git a/core/src/main/java/hudson/model/Slave.java b/core/src/main/java/hudson/model/Slave.java index 7318f8bebe..b907542185 100644 --- a/core/src/main/java/hudson/model/Slave.java +++ b/core/src/main/java/hudson/model/Slave.java @@ -30,7 +30,6 @@ import hudson.Util; import hudson.Launcher.RemoteLauncher; import hudson.model.Descriptor.FormException; import hudson.remoting.Callable; -import hudson.remoting.VirtualChannel; import hudson.slaves.CommandLauncher; import hudson.slaves.ComputerLauncher; import hudson.slaves.DumbSlave; @@ -57,6 +56,7 @@ import java.util.Set; import javax.servlet.ServletException; +import hudson.util.TimeUnit2; import jenkins.model.Jenkins; import jenkins.slaves.WorkspaceLocator; @@ -252,16 +252,9 @@ public abstract class Slave extends Node implements Serializable { getAssignedLabels(); } - public ClockDifference getClockDifference() throws IOException, InterruptedException { - VirtualChannel channel = getChannel(); - if(channel==null) - throw new IOException(getNodeName()+" is offline"); - - long startTime = System.currentTimeMillis(); - long slaveTime = channel.call(new GetSystemTime()); - long endTime = System.currentTimeMillis(); - - return new ClockDifference((startTime+endTime)/2 - slaveTime); + @Override + public Callable getClockDifferenceCallable() { + return new GetClockDifference1(); } public Computer createComputer() { @@ -432,16 +425,59 @@ public abstract class Slave extends Node implements Serializable { private transient String agentCommand; /** - * Obtains the system clock. + * Obtains the clock difference between this side and that side of the channel. + * + *

+ * This is a hack to wrap the whole thing into a simple {@link Callable}. + * + *

    + *
  1. When the callable is sent to remote, we capture the time (on this side) in {@link GetClockDifference2#startTime} + *
  2. When the other side receives the callable it is {@link GetClockDifference2}. + *
  3. We capture the time on the other side and {@link GetClockDifference3} gets sent from the other side + *
  4. When it's read on this side as a return value, it morphs itself into {@link ClockDifference}. + *
*/ - private static final class GetSystemTime implements Callable { - public Long call() { - return System.currentTimeMillis(); + private static final class GetClockDifference1 implements Callable { + public ClockDifference call() { + // this method must be being invoked locally, which means the clock is in sync + return new ClockDifference(0); + } + + private Object writeReplace() { + return new GetClockDifference2(); } private static final long serialVersionUID = 1L; } + private static final class GetClockDifference2 implements Callable { + /** + * Capture the time on the master when this object is sent to remote, which is when + * {@link GetClockDifference1#writeReplace()} is run. + */ + private final long startTime = System.nanoTime(); + + public GetClockDifference3 call() { + return new GetClockDifference3(startTime); + } + + private static final long serialVersionUID = 1L; + } + + private static final class GetClockDifference3 implements Serializable { + private final long remoteTime = System.nanoTime(); + private final long startTime; + + public GetClockDifference3(long startTime) { + this.startTime = startTime; + } + + private Object readResolve() { + long endTime = System.nanoTime(); + return new ClockDifference(TimeUnit2.NANOSECONDS.toMillis((startTime + endTime)/2-remoteTime)); + } + } + /** * Determines the workspace root file name for those who really really need the shortest possible path name. */ diff --git a/core/src/main/java/hudson/node_monitors/AbstractAsyncNodeMonitorDescriptor.java b/core/src/main/java/hudson/node_monitors/AbstractAsyncNodeMonitorDescriptor.java new file mode 100644 index 0000000000..bfb88d264e --- /dev/null +++ b/core/src/main/java/hudson/node_monitors/AbstractAsyncNodeMonitorDescriptor.java @@ -0,0 +1,119 @@ +package hudson.node_monitors; + +import hudson.model.Computer; +import hudson.remoting.Callable; +import hudson.remoting.VirtualChannel; +import jenkins.model.Jenkins; + +import javax.annotation.CheckForNull; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.logging.Level.WARNING; + +/** + * Sophisticated version of {@link AbstractNodeMonitorDescriptor} that + * performs monitoring on all slaves concurrently and asynchronously. + * + * @param + * represents the the result of the monitoring. + * @author Kohsuke Kawaguchi + */ +public abstract class AbstractAsyncNodeMonitorDescriptor extends AbstractNodeMonitorDescriptor { + protected AbstractAsyncNodeMonitorDescriptor() { + } + + protected AbstractAsyncNodeMonitorDescriptor(long interval) { + super(interval); + } + + protected AbstractAsyncNodeMonitorDescriptor(Class clazz) { + super(clazz); + } + + protected AbstractAsyncNodeMonitorDescriptor(Class clazz, long interval) { + super(clazz, interval); + } + + /** + * Creates a {@link Callable} that performs the monitoring when executed. + */ + protected abstract @CheckForNull Callable createCallable(Computer c); + + @Override + protected T monitor(Computer c) throws IOException, InterruptedException { + VirtualChannel ch = c.getChannel(); + if (ch != null) { + Callable cc = createCallable(c); + if (cc!=null) + return ch.call(cc); + } + return null; + } + + /** + * Performs all monitoring concurrently. + */ + @Override + protected Map monitor() throws InterruptedException { + Map> futures = new HashMap>(); + + for (Computer c : Jenkins.getInstance().getComputers()) { + try { + VirtualChannel ch = c.getChannel(); + futures.put(c,null); // sentinel value + if (ch!=null) { + Callable cc = createCallable(c); + if (cc!=null) + futures.put(c,ch.callAsync(cc)); + } + } catch (RuntimeException e) { + LOGGER.log(WARNING, "Failed to monitor "+c.getDisplayName()+" for "+getDisplayName(), e); + } catch (IOException e) { + LOGGER.log(WARNING, "Failed to monitor "+c.getDisplayName()+" for "+getDisplayName(), e); + } + } + + final long now = System.currentTimeMillis(); + final long end = now + getMonitoringTimeOut(); + + final Map data = new HashMap(); + + for (Entry> e : futures.entrySet()) { + Computer c = e.getKey(); + Future f = futures.get(c); + data.put(c, null); // sentinel value + + if (f!=null) { + try { + data.put(c,f.get(Math.max(0,end-System.currentTimeMillis()), MILLISECONDS)); + } catch (RuntimeException x) { + LOGGER.log(WARNING, "Failed to monitor " + c.getDisplayName() + " for " + getDisplayName(), x); + } catch (ExecutionException x) { + LOGGER.log(WARNING, "Failed to monitor " + c.getDisplayName() + " for " + getDisplayName(), x); + } catch (TimeoutException x) { + LOGGER.log(WARNING, "Failed to monitor " + c.getDisplayName() + " for " + getDisplayName(), x); + } + } + } + + return data; + } + + /** + * Controls the time out of monitoring. + */ + protected long getMonitoringTimeOut() { + return TimeUnit.SECONDS.toMillis(30); + } + + private static final Logger LOGGER = Logger.getLogger(AbstractAsyncNodeMonitorDescriptor.class.getName()); +} diff --git a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java index 160951e65c..3e57ea5bfe 100644 --- a/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java +++ b/core/src/main/java/hudson/node_monitors/AbstractNodeMonitorDescriptor.java @@ -34,6 +34,7 @@ import hudson.triggers.SafeTimerTask; import hudson.slaves.OfflineCause; import java.io.IOException; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -51,18 +52,34 @@ import java.util.logging.Logger; * @author Kohsuke Kawaguchi */ public abstract class AbstractNodeMonitorDescriptor extends Descriptor { + /** + * @deprecated as of 1.522 + * Extend from {@link AbstractAsyncNodeMonitorDescriptor} + */ protected AbstractNodeMonitorDescriptor() { this(HOUR); } + /** + * @deprecated as of 1.522 + * Extend from {@link AbstractAsyncNodeMonitorDescriptor} + */ protected AbstractNodeMonitorDescriptor(long interval) { schedule(interval); } + /** + * @deprecated as of 1.522 + * Extend from {@link AbstractAsyncNodeMonitorDescriptor} + */ protected AbstractNodeMonitorDescriptor(Class clazz) { this(clazz,HOUR); } + /** + * @deprecated as of 1.522 + * Extend from {@link AbstractAsyncNodeMonitorDescriptor} + */ protected AbstractNodeMonitorDescriptor(Class clazz, long interval) { super(clazz); @@ -106,6 +123,33 @@ public abstract class AbstractNodeMonitorDescriptor extends Descriptor monitor() throws InterruptedException { + Map data = new HashMap(); + for( Computer c : Jenkins.getInstance().getComputers() ) { + try { + Thread.currentThread().setName("Monitoring "+c.getDisplayName()+" for "+getDisplayName()); + + if(c.getChannel()==null) + data.put(c,null); + else + data.put(c,monitor(c)); + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, "Failed to monitor "+c.getDisplayName()+" for "+getDisplayName(), e); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to monitor "+c.getDisplayName()+" for "+getDisplayName(), e); + } catch (InterruptedException e) { + throw (InterruptedException)new InterruptedException("Node monitoring "+c.getDisplayName()+" for "+getDisplayName()+" aborted.").initCause(e); + } + } + return data; + } + /** * Obtains the monitoring result currently available, or null if no data is available. * @@ -214,7 +258,7 @@ public abstract class AbstractNodeMonitorDescriptor extends Descriptor data = new HashMap(); + private /*final*/ Map data = Collections.emptyMap(); private long timestamp; @@ -235,24 +279,7 @@ public abstract class AbstractNodeMonitorDescriptor extends Descriptor { - protected String monitor(Computer c) throws IOException, InterruptedException { - return c.getChannel().call(new GetArchTask()); + public static final class DescriptorImpl extends AbstractAsyncNodeMonitorDescriptor { + @Override + protected Callable createCallable(Computer c) { + return new GetArchTask(); } public String getDisplayName() { @@ -55,7 +56,7 @@ public class ArchitectureMonitor extends NodeMonitor { /** * Obtains the string that represents the architecture. */ - private static class GetArchTask implements Callable { + private static class GetArchTask implements Callable { public String call() { String os = System.getProperty("os.name"); String arch = System.getProperty("os.arch"); diff --git a/core/src/main/java/hudson/node_monitors/ClockMonitor.java b/core/src/main/java/hudson/node_monitors/ClockMonitor.java index 4352ec9bd4..cf3f025997 100644 --- a/core/src/main/java/hudson/node_monitors/ClockMonitor.java +++ b/core/src/main/java/hudson/node_monitors/ClockMonitor.java @@ -25,6 +25,7 @@ package hudson.node_monitors; import hudson.model.Computer; import hudson.model.Node; +import hudson.remoting.Callable; import hudson.util.ClockDifference; import hudson.Extension; import org.kohsuke.stapler.StaplerRequest; @@ -46,11 +47,12 @@ public class ClockMonitor extends NodeMonitor { } @Extension - public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractNodeMonitorDescriptor() { - protected ClockDifference monitor(Computer c) throws IOException, InterruptedException { + public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractAsyncNodeMonitorDescriptor() { + @Override + protected Callable createCallable(Computer c) { Node n = c.getNode(); if(n==null) return null; - return n.getClockDifference(); + return n.getClockDifferenceCallable(); } public String getDisplayName() { diff --git a/core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java b/core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java index bc43661e2c..1177652124 100644 --- a/core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java +++ b/core/src/main/java/hudson/node_monitors/DiskSpaceMonitor.java @@ -27,6 +27,7 @@ import hudson.Extension; import hudson.FilePath; import hudson.Functions; import hudson.model.Computer; +import hudson.remoting.Callable; import jenkins.model.Jenkins; import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace; import org.kohsuke.stapler.DataBoundConstructor; @@ -64,11 +65,12 @@ public class DiskSpaceMonitor extends AbstractDiskSpaceMonitor { return Messages.DiskSpaceMonitor_DisplayName(); } - @Override protected DiskSpace monitor(Computer c) throws IOException, InterruptedException { + @Override + protected Callable createCallable(Computer c) { FilePath p = c.getNode().getRootPath(); if(p==null) return null; - return p.act(new GetUsableSpace()); + return p.asCallableWith(new GetUsableSpace()); } }; diff --git a/core/src/main/java/hudson/node_monitors/DiskSpaceMonitorDescriptor.java b/core/src/main/java/hudson/node_monitors/DiskSpaceMonitorDescriptor.java index baf1fc4446..8f1fb84995 100644 --- a/core/src/main/java/hudson/node_monitors/DiskSpaceMonitorDescriptor.java +++ b/core/src/main/java/hudson/node_monitors/DiskSpaceMonitorDescriptor.java @@ -47,7 +47,7 @@ import org.kohsuke.stapler.export.Exported; * @author Kohsuke Kawaguchi * @since 1.520 */ -public abstract class DiskSpaceMonitorDescriptor extends AbstractNodeMonitorDescriptor { +public abstract class DiskSpaceMonitorDescriptor extends AbstractAsyncNodeMonitorDescriptor { /** * Value object that represents the disk space. */ diff --git a/core/src/main/java/hudson/node_monitors/ResponseTimeMonitor.java b/core/src/main/java/hudson/node_monitors/ResponseTimeMonitor.java index b8b38e9283..db8271f4bd 100644 --- a/core/src/main/java/hudson/node_monitors/ResponseTimeMonitor.java +++ b/core/src/main/java/hudson/node_monitors/ResponseTimeMonitor.java @@ -29,12 +29,16 @@ import hudson.slaves.OfflineCause; import hudson.model.Computer; import hudson.remoting.Callable; import hudson.remoting.Future; +import hudson.util.ClockDifference; import hudson.util.TimeUnit2; import hudson.util.IOException2; import net.sf.json.JSONObject; import org.kohsuke.stapler.StaplerRequest; import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -49,33 +53,33 @@ import org.kohsuke.stapler.export.ExportedBean; */ public class ResponseTimeMonitor extends NodeMonitor { @Extension - public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractNodeMonitorDescriptor() { - protected Data monitor(Computer c) throws IOException, InterruptedException { - Data old = get(c); - Data d; + public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractAsyncNodeMonitorDescriptor() { + @Override + protected Callable createCallable(Computer c) { + return new Step1(get(c)); + } - long start = System.nanoTime(); - Future f = c.getChannel().callAsync(new NoopTask()); - try { - f.get(TIMEOUT, TimeUnit.MILLISECONDS); - long end = System.nanoTime(); - d = new Data(old,TimeUnit2.NANOSECONDS.toMillis(end-start)); - } catch (ExecutionException e) { - throw new IOException2(e.getCause()); // I don't think this is possible - } catch (TimeoutException e) { - // special constant to indicate that the processing timed out. - d = new Data(old,-1L); - } + @Override + protected Map monitor() throws InterruptedException { + Map base = super.monitor(); + for (Entry e : base.entrySet()) { + Computer c = e.getKey(); + Data d = e.getValue(); + if (d ==null) { + // if we failed to monitor, put in the special value that indicates a failure + e.setValue(d=new Data(get(c),-1L)); + } - if(d.hasTooManyTimeouts() && !isIgnored()) { - // unlike other monitors whose failure still allow us to communicate with the slave, - // the failure in this monitor indicates that we are just unable to make any requests - // to this slave. So we should severe the connection, as opposed to marking it temporarily - // off line, which still keeps the underlying channel open. - c.disconnect(d); - LOGGER.warning(Messages.ResponseTimeMonitor_MarkedOffline(c.getName())); + if(d.hasTooManyTimeouts() && !isIgnored()) { + // unlike other monitors whose failure still allow us to communicate with the slave, + // the failure in this monitor indicates that we are just unable to make any requests + // to this slave. So we should severe the connection, as opposed to marking it temporarily + // off line, which still keeps the underlying channel open. + c.disconnect(d); + LOGGER.warning(Messages.ResponseTimeMonitor_MarkedOffline(c.getName())); + } } - return d; + return base; } public String getDisplayName() { @@ -88,11 +92,63 @@ public class ResponseTimeMonitor extends NodeMonitor { } }; + private static final class Step1 implements Callable { + private Data cur; + + private Step1(Data cur) { + this.cur = cur; + } + + public Data call() { + // this method must be being invoked locally, which means the roundtrip time is zero and zero forever + return new Data(cur,0); + } + + private Object writeReplace() { + return new Step2(cur); + } + + private static final long serialVersionUID = 1L; + } + + private static final class Step2 implements Callable { + private final Data cur; + private final long start = System.nanoTime(); + + public Step2(Data cur) { + this.cur = cur; + } + + public Step3 call() { + // this method must be being invoked locally, which means the roundtrip time is zero and zero forever + return new Step3(cur,start); + } + + private static final long serialVersionUID = 1L; + } + + private static final class Step3 implements Serializable { + private final Data cur; + private final long start; + + private Step3(Data cur, long start) { + this.cur = cur; + this.start = start; + } + + private Object readResolve() { + long end = System.nanoTime(); + return new Data(cur,TimeUnit2.NANOSECONDS.toMillis(end-start)); + } + + private static final long serialVersionUID = 1L; + } + /** * Immutable representation of the monitoring data. */ @ExportedBean - public static final class Data extends OfflineCause { + public static final class Data extends OfflineCause implements Serializable { /** * Record of the past 5 times. -1 if time out. Otherwise in milliseconds. * Old ones first. @@ -153,12 +209,6 @@ public class ResponseTimeMonitor extends NodeMonitor { return Util.wrapToErrorSpan(Messages.ResponseTimeMonitor_TimeOut(fc)); return getAverage()+"ms"; } - } - - private static class NoopTask implements Callable { - public String call() { - return null; - } private static final long serialVersionUID = 1L; } diff --git a/core/src/main/java/hudson/node_monitors/SwapSpaceMonitor.java b/core/src/main/java/hudson/node_monitors/SwapSpaceMonitor.java index b2edc41fc9..404aa3f779 100644 --- a/core/src/main/java/hudson/node_monitors/SwapSpaceMonitor.java +++ b/core/src/main/java/hudson/node_monitors/SwapSpaceMonitor.java @@ -78,9 +78,10 @@ public class SwapSpaceMonitor extends NodeMonitor { } @Extension - public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractNodeMonitorDescriptor() { - protected MemoryUsage monitor(Computer c) throws IOException, InterruptedException { - return c.getChannel().call(new MonitorTask()); + public static final AbstractNodeMonitorDescriptor DESCRIPTOR = new AbstractAsyncNodeMonitorDescriptor() { + @Override + protected MonitorTask createCallable(Computer c) { + return new MonitorTask(); } public String getDisplayName() { diff --git a/core/src/main/java/hudson/node_monitors/TemporarySpaceMonitor.java b/core/src/main/java/hudson/node_monitors/TemporarySpaceMonitor.java index f6919f56d3..05407d318f 100644 --- a/core/src/main/java/hudson/node_monitors/TemporarySpaceMonitor.java +++ b/core/src/main/java/hudson/node_monitors/TemporarySpaceMonitor.java @@ -28,6 +28,7 @@ import hudson.FilePath; import hudson.FilePath.FileCallable; import hudson.Functions; import hudson.model.Computer; +import hudson.remoting.Callable; import jenkins.model.Jenkins; import hudson.node_monitors.DiskSpaceMonitorDescriptor.DiskSpace; import hudson.remoting.VirtualChannel; @@ -66,11 +67,12 @@ public class TemporarySpaceMonitor extends AbstractDiskSpaceMonitor { return Messages.TemporarySpaceMonitor_DisplayName(); } - @Override protected DiskSpace monitor(Computer c) throws IOException, InterruptedException { + @Override + protected Callable createCallable(Computer c) { FilePath p = c.getNode().getRootPath(); if(p==null) return null; - return p.act(new GetTempSpace()); + return p.asCallableWith(new GetTempSpace()); } }; diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index dcd190a7e7..e155381e24 100755 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -126,6 +126,7 @@ import hudson.lifecycle.Lifecycle; import hudson.logging.LogRecorderManager; import hudson.lifecycle.RestartNotSupportedException; import hudson.markup.RawHtmlMarkupFormatter; +import hudson.remoting.Callable; import hudson.remoting.Channel; import hudson.remoting.LocalChannel; import hudson.remoting.VirtualChannel; @@ -1960,6 +1961,15 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro return ClockDifference.ZERO; } + @Override + public Callable getClockDifferenceCallable() { + return new Callable() { + public ClockDifference call() throws IOException { + return new ClockDifference(0); + } + }; + } + /** * For binding {@link LogRecorderManager} to "/log". * Everything below here is admin-only, so do the check here. diff --git a/core/src/test/java/hudson/slaves/NodeListTest.java b/core/src/test/java/hudson/slaves/NodeListTest.java index 29b1d96ba5..b802ce372d 100644 --- a/core/src/test/java/hudson/slaves/NodeListTest.java +++ b/core/src/test/java/hudson/slaves/NodeListTest.java @@ -24,6 +24,7 @@ package hudson.slaves; +import hudson.remoting.Callable; import jenkins.model.Jenkins; import junit.framework.TestCase; import hudson.model.Node; @@ -98,7 +99,8 @@ public class NodeListTest extends TestCase { throw new UnsupportedOperationException(); } - public ClockDifference getClockDifference() throws IOException, InterruptedException { + @Override + public Callable getClockDifferenceCallable() { throw new UnsupportedOperationException(); }