Skip to content

Task 1-1: Fix Commented-Out machineSocketClose trong handleSocketClose(SocketClose)

Phase: 1 Priority: High Module: ConnectedClient Depends on: Không có Reference: docs/BountyHunter-ControlServer/details/feature-iot-bridge/SPEC.md

Background

ConnectedClient có 2 overload của handleSocketClose: overload không tham số gọi adminRMCCraneMachineClient.machineSocketClose(macIp) bình thường, nhưng overload nhận tham số SocketClose socketClose (triggered khi socket đóng kèm reason từ binary protocol) lại có dòng này bị comment out. Hậu quả là khi máy gửi close packet với reason code (ví dụ: lỗi nguồn, lỗi cơ học), backend KHÔNG nhận được thông báo, machine vẫn hiển thị ONLINE trên admin dashboard dù TCP socket đã đóng. Đây là silent bug có thể dẫn đến player bị charged nhưng máy không hoạt động.

Tasks

Note: Cả 2 overload đều chạy trên connection-reading thread. Không cần synchronization riêng vì machineSocketClose() là outbound HTTP/JMS call và không mutate shared state trong ConnectedClient. Tuy nhiên, cần đảm bảo không gọi 2 lần nếu cả 2 code paths đều active — dùng volatile boolean closeCalled guard nếu cần.

  • [ ] Chạy git blame ConnectedClient.java để xác định commit đã comment out dòng đó và đọc commit message
  • [ ] Nếu comment là unintentional (không có lý do rõ ràng trong commit): uncomment adminRMCCraneMachineClient.machineSocketClose(macIp) và thêm log
  • [ ] Nếu comment là intentional (ví dụ: throttle, duplicate call): thêm comment giải thích rõ lý do và xem xét dùng guard flag để tránh double-call với overload kia
  • [ ] Verify rằng macIp đã được set trước khi handleSocketClose(SocketClose) được gọi (tức là handshake đã hoàn thành)
// BEFORE — bị comment out trong handleSocketClose(SocketClose socketClose):
private void handleSocketClose(SocketClose socketClose) {
    log.info("Socket close with reason: {}", socketClose.getReason());
    // adminRMCCraneMachineClient.machineSocketClose(macIp);  ← BUG: không gọi backend
}

// AFTER — cả 2 overload đều notify backend:
private void handleSocketClose(SocketClose socketClose) {
    log.info("[{}] Socket close with reason: {}", macIp, socketClose.getReason());
    adminRMCCraneMachineClient.machineSocketClose(macIp);  // ← uncomment
}

private void handleSocketClose() {
    log.info("[{}] Socket close (no reason)", macIp);
    adminRMCCraneMachineClient.machineSocketClose(macIp);
}
  • [ ] Nếu lo ngại double-call: thêm AtomicBoolean closeNotified guard:
private final AtomicBoolean closeNotified = new AtomicBoolean(false);

private void notifyMachineOffline() {
    if (closeNotified.compareAndSet(false, true)) {
        log.info("[{}] Notifying backend: machine offline", macIp);
        adminRMCCraneMachineClient.machineSocketClose(macIp);
    }
}

Verification / Acceptance Criteria

  • [ ] Khi TCP socket đóng với reason (overload handleSocketClose(SocketClose) được trigger) → backend nhận machineSocketClose event
  • [ ] Machine status chuyển từ ONLINE sang OFFLINE trên admin dashboard sau socket close với reason
  • [ ] Log line xác nhận machineSocketClose được gọi, bao gồm macIp và reason
  • [ ] Không có double-call (2 lần gọi machineSocketClose) nếu cả 2 overloads đều được trigger cho cùng 1 disconnect event
  • [ ] Unit test: mock adminRMCCraneMachineClient, gọi handleSocketClose(new SocketClose(...)) → verify machineSocketClose(macIp) được gọi đúng 1 lần

Files to Modify

  • src/main/java/.../ConnectedClient.java