Ensure that exceptions in player event loop are handled

Any stray exception means that the entire event loop comes crashing down.
This commit is contained in:
Camotoy 2021-08-17 20:57:46 -04:00
parent 76399881a3
commit 3d04a957d0
9 changed files with 57 additions and 27 deletions

View file

@ -161,7 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(ModalFormResponsePacket packet) {
session.getEventLoop().execute(() -> session.getFormCache().handleResponse(packet));
session.executeInEventLoop(() -> session.getFormCache().handleResponse(packet));
return true;
}

View file

@ -115,6 +115,7 @@ public class GeyserSession implements CommandSender {
private final UpstreamSession upstream;
/**
* The loop where all packets and ticking is processed to prevent concurrency issues.
* If this is manually called, ensure that any exceptions are properly handled.
*/
private final EventLoop eventLoop;
private TcpClientSession downstream;
@ -804,9 +805,8 @@ public class GeyserSession implements CommandSender {
@Override
public void packetReceived(PacketReceivedEvent event) {
if (!closed) {
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
}
Packet packet = event.getPacket();
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(packet.getClass(), packet, GeyserSession.this);
}
@Override
@ -874,6 +874,32 @@ public class GeyserSession implements CommandSender {
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode()));
}
/**
* Executes a task and prints a stack trace if an error occurs.
*/
public void executeInEventLoop(Runnable runnable) {
eventLoop.execute(() -> {
try {
runnable.run();
} catch (Throwable e) {
e.printStackTrace();
}
});
}
/**
* Schedules a task and prints a stack trace if an error occurs.
*/
public ScheduledFuture<?> scheduleInEventLoop(Runnable runnable, long duration, TimeUnit timeUnit) {
return eventLoop.schedule(() -> {
try {
runnable.run();
} catch (Throwable e) {
e.printStackTrace();
}
}, duration, timeUnit);
}
/**
* Called every 50 milliseconds - one Minecraft tick.
*/

View file

@ -87,27 +87,31 @@ public class PacketTranslatorRegistry<T> {
@SuppressWarnings("unchecked")
public <P extends T> boolean translate(Class<? extends P> clazz, P packet, GeyserSession session) {
if (!session.getUpstream().isClosed() && !session.isClosed()) {
try {
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
if (translator != null) {
EventLoop eventLoop = session.getEventLoop();
if (eventLoop.inEventLoop()) {
translator.translate(packet, session);
} else {
eventLoop.execute(() -> translator.translate(packet, session));
}
return true;
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
if (translator != null) {
EventLoop eventLoop = session.getEventLoop();
if (eventLoop.inEventLoop()) {
translate0(session, translator, packet);
} else {
if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
// Other debug logs already take care of Bedrock packets for us if on standalone
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
}
eventLoop.execute(() -> translate0(session, translator, packet));
}
return true;
} else {
if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
// Other debug logs already take care of Bedrock packets for us if on standalone
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
}
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);
ex.printStackTrace();
}
}
return false;
}
private <P extends T> void translate0(GeyserSession session, PacketTranslator<P> translator, P packet) {
try {
translator.translate(packet, session);
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);
ex.printStackTrace();
}
}
}

View file

@ -48,7 +48,7 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
switch (packet.getAction()) {
case SWING_ARM:
// Delay so entity damage can be processed first
session.getEventLoop().schedule(() ->
session.scheduleInEventLoop(() ->
session.sendDownstreamPacket(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)),
25,
TimeUnit.MILLISECONDS

View file

@ -220,7 +220,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) {
// Delay the interaction in case the client doesn't intend to actually use the bucket
// See BedrockActionTranslator.java
session.setBucketScheduledFuture(session.getEventLoop().schedule(() -> {
session.setBucketScheduledFuture(session.scheduleInEventLoop(() -> {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
}, 5, TimeUnit.MILLISECONDS));

View file

@ -53,7 +53,7 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
session.sendDownstreamPacket(selectTradePacket);
session.getEventLoop().schedule(() -> {
session.scheduleInEventLoop(() -> {
Entity villager = session.getPlayerEntity();
Inventory openInventory = session.getOpenInventory();
if (openInventory instanceof MerchantContainer) {

View file

@ -78,7 +78,7 @@ public class JavaSetSlotTranslator extends PacketTranslator<ServerSetSlotPacket>
if (session.getCraftingGridFuture() != null) {
session.getCraftingGridFuture().cancel(false);
}
session.setCraftingGridFuture(session.getEventLoop().schedule(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS));
session.setCraftingGridFuture(session.scheduleInEventLoop(() -> updateCraftingGrid(session, packet, inventory, translator), 150, TimeUnit.MILLISECONDS));
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
if (packet.getWindowId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {

View file

@ -147,7 +147,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
if (session.getUpstream().isInitialized()) {
player.spawnEntity(session);
SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.getEventLoop().schedule(() -> {
SkullSkinManager.requestAndHandleSkin(player, session, (skin -> session.scheduleInEventLoop(() -> {
// Delay to minimize split-second "player" pop-in
player.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
player.updateBedrockMetadata(session);

View file

@ -79,7 +79,7 @@ public class InventoryUtils {
if (translator != null) {
translator.prepareInventory(session, inventory);
if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
session.getEventLoop().schedule(() -> {
session.scheduleInEventLoop(() -> {
Inventory openInv = session.getOpenInventory();
if (openInv != null && openInv.getId() == inventory.getId()) {
translator.openInventory(session, inventory);