From f725bb5e0c83ce395c196546249c8f7ab9d70732 Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Thu, 3 Oct 2024 09:39:23 +0200 Subject: [PATCH] fix EarlyConfig NPE from race conditions --- .../lib/internal/config/EarlyConfig.java | 54 ++++++++++++++----- .../core/LowLevelCallMultiplexer.java | 7 +-- .../dependencies/DependencyLoaderImpl.java | 2 +- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java b/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java index 9819e53..8fdb4d3 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java +++ b/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java @@ -24,14 +24,21 @@ package com.falsepattern.lib.internal.config; import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.internal.FalsePatternLib; +import com.falsepattern.lib.internal.Tags; import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; import lombok.Data; import lombok.SneakyThrows; import lombok.experimental.Accessors; import lombok.val; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -43,22 +50,45 @@ public class EarlyConfig { @StableAPI.Expose(since = "__INTERNAL__") private boolean enableLibraryDownloads; - private static EarlyConfig instance = null; + private static volatile EarlyConfig instance = null; - @SneakyThrows - public static EarlyConfig load() { - if (instance != null) - return instance; - val configFile = LowLevelCallMultiplexer.gameDir().resolve("config").resolve("falsepatternlib-early.json"); - val gson = new Gson(); - EarlyConfig config; - if (!Files.exists(configFile)) { + private static final Logger LOG = LogManager.getLogger(Tags.MODNAME + " Early Config"); + + public static @NotNull EarlyConfig getInstance() { + val config = instance; + if (config != null) + return config; + return loadFromDisk(); + } + + private static synchronized @NotNull EarlyConfig loadFromDisk() { + var config = instance; + if (config != null) { + return config; + } + val configDir = LowLevelCallMultiplexer.gameDir().resolve("config"); + val configFile = configDir.resolve("falsepatternlib-early.json"); + val gson = new GsonBuilder().setPrettyPrinting().create(); + try { + if (!Files.exists(configDir)) { + Files.createDirectories(configDir); + } + if (Files.exists(configFile)) { + config = gson.fromJson(new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8), + EarlyConfig.class); + } + } catch (IOException e) { + LOG.error("Failed to load from disk", e); + } + if (config == null) { config = new EarlyConfig(); config.enableLibraryDownloads(true); - } else { - config = gson.fromJson(new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8), EarlyConfig.class); + try { + Files.write(configFile, gson.toJson(config).getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + LOG.error("Failed to write to disk", e); + } } - Files.write(configFile, gson.toJson(config).getBytes(StandardCharsets.UTF_8)); instance = config; return config; } diff --git a/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java b/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java index c292787..4bfff31 100644 --- a/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java +++ b/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java @@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.core; import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.NotNull; import net.minecraft.launchwrapper.Launch; @@ -56,7 +57,7 @@ public class LowLevelCallMultiplexer { } } - public static Path gameDir() { + public static @NotNull Path gameDir() { if (rfbDetected) { return RFBLowLevel.gameDir(); } else { @@ -76,7 +77,7 @@ public class LowLevelCallMultiplexer { return RfbApiImpl.INSTANCE.compatClassLoader().getSources(); } - static Path gameDir() { + static @NotNull Path gameDir() { return RfbApiImpl.INSTANCE.gameDirectory(); } } @@ -90,7 +91,7 @@ public class LowLevelCallMultiplexer { return Launch.classLoader.getSources(); } - static Path gameDir() { + static @NotNull Path gameDir() { return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath(); } } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java index 68ad9df..45f7a9a 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java @@ -805,7 +805,7 @@ public class DependencyLoaderImpl { } private void validateDownloadsAllowed() { - if (!EarlyConfig.load().enableLibraryDownloads()) { + if (!EarlyConfig.getInstance().enableLibraryDownloads()) { val errorMessage = "Failed to load library " + groupId + ":"