Mirror von
https://github.com/PaperMC/Paper.git
synchronisiert 2024-12-18 20:40:08 +01:00
#640: Fix chunk load/unload callbacks for chunk load cancellations
When a chunk goes from a ticket level where it is loading a full chunk to an inactive state (i.e ticket level 33 to ticket level 45) the full status future will be completed with a "Right" Either (indicating unloaded). However, this will also schedule the unload callback immediately. However, the callback is not immediately executed. This means the next unload/load callback that needs to be scheduled will fail. The fix applied is to not schedule a callback if the chunk is not loaded - if the Either is "right." Even better, due to how completablefuture works, exceptions are not printed by default. So the exception thrown by the callback executor was not printed and the failure hidden from console. This explains why no-one has tracked this issue. Now the exception is printed so future failures with the callback system (if any) can be tracked easier.
Dieser Commit ist enthalten in:
Ursprung
13ed05decd
Commit
7f61a2526e
@ -46,23 +46,29 @@
|
|||||||
|
|
||||||
if (either == null || either.left().isPresent()) {
|
if (either == null || either.left().isPresent()) {
|
||||||
return completablefuture;
|
return completablefuture;
|
||||||
@@ -278,6 +287,24 @@
|
@@ -278,6 +287,30 @@
|
||||||
boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET;
|
boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET;
|
||||||
PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel);
|
PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel);
|
||||||
PlayerChunk.State playerchunk_state1 = getChunkState(this.ticketLevel);
|
PlayerChunk.State playerchunk_state1 = getChunkState(this.ticketLevel);
|
||||||
+ // CraftBukkit start
|
+ // CraftBukkit start
|
||||||
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
+ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
|
||||||
+ if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
+ if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
||||||
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAcceptAsync((either) -> {
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
||||||
+ either.ifLeft((chunkAccess) -> {
|
+ Chunk chunk = (Chunk)either.left().orElse(null);
|
||||||
+ Chunk chunk = (Chunk) chunkAccess;
|
+ if (chunk != null) {
|
||||||
|
+ playerchunkmap.callbackExecutor.execute(() -> {
|
||||||
+ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
|
+ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
|
||||||
+ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
|
+ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
|
||||||
+ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
|
+ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
|
||||||
+ chunk.setNeedsSaving(true);
|
+ chunk.setNeedsSaving(true);
|
||||||
+ chunk.unloadCallback();
|
+ chunk.unloadCallback();
|
||||||
+ });
|
+ });
|
||||||
+ }, playerchunkmap.callbackExecutor);
|
+ }
|
||||||
|
+ }).exceptionally((throwable) -> {
|
||||||
|
+ // ensure exceptions are printed, by default this is not the case
|
||||||
|
+ MinecraftServer.LOGGER.fatal("Failed to schedule unload callback for chunk " + PlayerChunk.this.location, throwable);
|
||||||
|
+ return null;
|
||||||
|
+ });
|
||||||
+
|
+
|
||||||
+ // Run callback right away if the future was already done
|
+ // Run callback right away if the future was already done
|
||||||
+ playerchunkmap.callbackExecutor.run();
|
+ playerchunkmap.callbackExecutor.run();
|
||||||
@ -71,7 +77,7 @@
|
|||||||
CompletableFuture completablefuture;
|
CompletableFuture completablefuture;
|
||||||
|
|
||||||
if (flag) {
|
if (flag) {
|
||||||
@@ -309,7 +336,7 @@
|
@@ -309,7 +342,7 @@
|
||||||
if (flag2 && !flag3) {
|
if (flag2 && !flag3) {
|
||||||
completablefuture = this.fullChunkFuture;
|
completablefuture = this.fullChunkFuture;
|
||||||
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
|
||||||
@ -80,19 +86,25 @@
|
|||||||
playerchunkmap.getClass();
|
playerchunkmap.getClass();
|
||||||
return either1.ifLeft(playerchunkmap::a);
|
return either1.ifLeft(playerchunkmap::a);
|
||||||
}));
|
}));
|
||||||
@@ -347,6 +374,20 @@
|
@@ -347,6 +380,26 @@
|
||||||
|
|
||||||
this.w.a(this.location, this::k, this.ticketLevel, this::d);
|
this.w.a(this.location, this::k, this.ticketLevel, this::d);
|
||||||
this.oldTicketLevel = this.ticketLevel;
|
this.oldTicketLevel = this.ticketLevel;
|
||||||
+ // CraftBukkit start
|
+ // CraftBukkit start
|
||||||
+ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
+ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
|
||||||
+ if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
+ if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) {
|
||||||
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAcceptAsync((either) -> {
|
+ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
|
||||||
+ either.ifLeft((chunkAccess) -> {
|
+ Chunk chunk = (Chunk)either.left().orElse(null);
|
||||||
+ Chunk chunk = (Chunk) chunkAccess;
|
+ if (chunk != null) {
|
||||||
|
+ playerchunkmap.callbackExecutor.execute(() -> {
|
||||||
+ chunk.loadCallback();
|
+ chunk.loadCallback();
|
||||||
+ });
|
+ });
|
||||||
+ }, playerchunkmap.callbackExecutor);
|
+ }
|
||||||
|
+ }).exceptionally((throwable) -> {
|
||||||
|
+ // ensure exceptions are printed, by default this is not the case
|
||||||
|
+ MinecraftServer.LOGGER.fatal("Failed to schedule load callback for chunk " + PlayerChunk.this.location, throwable);
|
||||||
|
+ return null;
|
||||||
|
+ });
|
||||||
+
|
+
|
||||||
+ // Run callback right away if the future was already done
|
+ // Run callback right away if the future was already done
|
||||||
+ playerchunkmap.callbackExecutor.run();
|
+ playerchunkmap.callbackExecutor.run();
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren