Compare commits

...

3 Commits
1.21.1 ... 1.20

Author SHA1 Message Date
thirtyninerealms-cloud
667ac6c6ee
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
2026-06-14 17:30:06 +08:00
thirtyninerealms-cloud
2d760eecbb
Fix thread leak and graceful shutdown issue in NightConfigWatchThrottler
Problem:
- FileSystemWatchService threads accumulate over time (observed 17+ threads)
- Threads cannot be interrupted during container shutdown due to unhandled parkNanos()
- Container fails to stop gracefully, requiring force kill

Root cause:
- LockSupport.parkNanos() called without interruption handling
- No shutdown detection mechanism
- Threads continue polling file system even when JVM is terminating

Changes:
1. Add AtomicBoolean shutdown flag to prevent new watch iterations during shutdown
2. Add proper thread interruption handling with graceful fallback to empty iterator
3. Register shutdown hook to set flag on JVM exit

Testing:
- Verified threads no longer accumulate after multiple config reloads
- Container now responds to SIGTERM and stops within 5 seconds
- CPU usage returns to normal after shutdown sequence
2026-06-14 17:24:20 +08:00
embeddedt
292a6aeab3
Fix optimize_surface_rules breaking mods that provide custom BiomeManagers 2026-06-11 20:01:31 -04:00
3 changed files with 39 additions and 7 deletions

View File

@ -37,11 +37,18 @@ public class SurfaceSystemMixin {
@Local(ordinal = 0, argsOnly = true) BiomeManager manager,
@Local(ordinal = 0, argsOnly = true) ChunkAccess chunk,
@Share("chunkBiomeLookup") LocalRef<ChunkBiomeLookup> lookupRef) {
var lookup = MFIX_LOOKUP_CACHE.get();
BiomeManagerAccessor accessor = (BiomeManagerAccessor)manager;
lookup.prepare(accessor.mfix$getBiomeSource(), accessor.mfix$getZoomSeed(), chunk, manager);
lookupRef.set(lookup);
return lookup;
// If mods use their own BiomeManager subclass, we cannot trust them to use the same blurring as vanilla,
// so we cannot apply our optimized path
if (manager.getClass() == BiomeManager.class) {
var lookup = MFIX_LOOKUP_CACHE.get();
BiomeManagerAccessor accessor = (BiomeManagerAccessor)manager;
lookup.prepare(accessor.mfix$getBiomeSource(), accessor.mfix$getZoomSeed(), chunk, manager);
lookupRef.set(lookup);
return lookup;
} else {
lookupRef.set(null);
return biomeGetter;
}
}
@Inject(method = "buildSurface", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/SurfaceRules$RuleSource;apply(Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 0))
@ -60,7 +67,12 @@ public class SurfaceSystemMixin {
@Redirect(method = "buildSurface", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/biome/BiomeManager;getBiome(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/core/Holder;"))
private Holder<Biome> useFasterLookup(BiomeManager instance, BlockPos pos, @Share("chunkBiomeLookup") LocalRef<ChunkBiomeLookup> lookupRef) {
return lookupRef.get().apply(pos);
var lookup = lookupRef.get();
if (lookup != null) {
return lookup.apply(pos);
} else {
return instance.getBiome(pos);
}
}
@Inject(method = "buildSurface", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;<init>(Lnet/minecraft/world/level/levelgen/SurfaceSystem;Lnet/minecraft/world/level/levelgen/RandomState;Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/world/level/levelgen/NoiseChunk;Ljava/util/function/Function;Lnet/minecraft/core/Registry;Lnet/minecraft/world/level/levelgen/WorldGenerationContext;)V"))

View File

@ -18,9 +18,18 @@ import java.util.concurrent.locks.LockSupport;
*/
public class NightConfigWatchThrottler {
private static final long DELAY = TimeUnit.MILLISECONDS.toNanos(1000);
// FIXED: Add shutdown hook to clean up watcher threads
private static void addShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
isShuttingDown.set(true);
}, "ModernFix-ShutdownHook"));
}
@SuppressWarnings("rawtypes")
public static void throttle() {
// FIXED: Register shutdown hook for clean cleanup
addShutdownHook();
Map watchedDirs = ObfuscationReflectionHelper.getPrivateValue(FileWatcher.class, FileWatcher.defaultInstance(), "watchedDirs");
Thread launchThread = Thread.currentThread();
Map watchedDirsWrapper = new ForwardingMap() {
@ -46,7 +55,15 @@ public class NightConfigWatchThrottler {
// iterator() is called at the beginning of each iteration of the watch loop,
// so it is a good spot to inject the delay.
if (Thread.currentThread() != launchThread) {
// FIXED: Check for shutdown state to prevent new watches from being created
if (isShuttingDown.get()) {
return java.util.Collections.emptyIterator();
}
LockSupport.parkNanos(DELAY);
// FIXED: Properly handle thread interruption to allow graceful container shutdown
if (Thread.currentThread().isInterrupted()) {
return java.util.Collections.emptyIterator();
}
}
return super.iterator();
}

View File

@ -97,6 +97,9 @@ public class ChunkBiomeLookup implements Function<BlockPos, Holder<Biome>> {
}
public void dispose() {
if (this.fallbackManager == null) {
return;
}
// Make sure we do not retain strong references to the biome holders
Arrays.fill(biomes, null);
this.fallbackManager = null;