Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ExceptionUtil & DaemonUtils config cache safe. #1223

Merged
merged 3 commits into from
Nov 28, 2024
Merged
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 @@ -72,6 +72,7 @@
import net.fabricmc.loom.util.ProcessUtil;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper;
import net.fabricmc.loom.util.gradle.daemon.DaemonUtils;
import net.fabricmc.loom.util.service.ScopedServiceFactory;
import net.fabricmc.loom.util.service.ServiceFactory;

Expand Down Expand Up @@ -112,7 +113,7 @@ public void run() {
extension.setDependencyManager(dependencyManager);
dependencyManager.handleDependencies(getProject(), serviceFactory);
} catch (Exception e) {
ExceptionUtil.processException(e, getProject());
ExceptionUtil.processException(e, DaemonUtils.Context.fromProject(getProject()));
disownLock();
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer;
import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger;
import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper;
import net.fabricmc.loom.util.gradle.daemon.DaemonUtils;
import net.fabricmc.loom.util.ipc.IPCClient;
import net.fabricmc.loom.util.ipc.IPCServer;
import net.fabricmc.loom.util.service.ScopedServiceFactory;
Expand Down Expand Up @@ -196,6 +197,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
@Inject
protected abstract ProgressLoggerFactory getProgressLoggerFactory();

@Nested
protected abstract Property<DaemonUtils.Context> getDaemonUtilsContext();

// Prevent Gradle from running two gen sources tasks in parallel
@ServiceReference(SyncTaskBuildService.NAME)
abstract Property<SyncTaskBuildService> getSyncTask();
Expand Down Expand Up @@ -250,6 +254,8 @@ public GenerateSourcesTask(DecompilerOptions decompilerOptions) {
getMaxCachedFiles().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_FILES).orElse(50_000));
getMaxCacheFileAge().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_AGE).orElse(90));

getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject()));

mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class));
}

Expand All @@ -267,7 +273,7 @@ public void run() throws IOException {
try (var timer = new Timer("Decompiled sources")) {
runWithoutCache();
} catch (Exception e) {
ExceptionUtil.processException(e, getProject());
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
}

Expand Down Expand Up @@ -300,7 +306,7 @@ public void run() throws IOException {
runWithCache(fs.getRoot());
}
} catch (Exception e) {
ExceptionUtil.processException(e, getProject());
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
}
}
Expand Down
15 changes: 7 additions & 8 deletions src/main/java/net/fabricmc/loom/util/ExceptionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.util.List;
import java.util.function.BiFunction;

import org.gradle.api.Project;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -60,27 +59,27 @@ public static <E, C extends Throwable> E createDescriptiveWrapper(BiFunction<Str
return constructor.apply(descriptiveMessage, cause);
}

