--- a/net/minecraft/world/level/chunk/storage/SectionStorage.java
+++ b/net/minecraft/world/level/chunk/storage/SectionStorage.java
@@ -34,10 +34,10 @@
 import net.minecraft.world.level.LevelHeightAccessor;
 import org.slf4j.Logger;
 
-public class SectionStorage<R> implements AutoCloseable {
+public class SectionStorage<R> extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final String SECTIONS_TAG = "Sections";
-    private final IOWorker worker;
+    // Paper - remove mojang I/O thread
     private final Long2ObjectMap<Optional<R>> storage = new Long2ObjectOpenHashMap<>();
     public final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet();
     private final Function<Runnable, Codec<R>> codec;
@@ -48,14 +48,49 @@
     protected final LevelHeightAccessor levelHeightAccessor;
 
     public SectionStorage(Path path, Function<Runnable, Codec<R>> codecFactory, Function<Runnable, R> factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) {
+        super(path, dsync); // Paper - remove mojang I/O thread
         this.codec = codecFactory;
         this.factory = factory;
         this.fixerUpper = dataFixer;
         this.type = dataFixTypes;
         this.registryAccess = dynamicRegistryManager;
         this.levelHeightAccessor = world;
-        this.worker = new IOWorker(path, dsync, path.getFileName().toString());
+        // Paper - remove mojang I/O thread
+    }
+
+    // Paper start - actually unload POI data
+    public void unloadData(long coordinate) {
+        ChunkPos chunkPos = new ChunkPos(coordinate);
+        this.flush(chunkPos);
+
+        Long2ObjectMap<Optional<R>> data = this.storage;
+        int before = data.size();
+
+        for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
+            data.remove(SectionPos.asLong(chunkPos.x, section, chunkPos.z));
+        }
+
+        if (before != data.size()) {
+            this.onUnload(coordinate);
+        }
+    }
+
+    protected void onUnload(long coordinate) {}
+
+    public boolean isEmpty(long coordinate) {
+        Long2ObjectMap<Optional<R>> data = this.storage;
+        int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
+        int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
+        for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
+            Optional<R> optional = data.get(SectionPos.asLong(x, section, z));
+            if (optional != null && optional.orElse(null) != null) {
+                return false;
+            }
+        }
+
+        return true;
     }
+    // Paper end - actually unload POI data
 
     protected void tick(BooleanSupplier shouldKeepTicking) {
         while(this.hasWork() && shouldKeepTicking.getAsBoolean()) {
@@ -70,11 +105,11 @@
     }
 
     @Nullable
-    protected Optional<R> get(long pos) {
+    public Optional<R> get(long pos) { // Paper - public
         return this.storage.get(pos);
     }
 
-    protected Optional<R> getOrLoad(long pos) {
+    public Optional<R> getOrLoad(long pos) { // Paper - public
         if (this.outsideStoredRange(pos)) {
             return Optional.empty();
         } else {
@@ -122,15 +157,20 @@
     }
 
     private CompletableFuture<Optional<CompoundTag>> tryRead(ChunkPos pos) {
-        return this.worker.loadAsync(pos).exceptionally((throwable) -> {
-            if (throwable instanceof IOException iOException) {
-                LOGGER.error("Error reading chunk {} data from disk", pos, iOException);
-                return Optional.empty();
-            } else {
-                throw new CompletionException(throwable);
-            }
-        });
+        // Paper start - async chunk io
+        try {
+            return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos)));
+        } catch (Throwable thr) {
+            return CompletableFuture.failedFuture(thr);
+        }
+        // Paper end - async chunk io
+    }
+
+    // Paper start - async chunk io
+    public void loadInData(ChunkPos chunkPos, CompoundTag compound) {
+        this.readColumn(chunkPos, RegistryOps.create(NbtOps.INSTANCE, this.registryAccess), compound);
     }
+    // Paper end - aync chnnk i
 
     private <T> void readColumn(ChunkPos pos, DynamicOps<T> ops, @Nullable T data) {
         if (data == null) {
@@ -142,7 +182,14 @@
             int j = getVersion(dynamic);
             int k = SharedConstants.getCurrentVersion().getWorldVersion();
             boolean bl = j != k;
-            Dynamic<T> dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k);
+            // Paper start - route to new converter system
+            Dynamic<T> dynamic2;
+            if (this.type.getType() == net.minecraft.util.datafix.fixes.References.POI_CHUNK) {
+                dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k));
+            } else {
+                dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k);
+            }
+            // Paper end - route to new converter system
             OptionalDynamic<T> optionalDynamic = dynamic2.get("Sections");
 
             for(int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); ++l) {
@@ -162,6 +209,7 @@
                 });
             }
         }
+        if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data
 
     }
 
@@ -170,7 +218,7 @@
         Dynamic<Tag> dynamic = this.writeColumn(pos, registryOps);
         Tag tag = dynamic.getValue();
         if (tag instanceof CompoundTag) {
-            this.worker.store(pos, (CompoundTag)tag);
+            try { this.write(pos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker
         } else {
             LOGGER.error("Expected compound tag, got {}", (Object)tag);
         }
@@ -198,6 +246,21 @@
         return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("Sections"), ops.createMap(map), ops.createString("DataVersion"), ops.createInt(SharedConstants.getCurrentVersion().getWorldVersion()))));
     }
 
+    // Paper start - internal get data function, copied from above
+    private CompoundTag getDataInternal(ChunkPos pos) {
+        RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, this.registryAccess);
+        Dynamic<Tag> dynamic = this.writeColumn(pos, registryOps);
+        Tag nbtbase = (Tag) dynamic.getValue();
+
+        if (nbtbase instanceof CompoundTag) {
+            return (CompoundTag)nbtbase;
+        } else {
+            SectionStorage.LOGGER.error("Expected compound tag, got {}", nbtbase);
+        }
+        return null;
+    }
+    // Paper end
+
     private static long getKey(ChunkPos chunkPos, int y) {
         return SectionPos.asLong(chunkPos.x, y, chunkPos.z);
     }
@@ -233,6 +296,23 @@
 
     @Override
     public void close() throws IOException {
-        this.worker.close();
+        //this.worker.close(); // Paper - nuke I/O worker
+    }
+
+    // Paper start - get data function
+    public CompoundTag getData(ChunkPos chunkcoordintpair) {
+        // Note: Copied from above
+        // This is checking if the data needs to be written, then it builds it later in getDataInternal(ChunkCoordIntPair)
+        if (!this.dirty.isEmpty()) {
+            for (int i = this.levelHeightAccessor.getMinSection(); i < this.levelHeightAccessor.getMaxSection(); ++i) {
+                long j = SectionPos.of(chunkcoordintpair, i).asLong();
+
+                if (this.dirty.contains(j)) {
+                    return this.getDataInternal(chunkcoordintpair);
+                }
+            }
+        }
+        return null;
     }
+    // Paper end
 }
