diff --git a/Spigot-Server-Patches/Avoid-hopper-searches-if-there-are-no-items.patch b/Spigot-Server-Patches/Avoid-hopper-searches-if-there-are-no-items.patch
index c9f719e9d4..c1f9515739 100644
--- a/Spigot-Server-Patches/Avoid-hopper-searches-if-there-are-no-items.patch
+++ b/Spigot-Server-Patches/Avoid-hopper-searches-if-there-are-no-items.patch
@@ -18,16 +18,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/server/Chunk.java
 +++ b/src/main/java/net/minecraft/server/Chunk.java
 @@ -0,0 +0,0 @@ public class Chunk {
-     // Paper start - Asynchronous light updates
-     public AtomicInteger pendingLightUpdates = new AtomicInteger();
-     public long lightUpdateTime;
+     protected gnu.trove.map.hash.TObjectIntHashMap<Class> entityCount = new gnu.trove.map.hash.TObjectIntHashMap<Class>(); // Spigot
+     public int lightUpdates; // Paper - Number of queued light updates for this chunk
+ 
++    // Paper start
 +    // Track the number of minecarts and items
 +    // Keep this synced with entitySlices.add() and entitySlices.remove()
 +    private final int[] itemCounts = new int[16];
 +    private final int[] inventoryEntityCounts = new int[16];
-     // Paper end
- 
++    // Paper end
++
      // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
+     private int neighbors = 0x1 << 12;
+ 
 @@ -0,0 +0,0 @@ public class Chunk {
          entity.ac = k;
          entity.ad = this.locZ;
diff --git a/Spigot-Server-Patches/Change-implementation-of-tile-entity-removal-list.patch b/Spigot-Server-Patches/Change-implementation-of-tile-entity-removal-list.patch
index d4148ea254..0218e03cf6 100644
--- a/Spigot-Server-Patches/Change-implementation-of-tile-entity-removal-list.patch
+++ b/Spigot-Server-Patches/Change-implementation-of-tile-entity-removal-list.patch
@@ -8,15 +8,18 @@ diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/m
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/server/World.java
 +++ b/src/main/java/net/minecraft/server/World.java
-@@ -0,0 +0,0 @@ import org.bukkit.generator.ChunkGenerator;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.HashMap;
+@@ -0,0 +0,0 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+ import org.bukkit.generator.ChunkGenerator;
+ // CraftBukkit end
+ 
++// Paper start
 +import java.util.Set;
 +import com.google.common.collect.Sets;
- import com.google.common.util.concurrent.ThreadFactoryBuilder;
- // Paper end
++// Paper end
++
+ public abstract class World implements IBlockAccess {
  
+     private int a = 63;
 @@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
          }
      };
diff --git a/Spigot-Server-Patches/Configurable-async-light-updates.patch b/Spigot-Server-Patches/Configurable-async-light-updates.patch
deleted file mode 100644
index d9bff60b85..0000000000
--- a/Spigot-Server-Patches/Configurable-async-light-updates.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Byteflux <byte@byteflux.net>
-Date: Wed, 2 Mar 2016 00:52:31 -0600
-Subject: [PATCH] Configurable async light updates
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -0,0 +0,0 @@ public class PaperWorldConfig {
- 
-     }
- 
-+    public boolean useAsyncLighting;
-+    private void useAsyncLighting() {
-+        useAsyncLighting = false; //getBoolean( "use-async-lighting", false );
-+        log("World async lighting: " + useAsyncLighting);
-+    }
- }
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -0,0 +0,0 @@ import java.util.Map;
- import java.util.Random;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.concurrent.atomic.AtomicInteger; // Paper
-+
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- 
-@@ -0,0 +0,0 @@ public class Chunk {
-     private int w;
-     private ConcurrentLinkedQueue<BlockPosition> x;
-     protected gnu.trove.map.hash.TObjectIntHashMap<Class> entityCount = new gnu.trove.map.hash.TObjectIntHashMap<Class>(); // Spigot
-+    // Paper start - Asynchronous light updates
-+    public AtomicInteger pendingLightUpdates = new AtomicInteger();
-+    public long lightUpdateTime;
-+    // Paper end
- 
-     // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
-     private int neighbors = 0x1 << 12;
-@@ -0,0 +0,0 @@ public class Chunk {
-     private void a(int i, int j, int k, int l) {
-         if (l > k && this.world.areChunksLoaded(new BlockPosition(i, 0, j), 16)) {
-             for (int i1 = k; i1 < l; ++i1) {
--                this.world.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
-+                this.world.updateLight(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
-             }
- 
-             this.r = true;
-@@ -0,0 +0,0 @@ public class Chunk {
- 
-     public void b(boolean flag) {
-         if (this.l && !this.world.worldProvider.m() && !flag) {
--            this.h(this.world.isClientSide);
-+            this.recheckGaps(this.world.isClientSide); // Paper - Asynchronous lighting updates
-         }
- 
-         this.q = true;
-@@ -0,0 +0,0 @@ public class Chunk {
- 
-     }
- 
-+    /**
-+     * Paper- Recheck gaps asynchronously
-+     */
-+    public void recheckGaps(final boolean isClientSide) {
-+        if (!world.paperConfig.useAsyncLighting) {
-+            this.h(this.world.isClientSide);
-+            return;
-+        }
-+
-+        world.lightingExecutor.submit(new Runnable() {
-+            @Override
-+            public void run() {
-+                Chunk.this.h(isClientSide);
-+            }
-+        });
-+    }
-+
-     public boolean isReady() {
-         // Spigot Start
-         /*
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
-     }
- 
-     public void queueUnload(int i, int j) {
-+        // Paper start - Asynchronous lighting updates
-+        Chunk chunk = chunks.get(LongHash.toLong(i, j));
-+        if (chunk != null && chunk.world.paperConfig.useAsyncLighting && (chunk.pendingLightUpdates.get() > 0 || chunk.world.getTime() - chunk.lightUpdateTime < 20)) {
-+            return;
-+        }
-+        // Paper end
-         if (this.world.worldProvider.c(i, j)) {
-             // CraftBukkit start
-             this.unloadQueue.add(i, j);
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -0,0 +0,0 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
- import org.bukkit.generator.ChunkGenerator;
- // CraftBukkit end
- 
-+// Paper start
-+import java.util.concurrent.ExecutorService;
-+import java.util.concurrent.Executors;
-+import com.google.common.util.concurrent.ThreadFactoryBuilder;
-+// Paper end
-+
- public abstract class World implements IBlockAccess {
- 
-     private int a = 63;
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
-     private org.spigotmc.TickLimiter entityLimiter;
-     private org.spigotmc.TickLimiter tileLimiter;
-     private int tileTickPosition;
-+    public ExecutorService lightingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper - Lighting Thread").build()); // Paper - Asynchronous lighting updates
- 
-     public CraftWorld getWorld() {
-         return this.world;
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
- 
-         if (!this.worldProvider.m()) {
-             for (i1 = k; i1 <= l; ++i1) {
--                this.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
-+                this.updateLight(EnumSkyBlock.SKY, new BlockPosition(i, i1, j)); // Paper - Asynchronous lighting updates
-             }
-         }
- 
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
-         boolean flag = false;
- 
-         if (!this.worldProvider.m()) {
--            flag |= this.c(EnumSkyBlock.SKY, blockposition);
-+            flag |= this.updateLight(EnumSkyBlock.SKY, blockposition); // Paper - Asynchronous lighting updates
-         }
- 
--        flag |= this.c(EnumSkyBlock.BLOCK, blockposition);
-+        flag |= this.updateLight(EnumSkyBlock.BLOCK, blockposition); // Paper - Asynchronous lighting updates
-         return flag;
-     }
- 
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
-         }
-     }
- 
-+    // Paper start - Asynchronous lighting updates
-     public boolean c(EnumSkyBlock enumskyblock, BlockPosition blockposition) {
-+        return this.c(enumskyblock, blockposition, null, null);
-+    }
-+
-+    public boolean c(EnumSkyBlock enumskyblock, BlockPosition blockposition, Chunk chunk, List<Chunk> neighbors) { // Paper
-         // CraftBukkit start - Use neighbor cache instead of looking up
--        Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
--        if (chunk == null || !chunk.areNeighborsLoaded(1) /*!this.areChunksLoaded(blockposition, 17, false)*/) {
-+        //Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
-+        if (chunk == null /*|| !chunk.areNeighborsLoaded(1)*/ /*!this.areChunksLoaded(blockposition, 17, false)*/) {
-             // CraftBukkit end
-             return false;
-         } else {
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
-                 i = 0;
-             }
- 
-+            // Paper start - Asynchronous light updates
-+            if (chunk.world.paperConfig.useAsyncLighting) {
-+                chunk.pendingLightUpdates.decrementAndGet();
-+                if (neighbors != null) {
-+                    for (Chunk neighbor : neighbors) {
-+                        neighbor.pendingLightUpdates.decrementAndGet();
-+                    }
-+                }
-+            }
-+            // Paper end
-+
-             this.methodProfiler.b();
-             this.methodProfiler.a("checkedPosition < toCheckCount");
- 
-@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
-         }
-     }
- 
-+    /**
-+     * Paper - Asynchronous lighting updates
-+     */
-+    public boolean updateLight(final EnumSkyBlock enumskyblock, final BlockPosition position) {
-+        int x = position.getX();
-+        int z = position.getZ();
-+        final Chunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4);
-+        if (chunk == null || !chunk.areNeighborsLoaded(1)) {
-+            return false;
-+        }
-+
-+        if (!chunk.world.paperConfig.useAsyncLighting) {
-+            return this.c(enumskyblock, position, chunk, null);
-+        }
-+
-+        chunk.pendingLightUpdates.incrementAndGet();
-+        chunk.lightUpdateTime = chunk.world.getTime();
-+
-+        final List<Chunk> neighbors = new ArrayList<Chunk>();
-+
-+        for (int cx = (x >> 4) - 1; cx <= (x >> 4) + 1; ++cx) {
-+            for (int cz = (z >> 4) - 1; cz <= (z >> 4) + 1; ++cz) {
-+                if (cx != x >> 4 && cz != z >> 4) {
-+                    Chunk neighbor = this.getChunkIfLoaded(cx, cz);
-+                    if (neighbor != null) {
-+                        neighbor.pendingLightUpdates.incrementAndGet();
-+                        neighbor.lightUpdateTime = chunk.world.getTime();
-+                        neighbors.add(neighbor);
-+                    }
-+                }
-+            }
-+        }
-+
-+        if (!Bukkit.isPrimaryThread()) {
-+            return this.c(enumskyblock, position, chunk, neighbors);
-+        }
-+
-+        lightingExecutor.submit(new Runnable() {
-+            @Override
-+            public void run() {
-+                World.this.c(enumskyblock, position, chunk, neighbors);
-+            }
-+        });
-+        return true;
-+    }
-+
-     public boolean a(boolean flag) {
-         return false;
-     }
---
\ No newline at end of file
diff --git a/Spigot-Server-Patches/Configurable-end-credits.patch b/Spigot-Server-Patches/Configurable-end-credits.patch
index f51496b6e0..1a441f0553 100644
--- a/Spigot-Server-Patches/Configurable-end-credits.patch
+++ b/Spigot-Server-Patches/Configurable-end-credits.patch
@@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
 @@ -0,0 +0,0 @@ public class PaperWorldConfig {
-         useAsyncLighting = false; //getBoolean( "use-async-lighting", false );
-         log("World async lighting: " + useAsyncLighting);
+         queueLightUpdates = getBoolean("queue-light-updates", false);
+         log("Lighting Queue enabled: " + queueLightUpdates);
      }
 +
 +    public boolean disableEndCredits;
diff --git a/Spigot-Server-Patches/Lighting-Queue.patch b/Spigot-Server-Patches/Lighting-Queue.patch
new file mode 100644
index 0000000000..62df13fc4d
--- /dev/null
+++ b/Spigot-Server-Patches/Lighting-Queue.patch
@@ -0,0 +1,231 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Byteflux <byte@byteflux.net>
+Date: Wed, 2 Mar 2016 00:52:31 -0600
+Subject: [PATCH] Lighting Queue
+
+
+diff --git a/src/main/java/co/aikar/timings/SpigotTimings.java b/src/main/java/co/aikar/timings/SpigotTimings.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/co/aikar/timings/SpigotTimings.java
++++ b/src/main/java/co/aikar/timings/SpigotTimings.java
+@@ -0,0 +0,0 @@ public final class SpigotTimings {
+     public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update");
+     public static final Timing serverCommandTimer = Timings.ofSafe("Server Command");
+     public static final Timing worldSaveTimer = Timings.ofSafe("World Save");
++    public static final Timing lightingQueueTimer = Timings.ofSafe("Lighting Queue");
+ 
+     public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity");
+     public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity");
+diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+@@ -0,0 +0,0 @@ public class PaperWorldConfig {
+ 
+     }
+ 
++    public boolean queueLightUpdates;
++    private void queueLightUpdates() {
++        queueLightUpdates = getBoolean("queue-light-updates", false);
++        log("Lighting Queue enabled: " + queueLightUpdates);
++    }
+ }
+diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/Chunk.java
++++ b/src/main/java/net/minecraft/server/Chunk.java
+@@ -0,0 +0,0 @@ public class Chunk {
+     private int w;
+     private ConcurrentLinkedQueue<BlockPosition> x;
+     protected gnu.trove.map.hash.TObjectIntHashMap<Class> entityCount = new gnu.trove.map.hash.TObjectIntHashMap<Class>(); // Spigot
++    public int lightUpdates; // Paper - Number of queued light updates for this chunk
+ 
+     // CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
+     private int neighbors = 0x1 << 12;
+@@ -0,0 +0,0 @@ public class Chunk {
+     private void h(boolean flag) {
+         this.world.methodProfiler.a("recheckGaps");
+         if (this.world.areChunksLoaded(new BlockPosition(this.locX * 16 + 8, 0, this.locZ * 16 + 8), 16)) {
++            // Paper start - Queue light update
++            if (!world.paperConfig.queueLightUpdates) {
++                recheckGaps(flag);
++            } else {
++                ++lightUpdates;
++                world.getServer().getServer().lightingQueue.add(() -> {
++                    recheckGaps(flag);
++                    --lightUpdates;
++                });
++            }
++        }
++    }
++
++    private void recheckGaps(boolean flag) {
++        if (true) {
++            // Paper end
+             for (int i = 0; i < 16; ++i) {
+                 for (int j = 0; j < 16; ++j) {
+                     if (this.h[i + j * 16]) {
+@@ -0,0 +0,0 @@ public class Chunk {
+             } else {
+                 if (flag) {
+                     this.initLighting();
+-                } else {
++                } else if (!world.paperConfig.queueLightUpdates) { // Paper
+                     int j1 = iblockdata.c();
+                     int k1 = iblockdata1.c();
+ 
+@@ -0,0 +0,0 @@ public class Chunk {
+                     if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
+                         this.d(i, k);
+                     }
++                    // Paper start - Queue light update
++                } else {
++                    int j1 = iblockdata.c();
++                    int k1 = iblockdata1.c();
++
++                    ++lightUpdates;
++                    world.getServer().getServer().lightingQueue.add(() -> {
++                        if (j1 > 0) {
++                            if (j >= i1) {
++                                this.c(i, j + 1, k);
++                            }
++                        } else if (j == i1 - 1) {
++                            this.c(i, j, k);
++                        }
++
++                        if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
++                            this.d(i, k);
++                        }
++
++                        --lightUpdates;
++                    });
++                    // Paper end
+                 }
+ 
+                 TileEntity tileentity;
+@@ -0,0 +0,0 @@ public class Chunk {
+ 
+         private EnumTileEntityState() {}
+     }
++
++    // Paper start
++    public boolean hasLightUpdates() {
++        if (world.paperConfig.queueLightUpdates) {
++            if (lightUpdates > 0) {
++                return true;
++            }
++
++            for (int x = locX - 2; x <= locX + 2; ++x) {
++                for (int z = locZ - 2; z <= locZ + 2; ++z) {
++                    if ((x == 0 && z == 0) || (x == locX && z == locZ)) {
++                        continue;
++                    }
++
++                    Chunk chunk = world.getChunkIfLoaded(x, z);
++                    if (chunk != null && chunk.lightUpdates > 0) {
++                        return true;
++                    }
++                }
++            }
++        }
++
++        return false;
++    }
++    // Paper end
+ }
+diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
++++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
+@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
+                 long chunkcoordinates = this.unloadQueue.popFirst();
+                 Chunk chunk = this.chunks.get(chunkcoordinates);
+                 if (chunk == null) continue;
++                if (chunk.hasLightUpdates()) continue; // Paper - Don't unload chunks with pending light updates.
+ 
+                 ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
+                 server.getPluginManager().callEvent(event);
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftServer;
+ // CraftBukkit end
+ import co.aikar.timings.SpigotTimings; // Paper
+ 
++// Paper start
++import java.util.LinkedList;
++import java.util.Queue;
++// Paper end
++
+ public abstract class MinecraftServer implements Runnable, ICommandListener, IAsyncTaskHandler, IMojangStatistics {
+ 
+     public static final Logger LOGGER = LogManager.getLogger();
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
+     public final Thread primaryThread;
+     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
+     public int autosavePeriod;
++    public final Queue<Runnable> lightingQueue = new LinkedList<Runnable>(); // Paper - Queued light updates
+     // CraftBukkit end
+ 
+     public MinecraftServer(OptionSet options, Proxy proxy, DataConverterManager dataconvertermanager, YggdrasilAuthenticationService yggdrasilauthenticationservice, MinecraftSessionService minecraftsessionservice, GameProfileRepository gameprofilerepository, UserCache usercache) {
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
+ 
+         this.methodProfiler.b();
+         this.methodProfiler.b();
++
++        // Paper start - Flush light updates
++        if (!lightingQueue.isEmpty()) {
++            SpigotTimings.lightingQueueTimer.startTiming();
++
++            int updatesThisTick = 0;
++            long cachedTime = System.currentTimeMillis();
++            long startTime = cachedTime - (this.h[this.ticks % 100] / 1000000);
++            int maxTickTimeCap = MathHelper.floor((TICK_TIME / 1000000) * 0.8);
++            int maxTickTime = Math.max(0, (int) (maxTickTimeCap - (cachedTime - startTime)));
++            Runnable lightUpdate;
++
++            while (maxTickTime > 0 && (lightUpdate = lightingQueue.poll()) != null) {
++                lightUpdate.run();
++                if (++updatesThisTick % 10 == 0) {
++                    long currentTime = System.currentTimeMillis();
++                    if (currentTime - cachedTime > maxTickTime) {
++                        break;
++                    }
++
++                    cachedTime = currentTime;
++                    maxTickTime = Math.max(0, (int) (maxTickTimeCap - (currentTime - startTime)));
++                }
++            }
++
++            SpigotTimings.lightingQueueTimer.stopTiming();
++        }
++        // Paper end
++
+         org.spigotmc.WatchdogThread.tick(); // Spigot
+         co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper
+     }
+diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/World.java
++++ b/src/main/java/net/minecraft/server/World.java
+@@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
+             } else {
+                 if (iblockdata.c() != iblockdata1.c() || iblockdata.d() != iblockdata1.d()) {
+                     this.methodProfiler.a("checkLight");
+-                    this.w(blockposition);
++                    // Paper start - Queue light update
++                    if (!paperConfig.queueLightUpdates) {
++                        this.w(blockposition);
++                    } else {
++                        ++chunk.lightUpdates;
++                        getMinecraftServer().lightingQueue.add(() -> {
++                            this.w(blockposition);
++                            --chunk.lightUpdates;
++                        });
++                    }
++                    // Paper end
+                     this.methodProfiler.b();
+                 }
+ 
+--
\ No newline at end of file
diff --git a/Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch b/Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch
index 9a44a83bad..ef7d7c4b60 100644
--- a/Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch
+++ b/Spigot-Server-Patches/Optimize-Chunk-Unload-Queue.patch
@@ -144,9 +144,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    continue;
 +                }
 +                // Paper end
+                 if (chunk.hasLightUpdates()) continue; // Paper - Don't unload chunks with pending light updates.
  
                  ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
-                 server.getPluginManager().callEvent(event);
 @@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
                                  continue;
                              }
diff --git a/Spigot-Server-Patches/Optimize-explosions.patch b/Spigot-Server-Patches/Optimize-explosions.patch
index cea94c8116..afbda3644a 100644
--- a/Spigot-Server-Patches/Optimize-explosions.patch
+++ b/Spigot-Server-Patches/Optimize-explosions.patch
@@ -139,19 +139,19 @@ diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/m
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/server/World.java
 +++ b/src/main/java/net/minecraft/server/World.java
-@@ -0,0 +0,0 @@ import org.bukkit.generator.ChunkGenerator;
- // Paper start
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
-+import java.util.HashMap;
- import com.google.common.util.concurrent.ThreadFactoryBuilder;
- // Paper end
+@@ -0,0 +0,0 @@ import java.util.concurrent.Callable;
  
+ // CraftBukkit start
+ import com.google.common.collect.Maps;
++import java.util.HashMap; // Paper
+ import java.util.Map;
+ import org.bukkit.Bukkit;
+ import org.bukkit.block.BlockState;
 @@ -0,0 +0,0 @@ public abstract class World implements IBlockAccess {
+     private org.spigotmc.TickLimiter entityLimiter;
      private org.spigotmc.TickLimiter tileLimiter;
      private int tileTickPosition;
-     public ExecutorService lightingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Paper - Lighting Thread").build()); // Paper - Asynchronous lighting updates
-+    public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<Explosion.CacheKey, Float>(); // Paper - Optimize explosions
++    public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
  
      public CraftWorld getWorld() {
          return this.world;