v1.0.14.2 — fix JFX hang (ForkJoinPool), retry logic for connection reset, library logging

This commit is contained in:
SashegDev
2026-07-30 16:09:44 +00:00
parent e962adbb58
commit 4acbdedf70
4 changed files with 318 additions and 257 deletions
@@ -65,17 +65,28 @@ public class VersionInstaller {
if (versionUrl == null) throw new Exception("Version " + versionId + " not found"); if (versionUrl == null) throw new Exception("Version " + versionId + " not found");
ProgressBar.show("Fetching version info", 0, 1, "files"); ProgressBar.show("Fetching version info", 0, 1, "files");
String versionJson = ZHttpClient.getWithSmartProxy(versionUrl); String versionJson;
Files.writeString(versionDir.resolve(versionId + ".json"), versionJson); try {
versionJson = ZHttpClient.getWithSmartProxy(versionUrl);
Files.writeString(versionDir.resolve(versionId + ".json"), versionJson);
} catch (Exception e) {
System.err.println(ZAnsi.red("[VERSION] Failed to fetch version info: " + e.getMessage()));
throw e;
}
ProgressBar.show("Version info", 1, 1, "files"); ProgressBar.show("Version info", 1, 1, "files");
JSONObject versionData = new JSONObject(versionJson); JSONObject versionData = new JSONObject(versionJson);
// client.jar // client.jar
ProgressBar.show("Downloading client.jar", 0, 1, "files"); ProgressBar.show("Downloading client.jar", 0, 1, "files");
ZHttpClient.downloadFileWithSmartProxy( try {
versionData.getJSONObject("downloads").getJSONObject("client").getString("url"), ZHttpClient.downloadFileWithSmartProxy(
versionDir.resolve(versionId + ".jar")); versionData.getJSONObject("downloads").getJSONObject("client").getString("url"),
versionDir.resolve(versionId + ".jar"));
} catch (Exception e) {
System.err.println(ZAnsi.red("[VERSION] Failed to download client.jar: " + e.getMessage()));
throw e;
}
ProgressBar.show("Client.jar", 1, 1, "files"); ProgressBar.show("Client.jar", 1, 1, "files");
// Libraries // Libraries
@@ -197,7 +208,7 @@ public class VersionInstaller {
try { try {
ZHttpClient.downloadFileWithSmartProxy(url, target); ZHttpClient.downloadFileWithSmartProxy(url, target);
} catch (Exception e) { } catch (Exception e) {
// Skip problematic libraries System.err.println(ZAnsi.yellow("[LIB] Failed to download " + path + ": " + e.getMessage()));
} }
} }
count++; count++;
@@ -802,7 +802,7 @@ public class JFXLauncher extends Application {
setInstallProgressWithStage("Installation failed", 0, 100, "Failed", 0, 1); setInstallProgressWithStage("Installation failed", 0, 100, "Failed", 0, 1);
log("Install error: " + name); log("Install error: " + name);
} }
} catch (Exception e) { } catch (Throwable e) {
log("Install error: " + e.getMessage()); log("Install error: " + e.getMessage());
setInstallInProgress(false); setInstallInProgress(false);
setInstallProgressWithStage("Error: " + e.getMessage(), 0, 100, "Error", 0, 1); setInstallProgressWithStage("Error: " + e.getMessage(), 0, 100, "Error", 0, 1);
@@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class ZHttpClient { public class ZHttpClient {
@@ -25,6 +26,7 @@ public class ZHttpClient {
private static final HttpClient client = HttpClient.newBuilder() private static final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15)) .connectTimeout(Duration.ofSeconds(15))
.version(HttpClient.Version.HTTP_1_1) .version(HttpClient.Version.HTTP_1_1)
.executor(Executors.newCachedThreadPool())
.build(); .build();
private static String BASE_URL = "https://api.zernmc.ru"; private static String BASE_URL = "https://api.zernmc.ru";
@@ -237,6 +239,9 @@ public class ZHttpClient {
if (url.contains("maven.neoforged.net")) return ServiceType.NEOFORGE_MAVEN; if (url.contains("maven.neoforged.net")) return ServiceType.NEOFORGE_MAVEN;
if (url.contains("google.com")) return ServiceType.GOOGLE; if (url.contains("google.com")) return ServiceType.GOOGLE;
if (url.contains("cloudflare.com")) return ServiceType.CLOUDFLARE; if (url.contains("cloudflare.com")) return ServiceType.CLOUDFLARE;
if (url.contains("libraries.minecraft.net")) return ServiceType.MOJANG_RESOURCES;
if (url.contains("piston-data.mojang.com")) return ServiceType.MOJANG_META;
if (url.contains("repo1.maven.org") || url.contains("repo.maven.apache.org")) return ServiceType.MOJANG_RESOURCES;
return null; return null;
} }
@@ -256,10 +261,13 @@ public class ZHttpClient {
return cause instanceof java.net.ConnectException || return cause instanceof java.net.ConnectException ||
cause instanceof java.net.UnknownHostException || cause instanceof java.net.UnknownHostException ||
cause instanceof java.nio.channels.ClosedChannelException || cause instanceof java.nio.channels.ClosedChannelException ||
cause instanceof java.net.SocketException ||
msg.contains("connection") || msg.contains("connection") ||
msg.contains("timeout") || msg.contains("timeout") ||
msg.contains("refused") || msg.contains("refused") ||
msg.contains("closed"); msg.contains("closed") ||
msg.contains("reset") ||
msg.contains("abort");
} }
private static void markServiceAsBlocked(String url) { private static void markServiceAsBlocked(String url) {
@@ -278,106 +286,148 @@ public class ZHttpClient {
public static String getWithSmartProxy(String url) throws IOException, InterruptedException { public static String getWithSmartProxy(String url) throws IOException, InterruptedException {
if (!shouldUseProxyForUrl(url)) { if (!shouldUseProxyForUrl(url)) {
int directRetries = 3;
for (int directAttempt = 1; directAttempt <= directRetries; directAttempt++) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(25))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
directSuccessCount++;
return response.body();
}
if (response.statusCode() >= 400) {
throw new IOException("HTTP " + response.statusCode());
}
} catch (Exception e) {
if (isConnectionError(e)) {
directFailCount++;
if (directAttempt < directRetries) {
System.out.println(ZAnsi.yellow("[NET] Direct retry " + directAttempt + "/" + directRetries + " for " + url.substring(0, Math.min(60, url.length())) + "..."));
try { Thread.sleep(1000 * directAttempt); } catch (InterruptedException ie) { break; }
continue;
}
markServiceAsBlocked(url);
} else {
throw e;
}
}
break;
}
}
int maxRetries = 3;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try { try {
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(proxyUrl))
.timeout(Duration.ofSeconds(25)) .timeout(Duration.ofSeconds(40))
.header("User-Agent", "ZernMC-Launcher/1.0") .header("User-Agent", "ZernMC-Launcher/1.0")
.GET() .GET()
.build(); .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { if (response.statusCode() != 200) {
directSuccessCount++; throw new IOException("Proxy HTTP " + response.statusCode());
return response.body();
} }
if (response.statusCode() >= 400) { proxySuccessCount++;
throw new IOException("HTTP " + response.statusCode()); return response.body();
}
} catch (Exception e) { } catch (Exception e) {
if (isConnectionError(e)) { if (attempt == maxRetries || !isConnectionError(e)) {
directFailCount++; throw new IOException("Failed to fetch data directly or via proxy: " + e.getMessage(), e);
markServiceAsBlocked(url);
} }
try { Thread.sleep(1000 * attempt); } catch (InterruptedException ie) { break; }
} }
} }
try { throw new IOException("Failed to fetch data: " + url);
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(proxyUrl))
.timeout(Duration.ofSeconds(40))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("Proxy HTTP " + response.statusCode());
}
proxySuccessCount++;
return response.body();
} catch (Exception e) {
throw new IOException("Failed to fetch data directly or via proxy: " + e.getMessage(), e);
}
} }
public static void downloadFileWithSmartProxy(String url, Path target) throws Exception { public static void downloadFileWithSmartProxy(String url, Path target) throws Exception {
if (!shouldUseProxyForUrl(url)) { if (!shouldUseProxyForUrl(url)) {
try { int directRetries = 3;
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() for (int directAttempt = 1; directAttempt <= directRetries; directAttempt++) {
.uri(URI.create(url)) try {
.timeout(Duration.ofSeconds(40)) HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.header("User-Agent", "ZernMC-Launcher/1.0") .uri(URI.create(url))
.GET(); .timeout(Duration.ofSeconds(40))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET();
if (url.startsWith(BASE_URL)) { if (url.startsWith(BASE_URL)) {
String accessToken = AuthManager.getAccessToken(); String accessToken = AuthManager.getAccessToken();
if (accessToken != null && !accessToken.equals("0")) { if (accessToken != null && !accessToken.equals("0")) {
requestBuilder.header("Authorization", "Bearer " + accessToken); requestBuilder.header("Authorization", "Bearer " + accessToken);
}
}
HttpRequest request = requestBuilder.build();
HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() == 200) {
directSuccessCount++;
return;
}
} catch (Exception e) {
if (isConnectionError(e)) {
directFailCount++;
if (directAttempt < directRetries) {
System.out.println(ZAnsi.yellow("[NET] Direct download retry " + directAttempt + "/" + directRetries + " for " + url.substring(0, Math.min(60, url.length())) + "..."));
try { Thread.sleep(1000 * directAttempt); } catch (InterruptedException ie) { break; }
continue;
}
markServiceAsBlocked(url);
} else {
throw e;
} }
} }
break;
HttpRequest request = requestBuilder.build();
HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() == 200) {
directSuccessCount++;
return;
}
} catch (Exception e) {
if (isConnectionError(e)) {
directFailCount++;
markServiceAsBlocked(url);
}
} }
} }
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); int maxRetries = 3;
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl; for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(proxyUrl)) .uri(URI.create(proxyUrl))
.timeout(Duration.ofMinutes(5)) .timeout(Duration.ofMinutes(5))
.header("User-Agent", "ZernMC-Launcher/1.0") .header("User-Agent", "ZernMC-Launcher/1.0")
.GET() .GET()
.build(); .build();
HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(target)); HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
throw new IOException("Proxy download failed: HTTP " + response.statusCode()); throw new IOException("Proxy download failed: HTTP " + response.statusCode());
}
proxySuccessCount++;
return;
} catch (Exception e) {
if (attempt == maxRetries || !isConnectionError(e)) {
throw new IOException("Proxy download failed: " + e.getMessage(), e);
}
try { Thread.sleep(1000 * attempt); } catch (InterruptedException ie) { break; }
}
} }
proxySuccessCount++;
} }
public static String get(String endpoint) throws IOException, InterruptedException { public static String get(String endpoint) throws IOException, InterruptedException {
+178 -178
View File
@@ -1,179 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>me.sashegdev</groupId> <groupId>me.sashegdev</groupId>
<artifactId>ZernMCLauncher</artifactId> <artifactId>ZernMCLauncher</artifactId>
<version>${revision}</version> <version>${revision}</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>ZernMC Launcher Parent</name> <name>ZernMC Launcher Parent</name>
<description>ZernMC Launcher - Multi-module project</description> <description>ZernMC Launcher - Multi-module project</description>
<modules> <modules>
<module>bootstrap</module> <module>bootstrap</module>
<module>launcher</module> <module>launcher</module>
</modules> </modules>
<properties> <properties>
<revision>1.0.14</revision> <revision>1.0.14</revision>
<hotfix>1</hotfix> <hotfix>2</hotfix>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.organization.name>ZernMC</project.organization.name> <project.organization.name>ZernMC</project.organization.name>
<project.inceptionYear>2026</project.inceptionYear> <project.inceptionYear>2026</project.inceptionYear>
<project.description>ZernMC Launcher - Multi-module project</project.description> <project.description>ZernMC Launcher - Multi-module project</project.description>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.5.14</version> <version>4.5.14</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <version>2.15.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.10.1</version> <version>2.10.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20231013</version> <version>20231013</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.fusesource.jansi</groupId> <groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId> <artifactId>jansi</artifactId>
<version>2.4.1</version> <version>2.4.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jline</groupId> <groupId>org.jline</groupId>
<artifactId>jline</artifactId> <artifactId>jline</artifactId>
<version>3.24.1</version> <version>3.24.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>me.tongfei</groupId> <groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId> <artifactId>progressbar</artifactId>
<version>0.9.5</version> <version>0.9.5</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.15.1</version> <version>2.15.1</version>
</dependency> </dependency>
<!-- JavaFX for Windows --> <!-- JavaFX for Windows -->
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId> <artifactId>javafx-controls</artifactId>
<version>23</version> <version>23</version>
<classifier>win</classifier> <classifier>win</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId> <artifactId>javafx-web</artifactId>
<version>23</version> <version>23</version>
<classifier>win</classifier> <classifier>win</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId> <artifactId>javafx-graphics</artifactId>
<version>23</version> <version>23</version>
<classifier>win</classifier> <classifier>win</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId> <artifactId>javafx-base</artifactId>
<version>23</version> <version>23</version>
<classifier>win</classifier> <classifier>win</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.openjfx</groupId> <groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId> <artifactId>javafx-media</artifactId>
<version>23</version> <version>23</version>
<classifier>win</classifier> <classifier>win</classifier>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId> <artifactId>flatten-maven-plugin</artifactId>
<version>1.6.0</version> <version>1.6.0</version>
<configuration> <configuration>
<updatePomFile>true</updatePomFile> <updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode> <flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>flatten</id> <id>flatten</id>
<phase>process-resources</phase> <phase>process-resources</phase>
<goals><goal>flatten</goal></goals> <goals><goal>flatten</goal></goals>
</execution> </execution>
<execution> <execution>
<id>flatten-clean</id> <id>flatten-clean</id>
<phase>clean</phase> <phase>clean</phase>
<goals><goal>clean</goal></goals> <goals><goal>clean</goal></goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.2.3</version> <version>3.2.3</version>
</plugin> </plugin>
<!-- Shade Plugin --> <!-- Shade Plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version> <version>3.5.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<outputFile>../../server/builds/ZernMCLauncher.jar</outputFile> <outputFile>../../server/builds/ZernMCLauncher.jar</outputFile>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${mainClass}</mainClass> <mainClass>${mainClass}</mainClass>
<manifestEntries> <manifestEntries>
<Implementation-Version>${project.version}.${hotfix}</Implementation-Version> <Implementation-Version>${project.version}.${hotfix}</Implementation-Version>
<Implementation-Title>ZernMC Launcher</Implementation-Title> <Implementation-Title>ZernMC Launcher</Implementation-Title>
<Implementation-Vendor>SashegDev</Implementation-Vendor> <Implementation-Vendor>SashegDev</Implementation-Vendor>
<Implementation-Description>Samopisnui Minecraft-launcher. by SashegDev</Implementation-Description> <Implementation-Description>Samopisnui Minecraft-launcher. by SashegDev</Implementation-Description>
<Implementation-URL>https://github.com/SashegDev/launcher</Implementation-URL> <Implementation-URL>https://github.com/SashegDev/launcher</Implementation-URL>
</manifestEntries> </manifestEntries>
</transformer> </transformer>
</transformers> </transformers>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles> <profiles>
<profile> <profile>
<id>global</id> <id>global</id>
<activation> <activation>
<activeByDefault>true</activeByDefault> <activeByDefault>true</activeByDefault>
</activation> </activation>
<properties> <properties>
<launcher.title>ZernMC Launcher</launcher.title> <launcher.title>ZernMC Launcher</launcher.title>
<server.url>http://87.120.187.36:1582</server.url> <server.url>http://87.120.187.36:1582</server.url>
</properties> </properties>
</profile> </profile>
</profiles> </profiles>
</project> </project>