diff --git a/changelog.html b/changelog.html
index 8346798549..0d5ae6f271 100644
--- a/changelog.html
+++ b/changelog.html
@@ -59,6 +59,9 @@ Upcoming changes
Flyweight tasks should execute on the master if there's no static
executors available.
(issue 7291)
+
+ Download tool installations directly from the slave when possible, since this is much faster than going through the master.
+ (issue 17330)
Improved UI for implicitly locked builds.
(issue 10197)
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 25362a7f94..3dabd48108 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -493,9 +493,10 @@ public final class FilePath implements Serializable {
});
}
- private void unzip(File dir, InputStream in) throws IOException {
+ private static void unzip(File dir, InputStream in) throws IOException {
File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
try {
+ // XXX why does this not simply use ZipInputStream?
IOUtils.copy(in, tmpFile);
unzip(dir,tmpFile);
}
@@ -504,7 +505,7 @@ public final class FilePath implements Serializable {
}
}
- private void unzip(File dir, File zipFile) throws IOException {
+ static private void unzip(File dir, File zipFile) throws IOException {
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
ZipFile zip = new ZipFile(zipFile);
@SuppressWarnings("unchecked")
@@ -724,6 +725,19 @@ public final class FilePath implements Serializable {
if(listener!=null)
listener.getLogger().println(message);
+ if (isRemote()) {
+ // First try to download from the slave machine.
+ try {
+ act(new Unpack(archive));
+ timestamp.touch(sourceTimestamp);
+ return true;
+ } catch (IOException x) {
+ if (listener != null) {
+ x.printStackTrace(listener.error("Failed to download " + archive + " from slave; will retry from master"));
+ }
+ }
+ }
+
// for HTTP downloads, enable automatic retry for added resilience
InputStream in = archive.getProtocol().startsWith("http") ? ProxyConfiguration.getInputStream(archive) : con.getInputStream();
CountingInputStream cis = new CountingInputStream(in);
@@ -743,6 +757,31 @@ public final class FilePath implements Serializable {
}
}
+ private static final class Unpack implements FileCallable {
+ private final URL archive;
+ Unpack(URL archive) {
+ this.archive = archive;
+ }
+ @Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
+ InputStream in = archive.openStream();
+ try {
+ CountingInputStream cis = new CountingInputStream(in);
+ try {
+ if (archive.toExternalForm().endsWith(".zip")) {
+ unzip(dir, cis);
+ } else {
+ readFromTar("input stream", dir, GZIP.extract(cis));
+ }
+ } catch (IOException x) {
+ throw new IOException2(String.format("Failed to unpack %s (%d bytes read)", archive, cis.getByteCount()), x);
+ }
+ } finally {
+ in.close();
+ }
+ return null;
+ }
+ }
+
/**
* Reads the URL on the current VM, and writes all the data to this {@link FilePath}
* (this is different from resolving URL remotely.)