--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
@@ -47,6 +_,7 @@
     private ClientInformation clientInformation;
     @Nullable
     private SynchronizeRegistriesTask synchronizeRegistriesTask;
+    public boolean switchToMain = false; // Folia - region threading - rewrite login process
 
     // CraftBukkit start
     public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) {
@@ -160,7 +_,58 @@
             }
 
             ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
-            playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
+            // Folia start - region threading - rewrite login process
+            io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot handle player login off global tick thread");
+            CommonListenerCookie clientData = this.createCookie(this.clientInformation);
+            org.apache.commons.lang3.mutable.MutableObject<net.minecraft.nbt.CompoundTag> data = new org.apache.commons.lang3.mutable.MutableObject<>();
+            org.apache.commons.lang3.mutable.MutableObject<String> lastKnownName = new org.apache.commons.lang3.mutable.MutableObject<>();
+            ca.spottedleaf.concurrentutil.completable.CallbackCompletable<org.bukkit.Location> toComplete = new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>();
+            // note: need to call addWaiter before completion to ensure the callback is invoked synchronously
+            // the loadSpawnForNewPlayer function always completes the completable once the chunks were loaded,
+            // on the load callback for those chunks (so on the same region)
+            // this guarantees the chunk cannot unload under our feet
+            toComplete.addWaiter((org.bukkit.Location loc, Throwable t) -> {
+                int chunkX = net.minecraft.util.Mth.floor(loc.getX()) >> 4;
+                int chunkZ = net.minecraft.util.Mth.floor(loc.getZ()) >> 4;
+
+                net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)loc.getWorld()).getHandle();
+                // we just need to hold the chunks at loaded until the next tick
+                // so we do not need to care about unique IDs for the ticket
+                world.getChunkSource().addTicketAtLevel(
+                    net.minecraft.server.level.TicketType.LOGIN,
+                    new net.minecraft.world.level.ChunkPos(chunkX, chunkZ),
+                    ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL,
+                    net.minecraft.util.Unit.INSTANCE
+                );
+
+                io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(
+                    world, chunkX, chunkZ,
+                    () -> {
+                        // once switchToMain is set, the current ticking region now owns the connection and is responsible
+                        // for cleaning it up
+                        playerList.placeNewPlayer(
+                            ServerConfigurationPacketListenerImpl.this.connection,
+                            playerForLogin,
+                            clientData,
+                            java.util.Optional.ofNullable(data.getValue()),
+                            lastKnownName.getValue(),
+                            loc
+                        );
+                    },
+                    ca.spottedleaf.concurrentutil.util.Priority.HIGHER
+                );
+            });
+            this.switchToMain = true;
+            try {
+                // now the connection responsibility is transferred on the region
+                playerList.loadSpawnForNewPlayer(this.connection, playerForLogin, clientData, data, lastKnownName, toComplete);
+            } catch (final Throwable throwable) {
+                // assume toComplete will not be invoked
+                // ensure global tick thread owns the connection again, to properly disconnect it
+                this.switchToMain = false;
+                throw new RuntimeException(throwable);
+            }
+            // Folia end - region threading - rewrite login process
         } catch (Exception var5) {
             LOGGER.error("Couldn't place player in world", (Throwable)var5);
             // Paper start - Debugging