public static void processException(Throwable e, Project project) {
public static void processException(Throwable e, DaemonUtils.Context context) {
Throwable cause = e;
boolean unrecoverable = false;

while (cause != null) {
if (cause instanceof FileSystemUtil.UnrecoverableZipException) {
unrecoverable = true;
} else if (cause instanceof FileSystemException fse) {
printFileLocks(fse.getFile(), project);
printFileLocks(fse.getFile());
break;
}

cause = cause.getCause();
}

if (unrecoverable) {
DaemonUtils.tryStopGradleDaemon(project);
DaemonUtils.tryStopGradleDaemon(context);
}
}

private static void printFileLocks(String filename, Project project) {
private static void printFileLocks(String filename) {
final Path path = Paths.get(filename);

if (!Files.exists(path)) {
Expand All @@ -100,13 +99,13 @@ private static void printFileLocks(String filename, Project project) {
return;
}

final ProcessUtil processUtil = ProcessUtil.create(project);
final ProcessUtil processUtil = ProcessUtil.create(LOGGER.isInfoEnabled() ? ProcessUtil.ArgumentVisibility.SHOW_SENSITIVE : ProcessUtil.ArgumentVisibility.HIDE);

final String noun = processes.size() == 1 ? "process has" : "processes have";
project.getLogger().error("The following {} a lock on the file '{}':", noun, path);
LOGGER.error("The following {} a lock on the file '{}':", noun, path);

for (ProcessHandle process : processes) {
project.getLogger().error(processUtil.printWithParents(process));
LOGGER.error(processUtil.printWithParents(process));
}
}
}
22 changes: 18 additions & 4 deletions src/main/java/net/fabricmc/loom/util/ProcessUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,22 @@
import net.fabricmc.loom.nativeplatform.LoomNativePlatform;
import net.fabricmc.loom.nativeplatform.LoomNativePlatformException;

public record ProcessUtil(LogLevel logLevel) {
public record ProcessUtil(ArgumentVisibility argumentVisibility) {
private static final String EXPLORER_COMMAND = "C:\\Windows\\explorer.exe";
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessUtil.class);

public static ProcessUtil create(Project project) {
return new ProcessUtil(project.getGradle().getStartParameter().getLogLevel());
return create(ArgumentVisibility.get(project));
}

public static ProcessUtil create(ArgumentVisibility argumentVisibility) {
return new ProcessUtil(argumentVisibility);
}

public String printWithParents(ProcessHandle handle) {
String result = printWithParents(handle, 0).trim();

if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) {
if (argumentVisibility == ArgumentVisibility.HIDE) {
return "Run with --info or --debug to show arguments, may reveal sensitive info\n" + result;
}

Expand All @@ -75,7 +79,7 @@ private String printWithParents(ProcessHandle handle, int depth) {
}

private Optional<String> getProcessArguments(ProcessHandle handle) {
if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) {
if (argumentVisibility != ArgumentVisibility.SHOW_SENSITIVE) {
return Optional.empty();
}

Expand Down Expand Up @@ -117,4 +121,14 @@ private Optional<String> getWindowTitles(ProcessHandle processHandle) {

return Optional.of(joiner.toString());
}

public enum ArgumentVisibility {
HIDE,
SHOW_SENSITIVE;

static ArgumentVisibility get(Project project) {
final LogLevel logLevel = project.getGradle().getStartParameter().getLogLevel();
return (logLevel == LogLevel.INFO || logLevel == LogLevel.DEBUG) ? SHOW_SENSITIVE : HIDE;
}
}
}
52 changes: 41 additions & 11 deletions src/main/java/net/fabricmc/loom/util/gradle/daemon/DaemonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@
import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.cache.FileLockManager;
import org.gradle.internal.file.Chmod;
import org.gradle.internal.remote.internal.RemoteConnection;
import org.gradle.internal.remote.internal.inet.TcpOutgoingConnector;
import org.gradle.internal.serialize.Serializers;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.invocation.DefaultGradle;
import org.gradle.launcher.daemon.client.DaemonClientConnection;
import org.gradle.launcher.daemon.client.StopDispatcher;
import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer;
Expand Down Expand Up @@ -63,17 +65,17 @@ private DaemonUtils() {
/**
* Request the Gradle daemon to stop when it becomes idle.
*/
public static void tryStopGradleDaemon(Project project) {
public static void tryStopGradleDaemon(DaemonUtils.Context context) {
try {
stopWhenIdle(project);
stopWhenIdle(context);
} catch (Throwable t) {
LOGGER.error("Failed to request the Gradle demon to stop", t);
}
}

@VisibleForTesting
public static boolean stopWhenIdle(Project project) {
DaemonInfo daemonInfo = findCurrentDaemon(project);
public static boolean stopWhenIdle(DaemonUtils.Context context) {
DaemonInfo daemonInfo = findCurrentDaemon(context);

if (daemonInfo == null) {
return false;
Expand All @@ -98,14 +100,13 @@ public static boolean stopWhenIdle(Project project) {
}

@Nullable
private static DaemonInfo findCurrentDaemon(Project project) {
private static DaemonInfo findCurrentDaemon(DaemonUtils.Context context) {
// Gradle maintains a list of running daemons in a registry.bin file.
final Path registryBin = project.getGradle().getGradleUserHomeDir().toPath().resolve("daemon").resolve(GradleVersion.current().getVersion()).resolve("registry.bin");
project.getLogger().lifecycle("Looking for daemon in: " + registryBin);
final Path registryBin = Path.of(context.getRegistryBin().get());
LOGGER.info("Looking for daemon in: {}", registryBin);

// We can use a PersistentDaemonRegistry to read this
final ServiceRegistry services = ((DefaultGradle) project.getGradle()).getServices();
final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class));
final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), context.getFileLockManager(), context.getChmod());

final long pid = ProcessHandle.current().pid();
final List<DaemonInfo> runningDaemons = registry.getAll();
Expand All @@ -121,4 +122,33 @@ private static DaemonInfo findCurrentDaemon(Project project) {
LOGGER.warn("Could not find current process in daemon registry: {}", registryBin);
return null;
}

public abstract static class Context {
@Input
protected abstract Property<String> getRegistryBin();

@Inject
protected abstract FileLockManager getFileLockManager();

@Inject
protected abstract Chmod getChmod();

@SuppressWarnings("unused")
@Inject
public Context(Project project) {
getRegistryBin().set(Context.getRegistryBinPathName(project));
}

public static Context fromProject(Project project) {
return project.getObjects().newInstance(Context.class, project);
}

private static String getRegistryBinPathName(Project project) {
return project.getGradle().getGradleUserHomeDir().toPath()
.resolve("daemon")
.resolve(GradleVersion.current().getVersion())
.resolve("registry.bin")
.toAbsolutePath().toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class TestPlugin implements Plugin<Project> {
def future = handler.daemonConnection.thenAccept { it.waitForAndProcessStop() }

// Stop the daemon
def result = DaemonUtils.stopWhenIdle(project)
def result = DaemonUtils.stopWhenIdle(DaemonUtils.Context.fromProject(project))

// Wait for the connection to be processed, this should have already happened, as the above call is blocking
future.join()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@

package net.fabricmc.loom.test.unit

import org.gradle.api.logging.LogLevel
import spock.lang.Specification

import net.fabricmc.loom.util.ProcessUtil

class ProcessUtilTest extends Specification {
def "print process info"() {
when:
def output = new ProcessUtil(LogLevel.DEBUG).printWithParents(ProcessHandle.current())
def output = new ProcessUtil(ProcessUtil.ArgumentVisibility.SHOW_SENSITIVE).printWithParents(ProcessHandle.current())

then:
// Just a simple check to see if the output is not empty
Expand Down