Skip to content

OC2R hangs server on shutdown #137

Description

@pjht

Describe the bug
OC2R hangs the server on shutdown. This is caused by threads not being stopped right, specifically Internet and Block Device Initializer.

To Reproduce
Run OC2R on a multiplayer server and stop the server, it will hang.
On linux you can view the java threads with jcmd $(pgrep -f forge) Thread.print 2>/dev/null, look for the threads not labeled daemon. those threads will be there.

Log files
None relevant, shows clean shutdown.

Expected behavior
OC2R to not hang the server on shutdown

Versions (please complete the following information):

  • Minecraft: 1.20.1
  • Forge: 47.4.20
  • oc2: latest dev build

The following diff fixes it but it's heavily AI assisted so don't really want to PR it RN in case something is wrong in a subtle way that isn't obvious to me at the moment.

Diff
diff --git a/src/main/java/li/cil/oc2/common/bus/device/vm/item/AbstractBlockStorageDevice.java b/src/main/java/li/cil/oc2/common/bus/device/vm/item/AbstractBlockStorageDevice.java
index 2f10a52..7f08523 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/vm/item/AbstractBlockStorageDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/vm/item/AbstractBlockStorageDevice.java
@@ -43,7 +43,7 @@ public abstract class AbstractBlockStorageDevice<TBlock extends BlockDevice, TId
 
     protected static final ExecutorService WORKERS = Executors.newCachedThreadPool(r -> {
         final Thread thread = new Thread(r, "Block Device Initializer");
-        thread.setDaemon(false);
+        thread.setDaemon(true);
         return thread;
     });
 
diff --git a/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java b/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java
index b5b49f3..cd46888 100644
--- a/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java
+++ b/src/main/java/li/cil/oc2/common/event/ForgeEventHandlers.java
@@ -3,9 +3,11 @@ package li.cil.oc2.common.event;
 import li.cil.oc2.api.API;
 import li.cil.oc2.common.config.AsyncConfig;
 import li.cil.oc2.common.util.AsyncUtils;
+import li.cil.oc2.common.inet.InternetManagerImpl;
 import net.minecraft.server.MinecraftServer;
 import net.minecraftforge.event.server.ServerAboutToStartEvent;
 import net.minecraftforge.event.server.ServerStoppedEvent;
+import net.minecraftforge.event.server.ServerStoppingEvent;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.common.Mod;
 import org.apache.logging.log4j.LogManager;
@@ -37,6 +39,11 @@ public final class ForgeEventHandlers {
         LOGGER.info("Server starting, initializing async components");
     }
 
+    @SubscribeEvent
+    public static void onServerStopping(final ServerStoppingEvent event) {
+        InternetManagerImpl.getInstance().ifPresent(manager -> manager.shutdown());
+    }
+
     @SubscribeEvent
     public static void handleServerStopped(final ServerStoppedEvent event) {
         LOGGER.info("Server stopped, cleaning up async components");
diff --git a/src/main/java/li/cil/oc2/common/inet/InternetManagerImpl.java b/src/main/java/li/cil/oc2/common/inet/InternetManagerImpl.java
index 53d55d1..26106aa 100644
--- a/src/main/java/li/cil/oc2/common/inet/InternetManagerImpl.java
+++ b/src/main/java/li/cil/oc2/common/inet/InternetManagerImpl.java
@@ -19,6 +19,7 @@ import java.util.*;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -58,6 +59,18 @@ public final class InternetManagerImpl implements InternetManager {
         }
     }
 
+    public void shutdown() {
+        executor.shutdown();
+        try {
+            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+                executor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            executor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+    }
+
     public static Optional<InternetManagerImpl> getInstance() {
         return Optional.ofNullable(INSTANCE);
     }

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions