Task 1-2: Synchronized gPackageId để tránh Race Condition
Phase: 1 Priority: High Module:
ConnectedClientDepends on: Không có Reference: docs/BountyHunter-ControlServer/details/feature-iot-bridge/SPEC.md
Background
gPackageId là int instance field trong ConnectedClient, được mutate từ 2 thread khác nhau mà không có synchronization: (1) socket-reading thread đọc/ghi trong queryMachineState() và queryGameResult(), và (2) JMS command thread ghi trong startGame(). Java int không đảm bảo atomic read-modify-write, nên 2 threads có thể đọc cùng giá trị, increment riêng lẻ, và write cùng 1 giá trị mới — tạo ra packet ID trùng lặp. Khi máy nhận 2 packets với cùng ID, nó sẽ reject lệnh thứ 2 hoặc behave không xác định theo binary protocol spec.
Tasks
Note:
AtomicIntegerlà lựa chọn tốt hơnsynchronizedmethod vì nó lock-free (dùng CAS) và không block socket-reading thread khi JMS thread đang increment. Tuy nhiên, nếustartGame()cần đọcgPackageIdVÀ build packet trong 1 atomic operation, cần xem xétsynchronizedblock rộng hơn. Kiểm tra xemexecuteGameAction()(nếu tồn tại) có cần synchronize toàn bộ sequence không.
- [ ] Đổi khai báo
gPackageIdtừintsangAtomicInteger:
// BEFORE:
private int gPackageId = 1;
// AFTER:
private final AtomicInteger gPackageId = new AtomicInteger(1);
- [ ] Tìm và thay thế tất cả call sites sử dụng
gPackageId:
// BEFORE (pattern phổ biến):
gPackageId++;
buildPacket(gPackageId, ...);
// AFTER — getAndIncrement() trả về giá trị TRƯỚC khi tăng:
int currentId = gPackageId.getAndIncrement();
buildPacket(currentId, ...);
- [ ] Kiểm tra
startGame(),queryMachineState(),queryGameResult()— mỗi method phải dùng 1 lầngetAndIncrement()duy nhất per packet gửi - [ ] Nếu
gPackageIdđược reset về 0 hoặc 1 ở bất kỳ đâu, đổi sanggPackageId.set(1) - [ ] Xem xét
executeGameAction(): nếu có thể bị gọi đồng thời từ nhiều JMS messages, cầnsynchronizedblock bao gồm toàn bộ "build packet → send" sequence để tránh interleaved bytes trên socket output stream:
private synchronized void executeGameAction(int command, Object params) {
int packetId = gPackageId.getAndIncrement();
byte[] packet = buildPacket(packetId, command, params);
sendDataToMachine(packet);
}
- [ ] Review
out.write()(OutputStream của socket) —OutputStreamkhông thread-safe, nên toàn bộ write sequence cũng cần synchronized
Verification / Acceptance Criteria
- [ ] Stress test: 2 threads đồng thời gọi
startGame()vàmoveLeft()1000 lần mỗi thread → tất cả packet IDs là unique, không có giá trị trùng lặp - [ ] Packet IDs tăng dần (monotonically increasing) và không bị reset giữa chừng trừ khi intentional
- [ ] Không có
ClassCastExceptionhoặc compilation error sau khi đổi sangAtomicInteger(int → AtomicInteger requires updating all usages) - [ ] Unit test: concurrent
startGame()calls từ executor pool → verifygPackageId.get()equals số lần gọi + 1
Files to Modify
src/main/java/.../ConnectedClient.java