Skip to content

Task 1-3: Thread Pool cho Machine Connections

Phase: 2 Priority: Medium Module: WawaServer, PhysicMachineThread Depends 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 — WawaServer nên được inject ExecutorService qua constructor injection thay vì tự tạo, để Spring quản lý shutdown (@PreDestroy threadPool.shutdown()). PhysicMachineThread là non-Spring thread (từ main()), cần truyền ExecutorService qua constructor hoặc static factory. Tránh dùng Executors.newCachedThreadPool() nếu không có bounded — prefer ThreadPoolExecutor với explicit bounds và RejectedExecutionHandler.

  • [ ] Thêm server.max-connections vào config.properties:
# Maximum concurrent machine connections
server.max-connections=500
  • [ ] Tạo ExecutorService với bounded thread pool trong WawaServer:
// 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ằng connectionPool.submit(...) trong iniConnections():
// BEFORE:
new Thread(() -> {
    ConnectedClient client = new ConnectedClient(socket);
    client.readMessages();
}).start();

// AFTER:
connectionPool.submit(() -> {
    ConnectedClient client = new ConnectedClient(socket);
    client.readMessages();
});
  • [ ] SocketMonitor threads: tạo separate ScheduledExecutorService monitorPool hoặc reuse connectionPool — không dùng new 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 RejectedExecutionHandler log 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 JMX java.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-connections thay đổi trong config → effective sau restart, không cần code change

Files to Modify

  • src/main/java/.../WawaServer.java
  • src/main/java/.../PhysicMachineThread.java
  • src/main/resources/config.properties