Make ExceptionUtil & DaemonUtils config cache safe. (#1223)

* Make ExceptionUtil & DaemonUtils config cache safe.

* Fix tests

* Fix build
This commit is contained in:
modmuss
2024-11-28 15:00:01 +00:00
committed by GitHub
parent 758dcb748d
commit 38cff6d2bb
7 changed files with 78 additions and 29 deletions

View File

@@ -72,6 +72,7 @@ import net.fabricmc.loom.util.ExceptionUtil;
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;
@@ -112,7 +113,7 @@ public abstract class CompileConfiguration implements Runnable {
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);
}

View File

@@ -98,6 +98,7 @@ import net.fabricmc.loom.util.gradle.SyncTaskBuildService;
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;
@@ -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();
@@ -250,6 +254,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
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));
}
@@ -267,7 +273,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
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);
}
@@ -300,7 +306,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
runWithCache(fs.getRoot());
}
} catch (Exception e) {
ExceptionUtil.processException(e, getProject());
ExceptionUtil.processException(e, getDaemonUtilsContext().get());
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e);
}
}

View File

@@ -31,7 +31,6 @@ import java.nio.file.Paths;
import java.util.List;
import java.util.function.BiFunction;
import org.gradle.api.Project;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,7 +59,7 @@ public final class ExceptionUtil {
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;
@@ -68,7 +67,7 @@ public final class ExceptionUtil {
if (cause instanceof FileSystemUtil.UnrecoverableZipException) {
unrecoverable = true;
} else if (cause instanceof FileSystemException fse) {
printFileLocks(fse.getFile(), project);
printFileLocks(fse.getFile());
break;
}
@@ -76,11 +75,11 @@ public final class ExceptionUtil {
}
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)) {
@@ -100,13 +99,13 @@ public final class ExceptionUtil {
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));
}
}
}

View File

@@ -37,18 +37,22 @@ import org.slf4j.LoggerFactory;
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;
}
@@ -75,7 +79,7 @@ public record ProcessUtil(LogLevel logLevel) {
}
private Optional<String> getProcessArguments(ProcessHandle handle) {
if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) {
if (argumentVisibility != ArgumentVisibility.SHOW_SENSITIVE) {
return Optional.empty();
}
@@ -117,4 +121,14 @@ public record ProcessUtil(LogLevel logLevel) {
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;
}
}
}

View File

@@ -28,14 +28,16 @@ import java.nio.file.Path;
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;
@@ -63,17 +65,17 @@ public final class 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;
@@ -98,14 +100,13 @@ public final class DaemonUtils {
}
@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();
@@ -121,4 +122,33 @@ public final class DaemonUtils {
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();
}
}
}