Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,31 @@

import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

public class LocalAuctionClusterBridge implements AuctionClusterBridge {

private final ConcurrentHashMap<UUID, UUID> itemLocks = new ConcurrentHashMap<>();
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConcurrentHashMap is using UUID as the key type, but item.getId() returns an int. This causes a type mismatch that will result in a compilation error. The map should be declared as ConcurrentHashMap<Integer, UUID> to match the item ID type.

Suggested change
private final ConcurrentHashMap<UUID, UUID> itemLocks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, UUID> itemLocks = new ConcurrentHashMap<>();

Copilot uses AI. Check for mistakes.

@Override
public CompletableFuture<Boolean> checkAvailability(Item item) {
return CompletableFuture.completedFuture(true);
return CompletableFuture.completedFuture(!itemLocks.containsKey(item.getId()));
}

@Override
public CompletableFuture<LockToken> lockItem(Item item, UUID buyerId, StorageType storageType) {
UUID existingLock = itemLocks.putIfAbsent(item.getId(), buyerId);

if (existingLock != null) {
return CompletableFuture.failedFuture(new IllegalStateException("Item already locked by another player"));
}

return CompletableFuture.completedFuture(LockToken.of(item));
}

@Override
public CompletableFuture<Void> unlockItem(Item item, LockToken lockToken, StorageType storageType) {
itemLocks.remove(item.getId());
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unlockItem method does not verify that the caller actually owns the lock before removing it. Any code with any LockToken can unlock an item, even if it was locked by a different buyer. This could allow malicious code or bugs to unlock items they don't own. Consider verifying that the buyerId matches the stored lock owner before allowing the unlock operation.

Suggested change
itemLocks.remove(item.getId());
UUID currentOwner = itemLocks.get(item.getId());
// If there is no current lock, consider it already unlocked.
if (currentOwner == null) {
return CompletableFuture.completedFuture(null);
}
// Verify that the lock token belongs to the current owner before unlocking.
UUID tokenBuyerId = lockToken.getBuyerId();
if (!currentOwner.equals(tokenBuyerId)) {
return CompletableFuture.failedFuture(
new IllegalStateException("Lock token does not match the current lock owner"));
}
itemLocks.remove(item.getId(), currentOwner);

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actual argument type 'Integer' is incompatible with expected argument type 'UUID'.

Copilot uses AI. Check for mistakes.
return CompletableFuture.completedFuture(null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ public CompletableFuture<Void> purchaseItem(Player player, Item item) {
auctionManager.openMainAuction(player);
return CompletableFuture.completedFuture(null);
}

if (item.getStatus() != ItemStatus.IS_PURCHASE_CONFIRM) {
logger.info("Item not available");
auctionManager.openMainAuction(player);
return CompletableFuture.completedFuture(null);

synchronized(item) {
if (item.getStatus() != ItemStatus.IS_PURCHASE_CONFIRM) {
logger.info("Item not available");
auctionManager.openMainAuction(player);
return CompletableFuture.completedFuture(null);
}
item.setStatus(ItemStatus.IS_BEING_PURCHASED);
}
Comment on lines +50 to 57
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The status check and status change are wrapped in a synchronized block, but this doesn't prevent race conditions when different threads have different Item object instances representing the same database record. Two threads could each have their own Item instance (same ID but different objects), pass the status check, and both set the status to IS_BEING_PURCHASED. The synchronized block only works if all threads use the exact same object instance, which is not guaranteed in a distributed or multi-request scenario.

Suggested change
synchronized(item) {
if (item.getStatus() != ItemStatus.IS_PURCHASE_CONFIRM) {
logger.info("Item not available");
auctionManager.openMainAuction(player);
return CompletableFuture.completedFuture(null);
}
item.setStatus(ItemStatus.IS_BEING_PURCHASED);
}
if (item.getStatus() != ItemStatus.IS_PURCHASE_CONFIRM) {
logger.info("Item not available");
auctionManager.openMainAuction(player);
return CompletableFuture.completedFuture(null);
}
item.setStatus(ItemStatus.IS_BEING_PURCHASED);

Copilot uses AI. Check for mistakes.

item.setStatus(ItemStatus.IS_BEING_PURCHASED);

// 2. Vérifier si l'item est lock
return clusterBridge.checkAvailability(item).thenCompose(available -> {

Expand Down
Loading