Skip to content

Commit 65a2eb9

Browse files
Merge pull request #821 from Swofty-Developments/fix/auction-house-and-proxy-heartbeat
Fix auction house bugs and redesign proxy liveness check
2 parents 5acf680 + db482a6 commit 65a2eb9

16 files changed

Lines changed: 249 additions & 165 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package net.swofty.commons.redis;
2+
3+
import redis.clients.jedis.Jedis;
4+
import redis.clients.jedis.JedisPool;
5+
6+
public final class ProxyHeartbeat {
7+
private static final String KEY = "proxy:heartbeat";
8+
private static final int TTL_SECONDS = 6;
9+
10+
private static volatile JedisPool pool;
11+
12+
private ProxyHeartbeat() {}
13+
14+
public static synchronized void init(String redisUri) {
15+
if (pool == null) {
16+
pool = RedisConnectionPool.connect(redisUri, RedisConnectionPool.Settings.standard());
17+
}
18+
}
19+
20+
public static void beat() {
21+
try (Jedis jedis = pool.getResource()) {
22+
jedis.setex(KEY, TTL_SECONDS, Long.toString(System.currentTimeMillis()));
23+
}
24+
}
25+
26+
public static boolean isProxyAlive() {
27+
try (Jedis jedis = pool.getResource()) {
28+
return jedis.exists(KEY);
29+
}
30+
}
31+
}

commons/src/main/java/net/swofty/commons/skyblock/auctions/AuctionItem.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package net.swofty.commons.skyblock.auctions;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
35
import lombok.Getter;
46
import lombok.Setter;
57
import net.swofty.commons.skyblock.item.UnderstandableSkyBlockItem;
8+
import net.swofty.commons.protocol.serializers.AuctionItemSerializer;
69
import net.swofty.commons.protocol.serializers.UnderstandableSkyBlockItemSerializer;
710
import org.bson.Document;
811
import org.jetbrains.annotations.NotNull;
@@ -39,6 +42,16 @@ public AuctionItem(UnderstandableSkyBlockItem item, UUID originator, long endTim
3942
this.bids = new ArrayList<>();
4043
}
4144

45+
@JsonValue
46+
public String toSerializedString() {
47+
return new AuctionItemSerializer<>().serialize(this);
48+
}
49+
50+
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
51+
public static AuctionItem fromSerializedString(String json) {
52+
return new AuctionItemSerializer<>().deserialize(json);
53+
}
54+
4255
public Document toDocument() {
4356
return new Document()
4457
.append("_id", uuid.toString())

configuration/i18n/en_US/tablist.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ tablist.skywars_game.footer.store = <green>Ranks, Boosters & MORE! <red><bold>ST
2727

2828
# Module Headers
2929
tablist.module.server_info = <dark_aqua><bold>Server Info
30-
tablist.module.players=<green><bold>Players <white>({<arg:0>})
31-
tablist.module.players_murder_mystery=<red><bold>Players <white>(<arg:0>})
30+
tablist.module.players=<green><bold>Players <white>(<arg:0>)
31+
tablist.module.players_murder_mystery=<red><bold>Players <white>(<arg:0>)
3232
tablist.module.account_info = <gold><bold>Account Info
3333
tablist.module.island=<aqua><bold>Island <white>(<arg:0>)
3434
tablist.module.guests = <light_purple><bold>Guests

loader/src/main/java/net/swofty/loader/Hypixel.java

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import net.swofty.commons.config.ConfigProvider;
2222
import net.swofty.commons.protocol.RedisProtocol;
2323
import net.swofty.commons.protocol.objects.proxy.to.*;
24+
import net.swofty.commons.redis.ProxyHeartbeat;
2425
import net.swofty.commons.redis.RedisClient;
2526
import net.swofty.proxyapi.ProxyAPI;
2627
import net.swofty.proxyapi.ProxyService;
@@ -48,8 +49,11 @@
4849
import java.util.Set;
4950
import java.util.UUID;
5051
import java.util.concurrent.CompletableFuture;
52+
import java.util.concurrent.Executors;
53+
import java.util.concurrent.ScheduledExecutorService;
5154
import java.util.concurrent.TimeUnit;
5255
import java.util.concurrent.atomic.AtomicBoolean;
56+
import java.util.concurrent.atomic.AtomicInteger;
5357
import java.util.stream.Collectors;
5458

5559

@@ -246,7 +250,7 @@ static void main(String[] args) {
246250

247251
Logger.info("Received server name: " + HypixelConst.getServerName());
248252
});
249-
checkProxyConnected(MinecraftServer.getSchedulerManager());
253+
checkProxyConnected();
250254

