Skip to content

Commit 1989f12

Browse files
committed
Remove locking system for Night Config files
This can cause deadlocks if mods themselves are also using their own internal locks (Sophisticated Backpacks does this on 1.16 by using a CHM) This system will be replaced by a command/keybind to manually reload configs
1 parent 5853f9b commit 1989f12

File tree

2 files changed

+26
-100
lines changed

2 files changed

+26
-100
lines changed

forge/src/main/java/org/embeddedt/modernfix/forge/config/ConfigFixer.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
99

1010
import java.util.Optional;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.locks.Lock;
13+
import java.util.concurrent.locks.ReentrantLock;
1114
import java.util.function.Consumer;
1215

1316
public class ConfigFixer {
@@ -35,6 +38,7 @@ public static void replaceConfigHandlers() {
3538
private static class LockingConfigHandler implements Consumer<ModConfig.ModConfigEvent> {
3639
private final Consumer<ModConfig.ModConfigEvent> actualHandler;
3740
private final String modId;
41+
private final Lock lock = new ReentrantLock();
3842

3943
LockingConfigHandler(String id, Consumer<ModConfig.ModConfigEvent> actualHandler) {
4044
this.modId = id;
@@ -43,14 +47,17 @@ private static class LockingConfigHandler implements Consumer<ModConfig.ModConfi
4347

4448
@Override
4549
public void accept(ModConfig.ModConfigEvent modConfigEvent) {
46-
Object cfgObj = NightConfigFixer.toWriteSyncConfig(modConfigEvent.getConfig().getConfigData());
47-
if(cfgObj != null) {
48-
// don't synchronize on 'this' as it produces a deadlock when used alongside NightConfigFixer
49-
synchronized (cfgObj) {
50-
this.actualHandler.accept(modConfigEvent);
51-
}
52-
} else {
53-
this.actualHandler.accept(modConfigEvent);
50+
try {
51+
if(lock.tryLock(2, TimeUnit.SECONDS)) {
52+
try {
53+
this.actualHandler.accept(modConfigEvent);
54+
} finally {
55+
lock.unlock();
56+
}
57+
} else
58+
ModernFix.LOGGER.error("Failed to post config event for {}, someone else is holding the lock", modId);
59+
} catch(InterruptedException e) {
60+
Thread.currentThread().interrupt();
5461
}
5562
}
5663

forge/src/main/java/org/embeddedt/modernfix/forge/config/NightConfigFixer.java

Lines changed: 11 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
package org.embeddedt.modernfix.forge.config;
22

3-
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
4-
import com.electronwill.nightconfig.core.file.FileConfig;
53
import com.electronwill.nightconfig.core.file.FileWatcher;
64
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
7-
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
85
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
96
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
107
import org.embeddedt.modernfix.util.CommonModUtil;
118

129
import java.lang.reflect.Field;
1310
import java.nio.file.Path;
14-
import java.util.Collections;
15-
import java.util.HashMap;
16-
import java.util.Map;
17-
import java.util.Set;
11+
import java.util.LinkedHashSet;
1812
import java.util.concurrent.ConcurrentHashMap;
1913
import java.util.function.Function;
2014

21-
/**
22-
* Relatively simple patch to wait for config saving to finish, made complex by Night Config classes being package-private,
23-
* and Forge not allowing mixins into libraries.
24-
*/
2515
public class NightConfigFixer {
16+
public static final LinkedHashSet<Runnable> configsToReload = new LinkedHashSet<>();
2617
public static void monitorFileWatcher() {
18+
if(!ModernFixMixinPlugin.instance.isOptionEnabled("bugfix.fix_config_crashes.NightConfigFixerMixin"))
19+
return;
2720
CommonModUtil.runWithoutCrash(() -> {
2821
FileWatcher watcher = FileWatcher.defaultInstance();
2922
Field field = FileWatcher.class.getDeclaredField("watchedFiles");
@@ -38,30 +31,6 @@ public static void monitorFileWatcher() {
3831
private static final Class<?> WATCHED_FILE = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.FileWatcher$WatchedFile"));
3932
private static final Field CHANGE_HANDLER = ObfuscationReflectionHelper.findField(WATCHED_FILE, "changeHandler");
4033

41-
public static final Class<?> WRITE_SYNC_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.WriteSyncFileConfig"));
42-
private static final Class<?> AUTOSAVE_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.AutosaveCommentedFileConfig"));
43-
private static final Field AUTOSAVE_FILECONFIG = ObfuscationReflectionHelper.findField(AUTOSAVE_CONFIG, "fileConfig");
44-
45-
private static final Field CURRENTLY_WRITING = ObfuscationReflectionHelper.findField(WRITE_SYNC_CONFIG, "currentlyWriting");
46-
47-
private static final Map<Class<?>, Field> CONFIG_WATCHER_TO_CONFIG_FIELD = Collections.synchronizedMap(new HashMap<>());
48-
49-
private static Field getCurrentlyWritingFieldOnWatcher(Object watcher) {
50-
return CONFIG_WATCHER_TO_CONFIG_FIELD.computeIfAbsent(watcher.getClass(), clz -> {
51-
while(clz != null && clz != Object.class) {
52-
for(Field f : clz.getDeclaredFields()) {
53-
if(CommentedFileConfig.class.isAssignableFrom(f.getType())) {
54-
f.setAccessible(true);
55-
ModernFixMixinPlugin.instance.logger.debug("Found CommentedFileConfig: field '{}' on {}", f.getName(), clz.getName());
56-
return f;
57-
}
58-
}
59-
clz = clz.getSuperclass();
60-
}
61-
return null;
62-
});
63-
}
64-
6534
static class MonitoringMap extends ConcurrentHashMap<Path, Object> {
6635
public MonitoringMap(ConcurrentHashMap<Path, ?> oldMap) {
6736
super(oldMap);
@@ -82,75 +51,25 @@ public Object computeIfAbsent(Path key, Function<? super Path, ?> mappingFunctio
8251
}
8352
}
8453

85-
private static final Set<Class<?>> UNKNOWN_FILE_CONFIG_CLASSES = Collections.synchronizedSet(new ReferenceOpenHashSet<>());
86-
87-
public static Object toWriteSyncConfig(Object config) {
88-
if(config == null)
89-
return null;
90-
try {
91-
if(WRITE_SYNC_CONFIG.isAssignableFrom(config.getClass())) {
92-
return config;
93-
} else if(AUTOSAVE_CONFIG.isAssignableFrom(config.getClass())) {
94-
FileConfig fc = (FileConfig)AUTOSAVE_FILECONFIG.get(config);
95-
return toWriteSyncConfig(fc);
96-
} else {
97-
if (UNKNOWN_FILE_CONFIG_CLASSES.add(config.getClass()))
98-
ModernFixMixinPlugin.instance.logger.warn("Unexpected FileConfig class: {}", config.getClass().getName());
99-
return null;
100-
}
101-
} catch(ReflectiveOperationException e) {
102-
return null;
103-
}
104-
}
105-
10654
static class MonitoringConfigTracker implements Runnable {
10755
private final Runnable configTracker;
10856

10957
MonitoringConfigTracker(Runnable r) {
11058
this.configTracker = r;
11159
}
11260

113-
private void protectFromSaving(FileConfig config, Runnable runnable) throws ReflectiveOperationException {
114-
Object writeSyncConfig = toWriteSyncConfig(config);
115-
if(writeSyncConfig != null) {
116-
// keep trying to write, releasing the config lock each time in case something else needs to lock it
117-
// for any reason
118-
while(true) {
119-
// acquiring synchronized block here should in theory prevent any other concurrent loads/saves, based
120-
// off WriteSyncFileConfig implementation
121-
synchronized (writeSyncConfig) {
122-
if(CURRENTLY_WRITING.getBoolean(writeSyncConfig)) {
123-
ModernFixMixinPlugin.instance.logger.fatal("Config being written during load!!!");
124-
try { Thread.sleep(500); } catch(InterruptedException e) { Thread.currentThread().interrupt(); }
125-
continue;
126-
}
127-
// at this point, currentlyWriting is false, and we acquired synchronized lock, should be good to
128-
// go
129-
runnable.run();
130-
break;
131-
}
132-
}
133-
} else {
134-
runnable.run();
135-
}
136-
}
137-
13861
/**
139-
* This entrypoint runs when the file watcher has detected a change on the config file. Before passing
140-
* this through to Forge, use reflection hacks to confirm the config system is not still writing to the file.
141-
* If it is, spin until writing finishes. Immediately returning might result in the event never being observed.
62+
* Add the config
14263
*/
14364
@Override
14465
public void run() {
145-
try {
146-
Field theField = getCurrentlyWritingFieldOnWatcher(this.configTracker);
147-
if(theField != null) {
148-
CommentedFileConfig cfg = (CommentedFileConfig)theField.get(this.configTracker);
149-
// will synchronize and check saving flag
150-
protectFromSaving(cfg, configTracker);
66+
synchronized(configsToReload) {
67+
int oldSize = configsToReload.size();
68+
configsToReload.add(configTracker);
69+
if(oldSize == 0) {
70+
ModernFixMixinPlugin.instance.logger.info("Config file change detected on disk. The Forge feature to watch config files for changes is currently disabled due to random corruption issues.");
71+
ModernFixMixinPlugin.instance.logger.info("This functionality will be restored in a future ModernFix update.");
15172
}
152-
} catch(ReflectiveOperationException e) {
153-
e.printStackTrace();
15473
}
15574
}
15675
}

0 commit comments

Comments
 (0)