Skip to content

Commit 8075bc9

Browse files
author
ZDiscord Maintainer
committed
fix: thread safety, resource leaks, dead code, shutdown hangs
- TicketModule: ticketCounter → AtomicInteger (prevents duplicate ticket numbers) - StatusModule: .complete() → .queue() on shutdown (prevents server hang) - DiscordReactionListener: null check on onReactionRemove (prevents NPE) - ConfigManager: try-with-resources for InputStream in migrateIfNeeded/reload - ZDiscordCommand: HashSet → ConcurrentHashMap.newKeySet() for dismissedUpdates - AdvancementListener: remove dead RARITY_THRESHOLD constant - SlashCommandManager: remove dead CONFESSION_COOLDOWN_MS constant - ReactionRoleModule: HashMap → ConcurrentHashMap for thread safety - plugin.yml: add panel/update to usage string
1 parent ce28599 commit 8075bc9

9 files changed

Lines changed: 35 additions & 32 deletions

File tree

src/main/java/dev/demonz/zdiscord/config/ConfigManager.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,12 @@ private void migrateIfNeeded() {
110110
logger.info("Configuration is at v" + current
111111
+ "; current schema is v" + CURRENT_VERSION + ". Merging in any new keys.");
112112

113-
InputStream defStream = defaultResource == null ? null : defaultResource.get();
114-
if (defStream == null) {
115-
logger.warning("Default config.yml not found in JAR; skipping migration.");
116-
return;
117-
}
113+
try (InputStream defStream = defaultResource == null ? null : defaultResource.get()) {
114+
if (defStream == null) {
115+
logger.warning("Default config.yml not found in JAR; skipping migration.");
116+
return;
117+
}
118118

119-
try {
120119
FileConfiguration defaults = YamlConfiguration.loadConfiguration(new InputStreamReader(defStream));
121120
for (String key : defaults.getKeys(true)) {
122121
if (!config.contains(key)) {
@@ -135,6 +134,8 @@ private void migrateIfNeeded() {
135134
StandardCopyOption.REPLACE_EXISTING);
136135
} catch (IOException ignored) {
137136
}
137+
} catch (Exception e) {
138+
logger.warning("Failed to read default config for migration: " + e.getMessage());
138139
}
139140
}
140141

@@ -143,11 +144,14 @@ public void reload() {
143144

144145
// Also merge in defaults from the JAR so that newly added keys are
145146
// visible via getX(path, defaultValue) even if the on-disk file is older.
146-
InputStream defStream = defaultResource == null ? null : defaultResource.get();
147-
if (defStream != null) {
148-
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(new InputStreamReader(defStream));
149-
config.setDefaults(defaults);
150-
config.options().copyDefaults(true);
147+
try (InputStream defStream = defaultResource == null ? null : defaultResource.get()) {
148+
if (defStream != null) {
149+
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(new InputStreamReader(defStream));
150+
config.setDefaults(defaults);
151+
config.options().copyDefaults(true);
152+
}
153+
} catch (Exception e) {
154+
logger.warning("Failed to load default config: " + e.getMessage());
151155
}
152156
}
153157

src/main/java/dev/demonz/zdiscord/discord/SlashCommandManager.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
*/
4747
public class SlashCommandManager extends ListenerAdapter {
4848

49-
private static final long CONFESSION_COOLDOWN_MS = 5 * 60 * 1000L;
50-
5149
private final ZDiscord plugin;
5250

5351
/** discord user id → last confession epoch ms (rate-limit). */

src/main/java/dev/demonz/zdiscord/discord/listeners/DiscordReactionListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public void onMessageReactionAdd(MessageReactionAddEvent event) {
4444

4545
@Override
4646
public void onMessageReactionRemove(MessageReactionRemoveEvent event) {
47+
if (event.getUser() == null || event.getUser().isBot()) {
48+
return;
49+
}
4750
if (plugin.getReactionRoleModule() != null) {
4851
plugin.getReactionRoleModule().onReactionRemove(event);
4952
}

src/main/java/dev/demonz/zdiscord/minecraft/commands/ZDiscordCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import org.bukkit.command.TabCompleter;
2525
import org.bukkit.entity.Player;
2626

27-
import java.util.HashSet;
2827
import java.util.Set;
2928
import java.util.UUID;
29+
import java.util.concurrent.ConcurrentHashMap;
3030

3131
import java.io.File;
3232
import java.io.FileWriter;
@@ -47,7 +47,7 @@
4747
public class ZDiscordCommand implements CommandExecutor, TabCompleter {
4848

4949
private final ZDiscord plugin;
50-
private final Set<UUID> dismissedUpdates = new HashSet<>();
50+
private final Set<UUID> dismissedUpdates = ConcurrentHashMap.newKeySet();
5151

5252
public ZDiscordCommand(ZDiscord plugin) {
5353
this.plugin = plugin;

src/main/java/dev/demonz/zdiscord/minecraft/listeners/AdvancementListener.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,6 @@
3939
*/
4040
public class AdvancementListener implements Listener {
4141

42-
/**
43-
* Unlockers below this fraction of the active player base
44-
* are considered "rare" and the embed shows the trophy
45-
* badge. 0.25 = bottom 25%.
46-
*/
47-
private static final double RARITY_THRESHOLD = 0.25;
4842

4943
private final ZDiscord plugin;
5044

src/main/java/dev/demonz/zdiscord/modules/ReactionRoleModule.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.io.IOException;
3131
import java.util.HashMap;
3232
import java.util.Map;
33+
import java.util.concurrent.ConcurrentHashMap;
3334
import java.util.UUID;
3435

3536
/**
@@ -41,7 +42,7 @@ public class ReactionRoleModule {
4142
private final ZDiscord plugin;
4243
private final File dataFile;
4344
private FileConfiguration data;
44-
private final Map<String, Map<String, RoleMapping>> mappings = new HashMap<>();
45+
private final Map<String, Map<String, RoleMapping>> mappings = new ConcurrentHashMap<>();
4546

4647
public ReactionRoleModule(ZDiscord plugin) {
4748
this.plugin = plugin;

src/main/java/dev/demonz/zdiscord/modules/StatusModule.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ public void shutdown() {
157157
ctx.maxCount = BukkitMaxPlayers();
158158

159159
try {
160-
channel.editMessageEmbedsById(statusMessageId, StatusEmbedBuilder.build(ctx)).complete();
160+
channel.editMessageEmbedsById(statusMessageId, StatusEmbedBuilder.build(ctx)).queue(
161+
null,
162+
e -> plugin.debug("Failed to send offline status: " + e.getMessage()));
161163
} catch (Exception e) {
162164
plugin.debug("Failed to send offline status: " + e.getMessage());
163165
}

src/main/java/dev/demonz/zdiscord/modules/TicketModule.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Map;
4141
import java.util.UUID;
4242
import java.util.concurrent.ConcurrentHashMap;
43+
import java.util.concurrent.atomic.AtomicInteger;
4344

4445
/**
4546
* Support ticket system. Each ticket is a private Discord channel
@@ -54,14 +55,14 @@ public class TicketModule {
5455

5556
private final ZDiscord plugin;
5657
private final Map<String, Integer> openTicketsByUser = new ConcurrentHashMap<>();
57-
private int ticketCounter = 0;
58+
private final AtomicInteger ticketCounter = new AtomicInteger(0);
5859

5960
public TicketModule(ZDiscord plugin) {
6061
this.plugin = plugin;
6162
}
6263

6364
public void init() {
64-
ticketCounter = plugin.getStorageManager().getDataInt("ticket-counter", 0);
65+
ticketCounter.set(plugin.getStorageManager().getDataInt("ticket-counter", 0));
6566
String openData = plugin.getStorageManager().getData("open-tickets", "");
6667
openTicketsByUser.clear();
6768
if (!openData.isEmpty()) {
@@ -78,7 +79,7 @@ public void init() {
7879
}
7980
}
8081
}
81-
plugin.debug("Ticket module initialised (counter: " + ticketCounter
82+
plugin.debug("Ticket module initialised (counter: " + ticketCounter.get()
8283
+ ", categories: " + getCategories().size()
8384
+ ", tracked users: " + openTicketsByUser.size() + ")");
8485
}
@@ -277,12 +278,12 @@ private TextChannel createTicketChannel(String username, String subject,
277278
category = guild.getCategoryById(categoryChannelId);
278279
}
279280

280-
ticketCounter++;
281-
plugin.getStorageManager().setData("ticket-counter", ticketCounter);
281+
int currentTicket = ticketCounter.incrementAndGet();
282+
plugin.getStorageManager().setData("ticket-counter", currentTicket);
282283

283284
TicketCategory cat = getCategory(categoryId);
284285

285-
String channelName = "ticket-" + String.format("%04d", ticketCounter)
286+
String channelName = "ticket-" + String.format("%04d", currentTicket)
286287
+ "-" + username.toLowerCase().replaceAll("[^a-z0-9-]", "");
287288

288289
try {
@@ -341,7 +342,7 @@ private void sendTicketWelcome(TextChannel channel, String username,
341342
.addField("Category", categoryLabel, true)
342343
.addField("Opened by", "`" + username + "`", true)
343344
.setColor(ColorUtil.parseHex(color))
344-
.setFooter("Ticket #" + ticketCounter + " \u2022 Use the buttons below to manage")
345+
.setFooter("Ticket #" + currentTicket + " \u2022 Use the buttons below to manage")
345346
.setTimestamp(Instant.now());
346347

347348
channel.sendMessageEmbeds(embed.build())
@@ -475,7 +476,7 @@ private void saveOpenTickets() {
475476
}
476477

477478
public void shutdown() {
478-
plugin.getStorageManager().setData("ticket-counter", ticketCounter);
479+
plugin.getStorageManager().setData("ticket-counter", ticketCounter.get());
479480
saveOpenTickets();
480481
}
481482

src/main/resources/plugin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ softdepend:
1717
commands:
1818
zdiscord:
1919
description: ZDiscord admin command
20-
usage: /<command> [reload|status|link|embed|ticket|lockdown|dump]
20+
usage: /<command> [reload|status|link|embed|ticket|panel|lockdown|update|dump]
2121
permission: zdiscord.admin
2222
aliases: [zd]
2323
discord:

0 commit comments

Comments
 (0)