Skip to content

Commit edac98b

Browse files
committed
fix: add download timeouts and retry on transient failures
Fixes #242. Three downloader weaknesses combined to make setup fragile when a remote (e.g. vault.omniarchive.uk) is slow or flaky: - openURLStream did not set connect or read timeouts, so a stalled remote could hang the GUI indefinitely (user reported being stuck at 5%) - downloadFile only attempted once, so any single SSL reset or timeout aborted the whole setup - A failed mid-stream download left a partial file on disk that could confuse subsequent runs Set 30s connect / 60s read timeouts, retry up to 3 times with linear backoff on IOException, and clean up the partial file before each retry. Also closes the URL stream via try-with-resources, fixing a small resource leak in the original.
1 parent c094346 commit edac98b

1 file changed

Lines changed: 32 additions & 4 deletions

File tree

src/main/java/org/mcphackers/mcp/tools/FileUtil.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,44 @@ public static void downloadFile(String url, Path output) throws IOException {
140140
}
141141

142142
public static void downloadFile(URL url, Path output) throws IOException {
143-
ReadableByteChannel channel = Channels.newChannel(openURLStream(url));
144-
try (FileOutputStream stream = new FileOutputStream(output.toAbsolutePath().toString())) {
145-
FileChannel fileChannel = stream.getChannel();
146-
fileChannel.transferFrom(channel, 0, Long.MAX_VALUE);
143+
final int maxAttempts = 3;
144+
IOException lastError = null;
145+
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
146+
try (InputStream in = openURLStream(url)) {
147+
ReadableByteChannel channel = Channels.newChannel(in);
148+
try (FileOutputStream stream = new FileOutputStream(output.toAbsolutePath().toString())) {
149+
FileChannel fileChannel = stream.getChannel();
150+
fileChannel.transferFrom(channel, 0, Long.MAX_VALUE);
151+
}
152+
return;
153+
} catch (IOException e) {
154+
lastError = e;
155+
// Don't leave a half-written file behind for the next attempt or
156+
// for the SHA1 verification on subsequent runs.
157+
try {
158+
Files.deleteIfExists(output);
159+
} catch (IOException ignored) {
160+
}
161+
if (attempt < maxAttempts) {
162+
try {
163+
Thread.sleep(1000L * attempt);
164+
} catch (InterruptedException ie) {
165+
Thread.currentThread().interrupt();
166+
throw e;
167+
}
168+
}
169+
}
147170
}
171+
throw lastError;
148172
}
149173

150174
public static InputStream openURLStream(URL url) throws IOException {
151175
URLConnection connection = url.openConnection();
152176
connection.setRequestProperty("User-Agent", "RetroMCP/" + MCP.VERSION);
177+
// Without explicit timeouts, a stalled remote (slow archive mirror, dropped
178+
// packet, blackholed route) hangs the download indefinitely.
179+
connection.setConnectTimeout(30_000);
180+
connection.setReadTimeout(60_000);
153181
return connection.getInputStream();
154182
}
155183

0 commit comments

Comments
 (0)