Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -872,19 +872,11 @@ else if (!isWindows && !(scriptExtension.equalsIgnoreCase("sh") || scriptExtensi
throw new ExecutionPolicyLimitException();
}

/// Starts the process output pumps and exit monitor.
private void startMonitors(ManagedProcess managedProcess, Path nativeFolder, ProcessListener processListener, Charset encoding, boolean isDaemon) {
processListener.setProcess(managedProcess);
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
processListener.onLog(it, false);
managedProcess.addLine(it);
}, encoding), "stdout-pump", isDaemon);
managedProcess.addRelatedThread(stdout);
Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {
processListener.onLog(it, true);
managedProcess.addLine(it);
}, encoding), "stderr-pump", isDaemon);
managedProcess.addRelatedThread(stderr);
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> {
List<Thread> joins = new ArrayList<>(2);
ExitWaiter exitWaiter = new ExitWaiter(managedProcess, joins, (exitCode, exitType) -> {
processListener.onExit(exitCode, exitType);

if (StringUtils.isNotBlank(options.getPostExitCommand())) {
Expand All @@ -896,7 +888,20 @@ private void startMonitors(ManagedProcess managedProcess, Path nativeFolder, Pro
LOG.warning("An Exception happened while running exit command.", e);
}
}
}), "exit-waiter", isDaemon));
});
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
processListener.onLog(it, false);
exitWaiter.onLog(it);
}, encoding), "stdout-pump", isDaemon);
managedProcess.addRelatedThread(stdout);
joins.add(stdout);
Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {
processListener.onLog(it, true);
exitWaiter.onLog(it);
}, encoding), "stderr-pump", isDaemon);
managedProcess.addRelatedThread(stderr);
joins.add(stderr);
managedProcess.addRelatedThread(Lang.thread(exitWaiter, "exit-waiter", isDaemon));
}

private record Command(
Expand Down
41 changes: 32 additions & 9 deletions HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import org.jackhuang.hmcl.util.platform.OperatingSystem;

import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;

/**
Expand All @@ -38,6 +38,10 @@ final class ExitWaiter implements Runnable {
private final ManagedProcess process;
private final Collection<Thread> joins;
private final BiConsumer<Integer, ProcessListener.ExitType> watcher;
/// Whether the output indicates that the JVM failed to start.
private volatile boolean jvmLaunchFailed;
/// Whether the output indicates that the game failed.
private volatile boolean applicationFailed;

/**
* Constructor.
Expand All @@ -51,6 +55,31 @@ public ExitWaiter(ManagedProcess process, Collection<Thread> joins, BiConsumer<I
this.watcher = watcher;
}

/// Records exit indicators from a process output line.
///
/// @param line the process output line
void onLog(String line) {
if (jvmLaunchFailed && applicationFailed)
return;

String lowerCaseLine = line.toLowerCase(Locale.ROOT);
boolean detectedJvmLaunchFailure = !jvmLaunchFailed && StringUtils.containsOne(lowerCaseLine,
"could not create the java virtual machine.",
"error occurred during initialization of vm",
"a fatal exception has occurred. program will exit.");
boolean detectedApplicationFailure = !applicationFailed && StringUtils.containsOne(lowerCaseLine,
"crash report saved to", "could not save crash report to", "this crash report has been saved to:",
"unable to launch", "an exception was thrown, the game will display an error screen and halt.");

if (!(detectedJvmLaunchFailure || detectedApplicationFailure) || !Log4jLevel.guessLogLineError(line))
return;

if (detectedJvmLaunchFailure)
jvmLaunchFailed = true;
if (detectedApplicationFailure)
applicationFailed = true;
}

@Override
public void run() {
try {
Expand All @@ -59,19 +88,13 @@ public void run() {
for (Thread thread : joins)
thread.join();

List<String> errorLines = process.getLines(Log4jLevel::guessLogLineError);
ProcessListener.ExitType exitType;

// LaunchWrapper will catch the exception logged and will exit normally.
if (exitCode != 0 && StringUtils.containsOne(errorLines,
"Could not create the Java Virtual Machine.",
"Error occurred during initialization of VM",
"A fatal exception has occurred. Program will exit.")) {
if (exitCode != 0 && jvmLaunchFailed) {
EventBus.EVENT_BUS.fireEvent(new JVMLaunchFailedEvent(this, process));
exitType = ProcessListener.ExitType.JVM_ERROR;
} else if (exitCode != 0 || StringUtils.containsOne(errorLines,
"Crash report saved to", "Could not save crash report to", "This crash report has been saved to:",
"Unable to launch", "An exception was thrown, the game will display an error screen and halt.")) {
} else if (exitCode != 0 || applicationFailed) {
EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process));

if (exitCode == 137 && OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
Expand Down