Fix excessive thread creation (issue #169) - bounded thread pool

Replace unbounded CachedThreadPool with bounded ThreadPoolExecutor.

Problem: CachedThreadPool creates unlimited threads. With many players
online and slow DB queries, thread count explodes (25000+ threads
observed in issue #169), causing memory leaks and server crashes.

Fix: ThreadPoolExecutor with 2 core / 8 max threads, 30s keepalive,
256-task bounded queue, and CallerRunsPolicy for backpressure.
When the queue is full, tasks execute on the calling thread instead
of creating more threads, providing natural flow control.

Closes mlus-asuka/PlayerSync#169

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
laforetbrut 2026-03-26 11:51:09 +01:00
parent f37e58be53
commit 87d320c1f4

View File

@ -57,9 +57,7 @@ import java.nio.file.Path;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
@EventBusSubscriber(modid = PlayerSync.MODID)
@ -67,7 +65,20 @@ public class VanillaSync {
public static void register() {}
static ExecutorService executorService = Executors.newCachedThreadPool(new PSThreadPoolFactory("PlayerSync"));
// FIX: Replace unbounded CachedThreadPool with a bounded ThreadPoolExecutor.
// CachedThreadPool creates unlimited threads with many players and slow DB queries,
// thread count can explode to 25000+ causing memory leaks and server crashes.
// Bounded pool: 2 core threads, max 8 threads, 30s keepalive, 256-task queue.
// If the queue is full, tasks run on the calling thread (CallerRunsPolicy) which
// provides natural backpressure instead of creating more threads.
static ExecutorService executorService = new ThreadPoolExecutor(
2, // core pool size
8, // maximum pool size
30L, TimeUnit.SECONDS, // idle thread keepalive
new LinkedBlockingQueue<>(256), // bounded work queue
new PSThreadPoolFactory("PlayerSync"),
new ThreadPoolExecutor.CallerRunsPolicy() // backpressure: run on caller thread if queue full
);
// Per-player locks to prevent concurrent save/restore operations (anti-duplication)
private static final ConcurrentHashMap<String, ReentrantLock> playerLocks = new ConcurrentHashMap<>();