251255
// Initialize anticheat
252256
if (ConfigProvider.settings().getIntegrations().isAnticheat()) {
@@ -340,32 +344,42 @@ private static Map<String, String> parseOptionalArgs(String[] args) {
340344
return options;
341345
}
342346

343-
private static void checkProxyConnected(Scheduler scheduler) {
344-
scheduler.submitTask(() -> {
345-
AtomicBoolean responded = new AtomicBoolean(false);
347+
private static final int PROXY_HEARTBEAT_CHECK_SECONDS = 3;
348+
private static final int PROXY_HEARTBEAT_MAX_MISSES = 3;
346349

350+
private static void checkProxyConnected() {
351+
ProxyHeartbeat.init(ConfigProvider.settings().getRedisUri());
352+
353+
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor(runnable -> {
354+
Thread thread = new Thread(runnable, "proxy-heartbeat-monitor");
355+
thread.setDaemon(true);
356+
return thread;
357+
});
358+
359+
AtomicInteger missed = new AtomicInteger(0);
360+
monitor.scheduleAtFixedRate(() -> {
361+
boolean alive;
347362
try {
348-
RedisClient.requestProxy(new ProxyIsOnlineProtocol(),
349-
new ProxyIsOnlineProtocol.Request()).thenAccept(response -> {
350-
if (response.online()) {
351-
responded.set(true);
352-
}
353-
});
363+
alive = ProxyHeartbeat.isProxyAlive();
354364
} catch (Exception e) {
355-
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> player.kick("§cServer has lost connection to the proxy, please rejoin"));
356-
CompletableFuture.delayedExecutor(500, TimeUnit.MILLISECONDS)
357-
.execute(() -> System.exit(0));
358-
return TaskSchedule.stop();
365+
alive = false;
359366
}
360367

361-
scheduler.scheduleTask(() -> {
362-
if (!responded.get()) {
363-
Logger.error("Proxy did not respond to alive check. Shutting down...");
364-
System.exit(0);
365-
}
366-
}, TaskSchedule.tick(20), TaskSchedule.stop());
368+
if (alive) {
369+
missed.set(0);
370+
return;
371+
}
367372

368-
return TaskSchedule.seconds(1);
369-
}, ExecutionType.TICK_END);
373+
int misses = missed.incrementAndGet();
374+
Logger.warn("Proxy heartbeat missing ({}/{})", misses, PROXY_HEARTBEAT_MAX_MISSES);
375+
if (misses >= PROXY_HEARTBEAT_MAX_MISSES) {
376+
Logger.error("Proxy heartbeat absent for ~{}s. Shutting down...",
377+
PROXY_HEARTBEAT_MAX_MISSES * PROXY_HEARTBEAT_CHECK_SECONDS);
378+
MinecraftServer.getConnectionManager().getOnlinePlayers()
379+
.forEach(player -> player.kick("§cServer has lost connection to the proxy, please rejoin"));
380+
CompletableFuture.delayedExecutor(500, TimeUnit.MILLISECONDS)
381+
.execute(() -> System.exit(0));
382+
}
383+
}, PROXY_HEARTBEAT_CHECK_SECONDS, PROXY_HEARTBEAT_CHECK_SECONDS, TimeUnit.SECONDS);
370384
}
371385
}

service.auctionhouse/src/main/java/net/swofty/service/auction/AuctionService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ public class AuctionService implements SkyBlockService {
1111
public static AuctionsCacheService cacheService;
1212

1313
static void main(String[] args) {
14-
SkyBlockService.init(new AuctionService());
14+
new AuctionActiveDatabase("_placeholder").connect(ConfigProvider.settings().getMongodb());
15+
new AuctionInactiveDatabase("_placeholder").connect(ConfigProvider.settings().getMongodb());
1516

1617
cacheService = new AuctionsCacheService();
1718

18-
new AuctionActiveDatabase("_placeholder").connect(ConfigProvider.settings().getMongodb());
19-
new AuctionInactiveDatabase("_placeholder").connect(ConfigProvider.settings().getMongodb());
19+
SkyBlockService.init(new AuctionService());
2020
}
2121

2222
@Override

service.auctionhouse/src/main/java/net/swofty/service/auction/endpoints/EndpointFetchItems.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ public AuctionFetchItemsProtocol.AuctionFetchItemsResponse handle(AuctionFetchIt
6666
break;
6767
}
6868

69-
return new AuctionFetchItemsProtocol.AuctionFetchItemsResponse(results.stream().map(AuctionItem::fromDocument).toList(), true, null);
69+
List<AuctionItem> items = new ArrayList<>(results.size());
70+
for (Document document : results) {
71+
try {
72+
items.add(AuctionItem.fromDocument(document));
73+
} catch (Exception e) {
74+
System.err.println("Skipping unreadable auction _id=" + document.get("_id") + " in category " + category);
75+
e.printStackTrace();
76+
}
77+
}
78+
return new AuctionFetchItemsProtocol.AuctionFetchItemsResponse(items, true, null);
7079
}
7180
}

