Task 1-3: Thread Pool cho Machine Connections
Phase: 2 Priority: Medium Module:
WawaServer,PhysicMachineThreadDepends on: Không có Reference: docs/BountyHunter-ControlServer/details/feature-iot-bridge/SPEC.md
Background
WawaServer.iniConnections() tạo new Thread(...).start() cho mỗi TCP connection, không có giới hạn. Mỗi connection còn spawn thêm 1 SocketMonitor-* thread để monitor keepalive — tức là 1 machine = 2 threads. Với 500 machines đang kết nối, JVM phải duy trì 1000 platform threads, mỗi thread chiếm 512KB–1MB stack memory → 500MB–1GB chỉ riêng thread stacks, chưa tính heap. Ngoài ra, context switching overhead tăng theo bậc thang. Hiện tại không có cơ chế reject hay queue khi quá tải, nên server có thể OOM hoàn toàn khi có DDoS hoặc reconnect storm.
Tasks
Note: Spring Bean lifecycle —
WawaServernên được injectExecutorServicequa constructor injection thay vì tự tạo, để Spring quản lý shutdown (@PreDestroy threadPool.shutdown()).PhysicMachineThreadlà non-Spring thread (từmain()), cần truyềnExecutorServicequa constructor hoặc static factory. Tránh dùngExecutors.newCachedThreadPool()nếu không có bounded — preferThreadPoolExecutorvới explicit bounds vàRejectedExecutionHandler.
- [ ] Thêm
server.max-connectionsvàoconfig.properties:
# Maximum concurrent machine connections
server.max-connections=500
- [ ] Tạo
ExecutorServicevới bounded thread pool trongWawaServer:
// Inject qua Spring @Bean hoặc tạo trong constructor:
private final ExecutorService connectionPool;
public WawaServer(@Value("${server.max-connections:500}") int maxConnections) {
this.connectionPool = new ThreadPoolExecutor(
maxConnections / 2, // corePoolSize
maxConnections, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new LinkedBlockingQueue<>(maxConnections), // bounded queue
new ThreadFactory() { ... }, // tên thread: "machine-conn-{n}"
new ThreadPoolExecutor.CallerRunsPolicy() // hoặc AbortPolicy với log
);
}
- [ ] Thay
new Thread(() -> {...}).start()bằngconnectionPool.submit(...)tronginiConnections():
// BEFORE:
new Thread(() -> {
ConnectedClient client = new ConnectedClient(socket);
client.readMessages();
}).start();
// AFTER:
connectionPool.submit(() -> {
ConnectedClient client = new ConnectedClient(socket);
client.readMessages();
});
- [ ]
SocketMonitorthreads: tạo separateScheduledExecutorService monitorPoolhoặc reuseconnectionPool— không dùngnew Thread()trực tiếp - [ ] Thêm
@PreDestroyđể graceful shutdown:
@PreDestroy
public void shutdown() {
connectionPool.shutdown();
try {
if (!connectionPool.awaitTermination(10, TimeUnit.SECONDS)) {
connectionPool.shutdownNow();
}
} catch (InterruptedException e) {
connectionPool.shutdownNow();
Thread.currentThread().interrupt();
}
}
- [ ] Log khi pool bị exhausted: custom
RejectedExecutionHandlerlog WARN với connection count
Verification / Acceptance Criteria
- [ ] 100 concurrent TCP connections → active thread count không vượt
2 * server.max-connections(verify via JMXjava.lang:type=Threading#ThreadCount) - [ ] Khi pool exhausted (submit vượt queue capacity) → new connection bị reject gracefully với log WARN, server KHÔNG crash
- [ ] Server shutdown (
Ctrl+C) →connectionPool.shutdown()được gọi, tất cả active connections được allow to finish trong 10s - [ ] Thread names hiển thị
machine-conn-{n}trong thread dump để dễ debug - [ ]
server.max-connectionsthay đổi trong config → effective sau restart, không cần code change
Files to Modify
src/main/java/.../WawaServer.javasrc/main/java/.../PhysicMachineThread.javasrc/main/resources/config.properties