Repository: webbukkit/dynmap
Issue: IllegalStateException: Asynchronous preparation of chunk data for async save!

**Issue Description:** *Dynmap errors on shutdown if it is actively rendering / saving tiles.*

* **Dynmap Version:** *dynmap version 3.4-819*
* **Server Version:** *git-Purpur-1752 (Paper 1.19.2)*
* **Pastebin of Configuration.txt:** *https://paste.gg/p/anonymous/84efb69a60124fe8b884a427bd745959/files/d92f9ce23d4d44c9adac79751491cc78/raw*
* **Server Host (if applicable):** *Selfhosted*
* **Pastebin of crashlogs or other relevant logs:** *https://paste.gg/p/anonymous/589ea0998bd846d7ad1017f5182c0a33/files/7cbd3245509949f3914ee68f68332974/raw*
* **Steps to Replicate:**

  - Generate new chunks and have dynmap queue up a backlog of tiles to render.
  - Shut down server to restart it.
  - See errors during shutdown.

```
[02:56:02] [Dynmap Render Thread/ERROR]: Thread Dynmap Render Thread failed main thread check: preparation of chunk data for async save
java.lang.Throwable: null
	at org.spigotmc.AsyncCatcher.catchOp(AsyncCatcher.java:15) ~[purpur-1.19.2.jar:git-Purpur-1752]
	at net.minecraft.world.level.chunk.storage.ChunkSerializer.getAsyncSaveData(ChunkSerializer.java:465) ~[?:?]
	at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:577) ~[?:?]
	at org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.lambda$getLoadedChunk$6(AsyncChunkProvider119.java:96) ~[Dynmap-HEAD-spigot.jar:?]
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?]
	at net.minecraft.util.thread.BlockableEventLoop.execute(BlockableEventLoop.java:100) ~[?:?]
	at java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1782) ~[?:?]
	at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:2005) ~[?:?]
	at org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.getLoadedChunk(AsyncChunkProvider119.java:94) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.bukkit.helper.v119.MapChunkCache119.getLoadedChunkAsync(MapChunkCache119.java:40) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.common.chunk.GenericMapChunkCache.getLoadedChunksAsync(GenericMapChunkCache.java:803) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.common.chunk.GenericMapChunkCache.loadChunksAsync(GenericMapChunkCache.java:844) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.bukkit.DynmapPlugin$BukkitServer.createMapChunkCache(DynmapPlugin.java:570) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.MapManager$FullWorldRenderState.processTile(MapManager.java:767) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.MapManager$FullWorldRenderState.run(MapManager.java:726) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.MapManager$DynmapScheduledThreadPoolExecutor$1.run(MapManager.java:234) ~[Dynmap-HEAD-spigot.jar:?]
	at org.dynmap.MapManager$DynmapScheduledThreadPoolExecutor$2.run(MapManager.java:252) ~[Dynmap-HEAD-spigot.jar:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[?:?]
	at java.lang.Thread.run(Thread.java:833) ~[?:?]
[02:56:02] [Dynmap Render Thread/ERROR]: [dynmap] Exception during render job: world=world, map=null
[02:56:02] [Dynmap Render Thread/WARN]: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.lambda$getLoadedChunk$7(AsyncChunkProvider119.java:114)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.MapChunkCache119.lambda$getLoadedChunkAsync$0(MapChunkCache119.java:42)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.common.chunk.GenericMapChunkCache.lambda$getLoadedChunksAsync$1(GenericMapChunkCache.java:829)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.common.chunk.GenericMapChunkCache.getLoadedChunksAsync(GenericMapChunkCache.java:827)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.common.chunk.GenericMapChunkCache.loadChunksAsync(GenericMapChunkCache.java:844)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.DynmapPlugin$BukkitServer.createMapChunkCache(DynmapPlugin.java:570)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.MapManager$FullWorldRenderState.processTile(MapManager.java:767)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.MapManager$FullWorldRenderState.run(MapManager.java:726)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.MapManager$DynmapScheduledThreadPoolExecutor$1.run(MapManager.java:234)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.MapManager$DynmapScheduledThreadPoolExecutor$2.run(MapManager.java:252)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.lang.Thread.run(Thread.java:833)
[02:56:02] [Dynmap Render Thread/WARN]: Caused by: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.lambda$getLoadedChunk$7(AsyncChunkProvider119.java:112)
[02:56:02] [Dynmap Render Thread/WARN]: 	... 16 more
[02:56:02] [Dynmap Render Thread/WARN]: Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.lambda$getLoadedChunk$6(AsyncChunkProvider119.java:98)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
[02:56:02] [Dynmap Render Thread/WARN]: 	at net.minecraft.util.thread.IAsyncTaskHandler.execute(IAsyncTaskHandler.java:100)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.CompletableFuture.asyncSupplyStage(CompletableFuture.java:1782)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:2005)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.getLoadedChunk(AsyncChunkProvider119.java:94)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.MapChunkCache119.getLoadedChunkAsync(MapChunkCache119.java:40)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.common.chunk.GenericMapChunkCache.getLoadedChunksAsync(GenericMapChunkCache.java:803)
[02:56:02] [Dynmap Render Thread/WARN]: 	... 12 more
[02:56:02] [Dynmap Render Thread/WARN]: Caused by: java.lang.reflect.InvocationTargetException
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/java.lang.reflect.Method.invoke(Method.java:577)
[02:56:02] [Dynmap Render Thread/WARN]: 	at Dynmap-HEAD-spigot.jar//org.dynmap.bukkit.helper.v119.AsyncChunkProvider119.lambda$getLoadedChunk$6(AsyncChunkProvider119.java:96)
[02:56:02] [Dynmap Render Thread/WARN]: 	... 19 more
[02:56:02] [Dynmap Render Thread/WARN]: Caused by: java.lang.IllegalStateException: Asynchronous preparation of chunk data for async save!
[02:56:02] [Dynmap Render Thread/WARN]: 	at org.spigotmc.AsyncCatcher.catchOp(AsyncCatcher.java:16)
[02:56:02] [Dynmap Render Thread/WARN]: 	at net.minecraft.world.level.chunk.storage.ChunkRegionLoader.getAsyncSaveData(ChunkRegionLoader.java:465)
[02:56:02] [Dynmap Render Thread/WARN]: 	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
[02:56:02] [Dynmap Render Thread/WARN]: 	... 21 more
```