service.orchestrator/src/main/java/net/swofty/service/orchestrator/OrchestratorService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
public class OrchestratorService implements SkyBlockService {
1414

1515
static void main() {
16-
SkyBlockService.init(new OrchestratorService());
17-
1816
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
1917
Thread t = new Thread(r, "orchestrator-cleanup");
2018
t.setDaemon(true);
2119
return t;
2220
});
2321
scheduler.scheduleAtFixedRate(OrchestratorCache::cleanup, 5, 5, TimeUnit.SECONDS);
2422
Logger.info("Started orchestrator service");
23+
24+
SkyBlockService.init(new OrchestratorService());
2525
}
2626

2727
@Override

type.generic/src/main/java/net/swofty/type/generic/event/actions/gui/ActionPlayerInventoryClick.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ public void handle(InventoryPreClickEvent event, final HypixelPlayer player) {
4040
ItemStack clickedItem = event.getClickedItem();
4141
ItemStack cursorItem = player.getInventory().getCursorItem();
4242

43-
// Check for offhand
44-
if (event.getSlot() == 45) {
43+
if (event.getSlot() == 45 && event.getInventory() instanceof PlayerInventory) {
4544
event.setCancelled(true);
4645
return;
4746
}

type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/auction/GUIAuctionBrowser.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import net.swofty.type.generic.i18n.I18n;
2929
import net.swofty.type.generic.user.HypixelPlayer;
3030
import net.swofty.type.generic.utility.PaginationList;
31+
import org.tinylog.Logger;
3132
import net.swofty.type.skyblockgeneric.auction.AuctionItemLoreHandler;
3233
import net.swofty.type.skyblockgeneric.item.SkyBlockItem;
3334
import net.swofty.type.skyblockgeneric.item.updater.PlayerItemUpdater;
@@ -82,6 +83,10 @@ private void updateItemsCache() {
8283
// Set the items in the GUI
8384
List<AuctionItem> paginatedItems = paginationList.getPage(page);
8485
setItemCache(paginatedItems);
86+
})
87+
.exceptionally(ex -> {
88+
Logger.error(ex, "Auction browse failed for category {}", category);
89+
return null;
8590
});
8691
}
8792

@@ -276,12 +281,14 @@ public void onBottomClick(InventoryPreClickEvent e) {
276281

277282
@Override
278283
public void refreshItems(HypixelPlayer player) {
279-
if (!new ProxyService(ServiceType.AUCTION_HOUSE).isOnline().join()) {
280-
player.sendMessage(I18n.t("gui_auction.browser.offline_message"));
281-
player.closeInventory();
282-
}
283-
284-
setItems();
284+
new ProxyService(ServiceType.AUCTION_HOUSE).isOnline().thenAccept(online -> {
285+
if (!online) {
286+
player.sendMessage(I18n.t("gui_auction.browser.offline_message"));
287+
player.closeInventory();
288+
return;
289+
}
290+
setItems();
291+
});
285292
}
286293

287294
@Override

type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/auction/GUIAuctionCreateItem.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,11 @@ public void run(InventoryPreClickEvent e, HypixelPlayer p) {
171171
new AuctionAddItemProtocol.AuctionAddItemMessage(item, category);
172172
CompletableFuture<AuctionAddItemProtocol.AuctionAddItemResponse> future =
173173
auctionService.handleRequest(message);
174-
UUID auctionUUID = future.join().uuid();
175-
176-
player.sendMessage(I18n.t("gui_auction.create.started_message", Component.text(itemName)));
177-
player.sendMessage(I18n.t("gui_auction.create.started_id", Component.text(auctionUUID.toString())));
174+
future.thenAccept(addResponse -> {
175+
UUID auctionUUID = addResponse.uuid();
176+
player.sendMessage(I18n.t("gui_auction.create.started_message", Component.text(itemName)));
177+
player.sendMessage(I18n.t("gui_auction.create.started_id", Component.text(auctionUUID.toString())));
178+
});
178179
});
179180
}
180181

@@ -278,10 +279,12 @@ public void onBottomClick(InventoryPreClickEvent e) {
278279

279280
@Override
280281
public void refreshItems(HypixelPlayer player) {
281-
if (!new ProxyService(ServiceType.AUCTION_HOUSE).isOnline().join()) {
282-
player.sendMessage(I18n.t("gui_auction.create.offline_message"));
283-
player.closeInventory();
284-
}
282+
new ProxyService(ServiceType.AUCTION_HOUSE).isOnline().thenAccept(online -> {
283+
if (!online) {
284+
player.sendMessage(I18n.t("gui_auction.create.offline_message"));
285+
player.closeInventory();
286+
}
287+
});
285288
}
286289

287290
@Override

0 commit comments

Comments
 (0)