Skip to content

Commit 4e57271

Browse files
feat: switch to RW locks
1 parent 34c0082 commit 4e57271

File tree

13 files changed

+219
-76
lines changed

13 files changed

+219
-76
lines changed

shared/src/main/java/net/pistonmaster/pistonqueue/shared/command/MainCommandShared.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import net.pistonmaster.pistonqueue.shared.wrapper.PlayerWrapper;
3333

3434
import java.util.*;
35+
import java.util.concurrent.locks.Lock;
3536

3637
public interface MainCommandShared {
3738
List<String> commands = List.of("help", "version", "stats");
@@ -59,7 +60,7 @@ default void onCommand(CommandSourceWrapper sender, String[] args, PistonQueuePl
5960
sender.sendMessage(component().text(group.getName()).color(TextColorWrapper.GOLD));
6061
for (QueueType type : group.getQueueTypes()) {
6162
sender.sendMessage(component().text(" - " + type.getName() + ": ").color(TextColorWrapper.GOLD)
62-
.append(component().text(String.valueOf(type.getQueueMap().size())).color(TextColorWrapper.GOLD).decorate(TextDecorationWrapper.BOLD)));
63+
.append(component().text(String.valueOf(queueSize(type))).color(TextColorWrapper.GOLD).decorate(TextDecorationWrapper.BOLD)));
6364
}
6465
}
6566
sendLine(sender);
@@ -260,5 +261,15 @@ default void addPlayers(List<String> completions, String[] args, PistonQueuePlug
260261
}
261262
}
262263

264+
private static int queueSize(QueueType type) {
265+
Lock readLock = type.getQueueLock().readLock();
266+
readLock.lock();
267+
try {
268+
return type.getQueueMap().size();
269+
} finally {
270+
readLock.unlock();
271+
}
272+
}
273+
263274
ComponentWrapperFactory component();
264275
}

shared/src/main/java/net/pistonmaster/pistonqueue/shared/hooks/PistonMOTDPlaceholder.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import net.pistonmaster.pistonqueue.shared.config.Config;
2626

2727
import java.util.Locale;
28+
import java.util.concurrent.locks.Lock;
2829

