From 4213d37c766d2dc90cd77459715090ba02005374 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 21 Jun 2019 22:06:04 +0200 Subject: [PATCH] Fix race-condition-induced dead lock on replay restart in 1.14+ MC clears it scheduled tasks on disconnect. If we don't wait for that to have happened on the async sender, then we might loose some of the initial handshake packets (resulting in a dead lock in fabric's registry syncing code). --- .../java/com/replaymod/core/ReplayMod.java | 29 +++++++++++++----- .../com/replaymod/replay/ReplayHandler.java | 30 +++++++++++-------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index d1f3875d..f3a9aedb 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -42,6 +42,8 @@ import net.minecraft.util.NonBlockingThreadExecutor; //$$ import com.google.common.util.concurrent.ListenableFutureTask; //$$ import net.minecraft.resources.FolderPack; //$$ import net.minecraftforge.eventbus.api.SubscribeEvent; +//$$ import java.util.Queue; +//$$ import java.util.concurrent.FutureTask; //$$ //#if MC>=11300 //$$ import com.replaymod.core.versions.LangResourcePack; @@ -89,8 +91,9 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.FutureTask; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static com.replaymod.core.versions.MCVer.*; @@ -385,16 +388,28 @@ public class ReplayMod implements }); } - public void runSync(Runnable runnable) { + /** + * Execute the given runnable on the main client thread, returning only after it has been run (or after 30 seconds). + */ + public void runSync(Runnable runnable) throws InterruptedException, ExecutionException, TimeoutException { //#if MC>=11400 if (mc.isOnThread()) { - //#else - //$$ if (mc.isCallingFromMinecraftThread()) { - //#endif runnable.run(); } else { - runLater(runnable); + executor.executeFuture(() -> { + runnable.run(); + return null; + }).get(30, TimeUnit.SECONDS); } + //#else + //$$ if (mc.isCallingFromMinecraftThread()) { + //$$ runnable.run(); + //$$ } else { + //$$ FutureTask future = new FutureTask<>(runnable, null); + //$$ runLater(future); + //$$ future.get(30, TimeUnit.SECONDS); + //$$ } + //#endif } /** diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index affd8b2a..c0798670 100755 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -35,6 +35,8 @@ import net.minecraft.network.ClientConnection; import java.io.IOException; import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; //#if MC>=11300 import com.replaymod.replay.mixin.EntityLivingBaseAccessor; @@ -162,18 +164,22 @@ public class ReplayHandler { channel.close(); // Force re-creation of camera entity by unloading the previous world - ReplayMod.instance.runSync(() -> { - //#if MC>=11300 - mc.mouse.unlockCursor(); - //#else - //$$ mc.setIngameNotInFocus(); - //#endif - //#if MC>=11400 - mc.disconnect(); - //#else - //$$ mc.loadWorld(null); - //#endif - }); + try { + ReplayMod.instance.runSync(() -> { + //#if MC>=11300 + mc.mouse.unlockCursor(); + //#else + //$$ mc.setIngameNotInFocus(); + //#endif + //#if MC>=11400 + mc.disconnect(); + //#else + //$$ mc.loadWorld(null); + //#endif + }); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + LOGGER.error("Failed to properly restart (shutdown) replay:", e); + } restrictions = new Restrictions();