-  [x] *I have looked at all other issues and this is not a duplicate*  
-  [x] *I have been able to replicate this*


Changed files:
bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java
bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java
bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java
bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java

diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java
index 745527e6..03e831f7 100644
--- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java
+++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java
@@ -93,6 +93,9 @@ public class AsyncChunkProvider118_2 {
         }
         //prepare data synchronously
         CompletableFuture<?> future = CompletableFuture.supplyAsync(() -> {
+            //Null will mean that we save with spigot methods, which may be risky on async
+            //Since we're not in main thread, it now refuses new tasks because of shutdown, the risk is lower
+            if (!Bukkit.isPrimaryThread()) return null;
             try {
                 return getAsyncSaveData.invoke(null, world.getHandle(), c);
             } catch (IllegalAccessException | InvocationTargetException e) {
@@ -103,15 +106,21 @@ public class AsyncChunkProvider118_2 {
         if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) {
             try {
                 Thread.sleep(25); //hold the lock so other threads also won't stress main thread
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
+            } catch (InterruptedException ignored) {}
         }
         //save data asynchronously
         return () -> {
+            Object o = null;
             try {
-                return (NBTTagCompound) save.invoke(null, world.getHandle(), c, future.get());
-            } catch (ReflectiveOperationException | ExecutionException | InterruptedException e) {
+                o = future.get();
+                return (NBTTagCompound) save.invoke(null, world.getHandle(), c, o);
+            } catch (InterruptedException e) {
+                return null;
+            } catch (InvocationTargetException e) {
+                //We tried to use simple spigot methods at shutdown and failed, hopes for reading from disk
+                if (o == null) return null;
+                throw new RuntimeException(e);
+            } catch (ReflectiveOperationException | ExecutionException e) {
                 throw new RuntimeException(e);
             }
         };
diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java
index c96b6670..21b94ea3 100644
--- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java
+++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java
@@ -22,7 +22,7 @@ import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ExecutionException;
 import java.util.function.Supplier;
 
 /**
@@ -61,7 +61,14 @@ public class MapChunkCache118_2 extends GenericMapChunkCache {
         try {
             CompletableFuture<NBTTagCompound> nbt = provider.getChunk(((CraftWorld) w).getHandle(), chunk.x, chunk.z);
             return () -> {
-                NBTTagCompound compound = nbt.join();
+                NBTTagCompound compound;
+                try {
+                    compound = nbt.get();
+                } catch (InterruptedException e) {
+                    return null;
+                } catch (ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
                 return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound));
             };
         } catch (InvocationTargetException | IllegalAccessException ignored) {
diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java
index ac93ee97..eef534b0 100644
--- a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java
+++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java
@@ -92,6 +92,9 @@ public class AsyncChunkProvider119 {
         }
         //prepare data synchronously
         CompletableFuture<?> future = CompletableFuture.supplyAsync(() -> {
+            //Null will mean that we save with spigot methods, which may be risky on async
+            //Since we're not in main thread, it now refuses new tasks because of shutdown, the risk is lower
+            if (!Bukkit.isPrimaryThread()) return null;
             try {
                 return getAsyncSaveData.invoke(null, world.getHandle(), c);
             } catch (ReflectiveOperationException e) {
@@ -102,15 +105,21 @@ public class AsyncChunkProvider119 {
         if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) {
             try {
                 Thread.sleep(25); //hold the lock so other threads also won't stress main thread
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
+            } catch (InterruptedException ignored) {}
         }
         //save data asynchronously
         return () -> {
+            Object o = null;
             try {
-                return (NBTTagCompound) save.invoke(null, world.getHandle(), c, future.get());
-            } catch (ReflectiveOperationException | ExecutionException | InterruptedException e) {
+                o = future.get();
+                return (NBTTagCompound) save.invoke(null, world.getHandle(), c, o);
+            } catch (InterruptedException e) {
+                return null;
+            } catch (InvocationTargetException e) {
+                //We tried to use simple spigot methods at shutdown and failed, hopes for reading from disk
+                if (o == null) return null;
+                throw new RuntimeException(e);
+            } catch (ReflectiveOperationException | ExecutionException e) {
                 throw new RuntimeException(e);
             }
         };
diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java
index a94c0898..5ecbc277 100644
--- a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java
+++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java
@@ -19,6 +19,7 @@ import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.function.Supplier;
 
 /**
@@ -58,7 +59,14 @@ public class MapChunkCache119 extends GenericMapChunkCache {
         try {
             CompletableFuture<NBTTagCompound> nbt = provider.getChunk(((CraftWorld) w).getHandle(), chunk.x, chunk.z);
             return () -> {
-                NBTTagCompound compound = nbt.join();
+                NBTTagCompound compound;
+                try {
+                    compound = nbt.get();
+                } catch (InterruptedException e) {
+                    return null;
+                } catch (ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
                 return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound));
             };
         } catch (InvocationTargetException | IllegalAccessException ignored) {