2930
public final class PistonMOTDPlaceholder implements PlaceholderParser {
3031
private final Config config;
@@ -37,8 +38,18 @@ public PistonMOTDPlaceholder(Config config) {
3738
@Override
3839
public String parseString(String s) {
3940
for (QueueType type : config.getAllQueueTypes()) {
40-
s = s.replace("%pistonqueue_" + type.getName().toLowerCase(Locale.ROOT) + "%", String.valueOf(type.getQueueMap().size()));
41+
s = s.replace("%pistonqueue_" + type.getName().toLowerCase(Locale.ROOT) + "%", String.valueOf(queueSize(type)));
4142
}
4243
return s;
4344
}
45+
46+
private static int queueSize(QueueType type) {
47+
Lock readLock = type.getQueueLock().readLock();
48+
readLock.lock();
49+
try {
50+
return type.getQueueMap().size();
51+
} finally {
52+
readLock.unlock();
53+
}
54+
}
4455
}

shared/src/main/java/net/pistonmaster/pistonqueue/shared/plugin/PistonQueuePlugin.java

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.concurrent.CountDownLatch;
4343
import java.util.concurrent.TimeUnit;
4444
import java.util.concurrent.atomic.AtomicInteger;
45+
import java.util.concurrent.locks.Lock;
4546
import java.util.stream.Collectors;
4647

4748
public interface PistonQueuePlugin {
@@ -95,8 +96,9 @@ default void scheduleTasks(QueueListenerShared queueListener) {
9596
}
9697
} else if (config.PAUSE_QUEUE_IF_TARGET_DOWN) {
9798
for (QueueType type : config.getAllQueueTypes()) {
98-
type.getQueueMap().forEach((id, queuedPlayer) ->
99-
getPlayer(id).ifPresent(value -> value.sendMessage(config.PAUSE_QUEUE_IF_TARGET_DOWN_MESSAGE)));
99+
for (UUID uuid : snapshotQueueOrder(type)) {
100+
getPlayer(uuid).ifPresent(value -> value.sendMessage(config.PAUSE_QUEUE_IF_TARGET_DOWN_MESSAGE));
101+
}
100102
}
101103
}
102104
}, config.POSITION_MESSAGE_DELAY, config.POSITION_MESSAGE_DELAY, TimeUnit.MILLISECONDS);
@@ -157,8 +159,7 @@ default void scheduleTasks(QueueListenerShared queueListener) {
157159

158160
default void sendMessage(QueueType queue, MessageType type) {
159161
Config config = getConfiguration();
160-
Map<UUID, QueueType.QueuedPlayer> queueMap = queue.getQueueMap();
161-
List<UUID> queueOrder = snapshotQueueOrder(queueMap);
162+
List<UUID> queueOrder = snapshotQueueOrder(queue);
162163

163164
int totalQueued = queueOrder.size();
164165
int position = 0;
@@ -177,8 +178,7 @@ default void sendMessage(QueueType queue, MessageType type) {
177178
}
178179

179180
default void updateTab(QueueType queue) {
180-
Map<UUID, QueueType.QueuedPlayer> queueMap = queue.getQueueMap();
181-
List<UUID> queueOrder = snapshotQueueOrder(queueMap);
181+
List<UUID> queueOrder = snapshotQueueOrder(queue);
182182

183183
int position = 0;
184184
for (UUID uuid : queueOrder) {
@@ -196,25 +196,35 @@ default void updateTab(QueueType queue) {
196196
}
197197

198198
default String replacePosition(String text, int position, QueueType type) {
199-
if (type.getDurationFromPosition().containsKey(position)) {
200-
Duration duration = type.getDurationFromPosition().get(position);
201-
202-
return SharedChatUtils.formatDuration(text, duration, position);
203-
} else {
204-
Map.Entry<Integer, Duration> biggestEntry = null;
199+
Duration durationForExactPosition = null;
200+
Integer biggestPosition = null;
201+
Duration biggestDuration = null;
205202

206-
for (Map.Entry<Integer, Duration> entry : type.getDurationFromPosition().entrySet()) {
207-
if (biggestEntry == null || entry.getKey() > biggestEntry.getKey()) {
208-
biggestEntry = entry;
203+
Lock readLock = type.getDurationLock().readLock();
204+
readLock.lock();
205+
try {
206+
durationForExactPosition = type.getDurationFromPosition().get(position);
207+
if (durationForExactPosition == null) {
208+
for (Map.Entry<Integer, Duration> entry : type.getDurationFromPosition().entrySet()) {
209+
if (biggestPosition == null || entry.getKey() > biggestPosition) {
210+
biggestPosition = entry.getKey();
211+
biggestDuration = entry.getValue();
212+
}
209213
}
210214
}
215+
} finally {
216+
readLock.unlock();
217+
}
211218

212-
Duration predictedDuration = biggestEntry == null ?
213-
Duration.of(position, ChronoUnit.MINUTES) :
214-
biggestEntry.getValue().plus(position - biggestEntry.getKey(), ChronoUnit.MINUTES);
215-
216-
return SharedChatUtils.formatDuration(text, predictedDuration, position);
219+
if (durationForExactPosition != null) {
220+
return SharedChatUtils.formatDuration(text, durationForExactPosition, position);
217221
}
222+
223+
Duration predictedDuration = biggestDuration == null
224+
? Duration.of(position, ChronoUnit.MINUTES)
225+
: biggestDuration.plus(position - biggestPosition, ChronoUnit.MINUTES);
226+
227+
return SharedChatUtils.formatDuration(text, predictedDuration, position);
218228
}
219229

220230
default void initializeReservationSlots() {
@@ -258,7 +268,7 @@ default void sendCustomData() {
258268
outOnlineQueue.writeInt(config.getAllQueueTypes().size());
259269
for (QueueType queueType : config.getAllQueueTypes()) {
260270
outOnlineQueue.writeUTF(queueType.getName().toLowerCase(Locale.ROOT));
261-
outOnlineQueue.writeInt(queueType.getQueueMap().size());
271+
outOnlineQueue.writeInt(getQueueSize(queueType));
262272
}
263273

264274
ByteArrayDataOutput outOnlineTarget = ByteStreams.newDataOutput();
@@ -304,9 +314,23 @@ default void loadConfig(Path file) throws IOException {
304314
getConfiguration().copyFrom(loaded);
305315
}
306316

307-
private static List<UUID> snapshotQueueOrder(Map<UUID, QueueType.QueuedPlayer> queueMap) {
308-
synchronized (queueMap) {
309-
return new ArrayList<>(queueMap.keySet());
317+
private static List<UUID> snapshotQueueOrder(QueueType queue) {
318+
Lock readLock = queue.getQueueLock().readLock();
319+
readLock.lock();
320+
try {
321+
return new ArrayList<>(queue.getQueueMap().keySet());
322+
} finally {
323+
readLock.unlock();
324+
}
325+
}
326+
327+
private static int getQueueSize(QueueType queue) {
328+
Lock readLock = queue.getQueueLock().readLock();
329+
readLock.lock();
330+
try {
331+
return queue.getQueueMap().size();
332+
} finally {
333+
readLock.unlock();
310334
}
311335
}
312336
}

shared/src/main/java/net/pistonmaster/pistonqueue/shared/queue/QueueListenerShared.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@
3838
import net.pistonmaster.pistonqueue.shared.utils.StorageTool;
3939
import net.pistonmaster.pistonqueue.shared.wrapper.PlayerWrapper;
4040

41-
import java.util.Collections;
42-
import java.util.HashSet;
4341
import java.util.Locale;
4442
import java.util.Set;
43+
import java.util.concurrent.ConcurrentHashMap;
44+
import java.util.concurrent.locks.Lock;
4545

4646
public abstract class QueueListenerShared {
4747
private final PistonQueuePlugin plugin;
4848
@Getter
49-
private final Set<String> onlineServers = Collections.synchronizedSet(new HashSet<>());
49+
private final Set<String> onlineServers = ConcurrentHashMap.newKeySet();
5050
private final QueueEnvironment queueEnvironment;
5151
private final QueuePlacementCoordinator queuePlacementCoordinator;
5252
private final QueueMoveProcessor queueMoveProcessor;
@@ -98,9 +98,14 @@ protected void onKick(PQKickedFromServerEvent event) {
9898

9999
event.getPlayer().sendMessage(config.IF_TARGET_DOWN_SEND_TO_QUEUE_MESSAGE);
100100

101-
config.getQueueType(event.getPlayer())
102-
.getQueueMap()
103-
.put(event.getPlayer().getUniqueId(), new QueueType.QueuedPlayer(event.getKickedFrom(), QueueType.QueueReason.SERVER_DOWN));
101+
QueueType queueType = config.getQueueType(event.getPlayer());
102+
Lock writeLock = queueType.getQueueLock().writeLock();
103+
writeLock.lock();
104+
try {
105+
queueType.getQueueMap().put(event.getPlayer().getUniqueId(), new QueueType.QueuedPlayer(event.getKickedFrom(), QueueType.QueueReason.SERVER_DOWN));
106+
} finally {
107+
writeLock.unlock();
108+
}
104109
});
105110
}
106111

shared/src/main/java/net/pistonmaster/pistonqueue/shared/queue/QueueType.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,22 @@
2525

2626
import java.time.Duration;
2727
import java.time.Instant;
28-
import java.util.*;
28+
import java.util.LinkedHashMap;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.UUID;
2932
import java.util.concurrent.ConcurrentHashMap;
3033
import java.util.concurrent.atomic.AtomicInteger;
34+
import java.util.concurrent.locks.ReadWriteLock;
35+
import java.util.concurrent.locks.ReentrantReadWriteLock;
3136

3237
@Getter
3338
@AllArgsConstructor
3439
public class QueueType {
35-
private final Map<UUID, QueuedPlayer> queueMap = Collections.synchronizedMap(new LinkedHashMap<>());
36-
private final Map<Integer, Duration> durationFromPosition = Collections.synchronizedMap(new LinkedHashMap<>());
40+
private final Map<UUID, QueuedPlayer> queueMap = new LinkedHashMap<>();
41+
private final Map<Integer, Duration> durationFromPosition = new LinkedHashMap<>();
42+
private final ReadWriteLock queueLock = new ReentrantReadWriteLock();
43+
private final ReadWriteLock durationLock = new ReentrantReadWriteLock();
3744
private final Map<UUID, Map<Integer, Instant>> positionCache = new ConcurrentHashMap<>();
3845
private final AtomicInteger playersWithTypeInTarget = new AtomicInteger();
3946
private final String name;

shared/src/main/java/net/pistonmaster/pistonqueue/shared/queue/logic/QueueAvailabilityCalculator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import net.pistonmaster.pistonqueue.shared.queue.QueueType;
2323

24+
import java.util.concurrent.locks.Lock;
25+
2426
/**
2527
* Encapsulates the slot and fullness calculation so it can be reused and
2628
* tested independently from the listener implementation.
@@ -40,6 +42,12 @@ public int getFreeSlots(QueueType type) {
4042
}
4143

4244
private boolean hasQueuedPlayers(QueueType type) {
43-
return !type.getQueueMap().isEmpty();
45+
Lock readLock = type.getQueueLock().readLock();
46+
readLock.lock();
47+
try {
48+
return !type.getQueueMap().isEmpty();
49+
} finally {
50+
readLock.unlock();
51+
}
4452
}
4553
}

shared/src/main/java/net/pistonmaster/pistonqueue/shared/queue/logic/QueueCleaner.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Objects;
3030
import java.util.Optional;
3131
import java.util.UUID;
32+
import java.util.concurrent.locks.Lock;
3233

3334
/**
3435
* Removes stale entries from the queue maps to prevent memory leaks.
@@ -44,8 +45,12 @@ public void cleanGroup(QueueGroup group) {
4445
for (QueueType type : group.getQueueTypes()) {
4546
Map<UUID, QueueType.QueuedPlayer> queueMap = type.getQueueMap();
4647
List<UUID> queueSnapshot;
47-
synchronized (queueMap) {
48+
Lock readLock = type.getQueueLock().readLock();
49+
readLock.lock();
50+
try {
4851
queueSnapshot = new ArrayList<>(queueMap.keySet());
52+
} finally {
53+
readLock.unlock();
4954
}
5055

5156
if (queueSnapshot.isEmpty()) {
@@ -62,8 +67,12 @@ public void cleanGroup(QueueGroup group) {
6267
}
6368

6469
if (!staleEntries.isEmpty()) {
65-
synchronized (queueMap) {
70+
Lock writeLock = type.getQueueLock().writeLock();
71+
writeLock.lock();
72+
try {
6673
staleEntries.forEach(queueMap::remove);
74+
} finally {
75+
writeLock.unlock();
6776
}
6877
}
6978
}

0 commit comments

Comments
 (0)