Skip to content

[Forge 1.20.1] Crash: RejectedExecutionException due to small Thread Pool limit with many Feature Steps #10

@NetroAki

Description

@NetroAki

I encountered a crash when loading a world with many mods installed. The crash is a java.util.concurrent.RejectedExecutionException originating from
FeatureRecycler
.

It appears that the mod initializes a ThreadPoolExecutor with a fixed maximum pool size (likely 11, matching the vanilla GenerationStep.Decoration enum length). However, with modded biomes (like those from Terralith/Biomes O' Plenty), the number of feature steps often exceeds 11.

When the mod attempts to submit tasks for these additional steps to the executor, the pool is exhausted (using SynchronousQueue), causing the executor to reject the new task and crash the game.

Crash Log Snippet

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.CompletableFuture$AsyncSupply@... rejected from java.util.concurrent.ThreadPoolExecutor@...
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
	at java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1782)
	at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:2005)
	at dev.corgitaco.featurerecycler.FeatureRecycler.recycle(FeatureRecycler.java:79)

The Fix
I was able to fix this locally by changing the ThreadPoolExecutor to be dynamic (Cached Thread Pool style), allowing it to spawn as many threads as needed for the number of feature steps detected.

Old Code (Inferred):

return new ThreadPoolExecutor(0, 11, // or GenerationStep.Decoration.values().length
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<>(), ...);

New Code (Fix):

// Change max pool size to Integer.MAX_VALUE to allow dynamic expansion
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<>(), (runnable) -> {
            Thread thread = new Thread(runnable);
            thread.setName("Feature-Recycler-Worker-" + WORKER_COUNT.getAndIncrement());
            // ... (uncaught handler)
            return thread;
        });

This ensures that no matter how many feature steps a biome has, the executor can handle the workload without rejecting tasks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions