Minecraft Version: 1.21.1
NeoForge Version: 21.1.219
Steps to Reproduce:
- Set maxThreads to 1 in fml.toml.
- Expect to see any deadlock issues during initialization (due to faulty mods) fixed since maxThreads is 1.
- See in a thread dump that Common and Sided Setup run with more than one thread anyway.
Description of issue:
maxThreads settings is not respected during the Common and Sided Setup phases of initialization. This is due to the tasks being submitted to the wrong executor:
In ClientModLoader.java the game calls NeoForge's resource loading hook which starts Common and Sided setup:
private static CompletableFuture<Void> onResourceReload(final PreparableReloadListener.PreparationBarrier stage, final ResourceManager resourceManager, final ProfilerFiller prepareProfiler, final ProfilerFiller executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) {
return CompletableFuture.runAsync(() -> startModLoading(syncExecutor, asyncExecutor), ModWorkManager.parallelExecutor())
.thenCompose(stage::wait)
.thenRunAsync(() -> finishModLoading(syncExecutor, asyncExecutor), ModWorkManager.parallelExecutor());
}
However, note that the executor passed to startModLoading(), which later calls load() which submits the mod setup tasks, is not the executor that has a configurable thread count (which is ModWorkManager.parallelExecutor()), but instead, asyncExecutor is passed instead, which seems to be a thread pool from the vanilla game.
ModWorkManager.parallelExecutor() is passed instead to runAsync, so the function that submits the tasks runs in ModWorkManager.parallelExecutor(), but the tasks themselves run in the vanilla game's executor.
This means that regardless of the max thread count configured for ModWorkManager.parallelExecutor(), mods will be loaded in the vanilla game's executor which has as many threads as CPUs are available.
This makes mitigating the classloader deadlock problems of the modpack by reducing the thread count impossible.
Swapping the two executors, that is, leaving the code like this, fixed the issue for me:
private static CompletableFuture<Void> onResourceReload(final PreparableReloadListener.PreparationBarrier stage, final ResourceManager resourceManager, final ProfilerFiller prepareProfiler, final ProfilerFiller executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) {
return CompletableFuture.runAsync(() -> startModLoading(syncExecutor, ModWorkManager.parallelExecutor()), asyncExecutor)
.thenCompose(stage::wait)
.thenRunAsync(() -> finishModLoading(syncExecutor, ModWorkManager.parallelExecutor()), asyncExecutor);
}
Minecraft Version: 1.21.1
NeoForge Version: 21.1.219
Steps to Reproduce:
Description of issue:
maxThreads settings is not respected during the Common and Sided Setup phases of initialization. This is due to the tasks being submitted to the wrong executor:
In ClientModLoader.java the game calls NeoForge's resource loading hook which starts Common and Sided setup:
However, note that the executor passed to startModLoading(), which later calls load() which submits the mod setup tasks, is not the executor that has a configurable thread count (which is ModWorkManager.parallelExecutor()), but instead, asyncExecutor is passed instead, which seems to be a thread pool from the vanilla game.
ModWorkManager.parallelExecutor() is passed instead to runAsync, so the function that submits the tasks runs in ModWorkManager.parallelExecutor(), but the tasks themselves run in the vanilla game's executor.
This means that regardless of the max thread count configured for ModWorkManager.parallelExecutor(), mods will be loaded in the vanilla game's executor which has as many threads as CPUs are available.
This makes mitigating the classloader deadlock problems of the modpack by reducing the thread count impossible.
Swapping the two executors, that is, leaving the code like this, fixed the issue for me: