Paper/nms-patches/ChunkRegionLoader.patch
Geoff Crossland 963b19c599 Fix a few chunk saving race conditions
* ChunkRegionLoader.c() picks an entry in the save queue, removes that entry from the save queue and then actually writes the entry to the region file. So, between the last two steps, the entry is neither in the save queue nor is it in the region file; if somebody loads the chunk again (with ChunkRegionLoader.loadChunk()) in that gap, they'll get old data. I've delayed the removal until the saving is done.
* ChunkRegionLoader.c() also records the coords of the chunks it's currently saving in this.c. ChunkRegionLoader.a(ChunkCoordIntPair, NBTTagCompound), which adds an entry to the save queue, stops the addition of an entry if its coords are in this.c. Now, I'm guessing that Mojang's intended purpose for this mechanism was to prevent multiple parallel writes for the same chunk. The "stops the addition" bit above should then be something like "block until it's no longer in c"; in fact, the vanilla implementation is "discard the new state of the chunk". I've taken the easy route to solving this, by just making ChunkRegionLoader.c() synchronized (since, in normal use, only the chunk saving thread is in here).
2017-08-11 17:34:19 +10:00

250 lines
9.6 KiB
Diff

--- a/net/minecraft/server/ChunkRegionLoader.java
+++ b/net/minecraft/server/ChunkRegionLoader.java
@@ -19,29 +19,47 @@
private static final Logger a = LogManager.getLogger();
private final Map<ChunkCoordIntPair, NBTTagCompound> b = Maps.newConcurrentMap();
- private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(Maps.newConcurrentMap());
+ // CraftBukkit
+ // private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(Maps.newConcurrentMap());
private final File d;
private final DataConverterManager e;
- private boolean f;
+ // private boolean f;
+ // CraftBukkit
public ChunkRegionLoader(File file, DataConverterManager dataconvertermanager) {
this.d = file;
this.e = dataconvertermanager;
}
+ // CraftBukkit start - Add async variant, provide compatibility
@Nullable
public Chunk a(World world, int i, int j) throws IOException {
+ Object[] data = loadChunk(world, i, j);
+ if (data != null) {
+ Chunk chunk = (Chunk) data[0];
+ NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
+ loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
+ return chunk;
+ }
+
+ return null;
+ }
+
+ public Object[] loadChunk(World world, int i, int j) throws IOException {
+ // CraftBukkit end
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
NBTTagCompound nbttagcompound = (NBTTagCompound) this.b.get(chunkcoordintpair);
if (nbttagcompound == null) {
- DataInputStream datainputstream = RegionFileCache.d(this.d, i, j);
+ // CraftBukkit start
+ nbttagcompound = RegionFileCache.d(this.d, i, j);
- if (datainputstream == null) {
+ if (nbttagcompound == null) {
return null;
}
- nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, NBTCompressedStreamTools.a(datainputstream));
+ nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, nbttagcompound);
+ // CraftBukkit end
}
return this.a(world, i, j, nbttagcompound);
@@ -55,7 +73,7 @@
}
@Nullable
- protected Chunk a(World world, int i, int j, NBTTagCompound nbttagcompound) {
+ protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
if (!nbttagcompound.hasKeyOfType("Level", 10)) {
ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
return null;
@@ -72,10 +90,28 @@
ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
nbttagcompound1.setInt("xPos", i);
nbttagcompound1.setInt("zPos", j);
+
+ // CraftBukkit start - Have to move tile entities since we don't load them at this stage
+ NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
+ if (tileEntities != null) {
+ for (int te = 0; te < tileEntities.size(); te++) {
+ NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
+ int x = tileEntity.getInt("x") - chunk.locX * 16;
+ int z = tileEntity.getInt("z") - chunk.locZ * 16;
+ tileEntity.setInt("x", i * 16 + x);
+ tileEntity.setInt("z", j * 16 + z);
+ }
+ }
+ // CraftBukkit end
chunk = this.a(world, nbttagcompound1);
}
- return chunk;
+ // CraftBukkit start
+ Object[] data = new Object[2];
+ data[0] = chunk;
+ data[1] = nbttagcompound;
+ return data;
+ // CraftBukkit end
}
}
}
@@ -98,7 +134,9 @@
}
protected void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) {
- if (!this.c.contains(chunkcoordintpair)) {
+ // CraftBukkit
+ // if (!this.c.contains(chunkcoordintpair))
+ {
this.b.put(chunkcoordintpair, nbttagcompound);
}
@@ -106,20 +144,32 @@
}
public boolean a() {
- if (this.b.isEmpty()) {
- if (this.f) {
+ // CraftBukkit start
+ return this.processSaveQueueEntry(false);
+ }
+
+ private synchronized boolean processSaveQueueEntry(boolean logCompletion) {
+ Iterator<Map.Entry<ChunkCoordIntPair, NBTTagCompound>> iter = this.b.entrySet().iterator();
+ if (!iter.hasNext()) {
+ if (logCompletion) {
+ // CraftBukkit end
ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.d.getName());
}
return false;
} else {
- ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) this.b.keySet().iterator().next();
+ // CraftBukkit start
+ Map.Entry<ChunkCoordIntPair, NBTTagCompound> entry = iter.next();
+ ChunkCoordIntPair chunkcoordintpair = entry.getKey();
+ NBTTagCompound nbttagcompound = entry.getValue();
+ // CraftBukkit end
boolean flag;
try {
- this.c.add(chunkcoordintpair);
- NBTTagCompound nbttagcompound = (NBTTagCompound) this.b.remove(chunkcoordintpair);
+ // this.c.add(chunkcoordintpair);
+ // NBTTagCompound nbttagcompound = (NBTTagCompound) this.b.remove(chunkcoordintpair);
+ // CraftBukkit
if (nbttagcompound != null) {
try {
@@ -131,7 +181,7 @@
flag = true;
} finally {
- this.c.remove(chunkcoordintpair);
+ this.b.remove(chunkcoordintpair, nbttagcompound); // CraftBukkit
}
return flag;
@@ -139,10 +189,14 @@
}
private void b(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException {
- DataOutputStream dataoutputstream = RegionFileCache.e(this.d, chunkcoordintpair.x, chunkcoordintpair.z);
+ // CraftBukkit start
+ RegionFileCache.e(this.d, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound);
+ /*
NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
dataoutputstream.close();
+ */
+ // CraftBukkit end
}
public void b(World world, Chunk chunk) throws IOException {}
@@ -151,15 +205,16 @@
public void c() {
try {
- this.f = true;
+ // this.f = true; // CraftBukkit
while (true) {
- if (this.a()) {
+ if (this.processSaveQueueEntry(true)) { // CraftBukkit
continue;
}
+ break; // CraftBukkit - Fix infinite loop when saving chunks
}
} finally {
- this.f = false;
+ // this.f = false; // CraftBukkit
}
}
@@ -334,6 +389,13 @@
chunk.a(nbttagcompound.getByteArray("Biomes"));
}
+ // CraftBukkit start - End this method here and split off entity loading to another method
+ return chunk;
+ }
+
+ public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
+ // CraftBukkit end
+
NBTTagList nbttaglist1 = nbttagcompound.getList("Entities", 10);
for (int l = 0; l < nbttaglist1.size(); ++l) {
@@ -371,7 +433,7 @@
}
}
- return chunk;
+ // return chunk; // CraftBukkit
}
@Nullable
@@ -399,14 +461,20 @@
}
@Nullable
+ // CraftBukkit start
public static Entity a(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag) {
+ return spawnEntity(nbttagcompound, world, d0, d1, d2, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
+ public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
+ // CraftBukkit end
Entity entity = a(nbttagcompound, world);
if (entity == null) {
return null;
} else {
entity.setPositionRotation(d0, d1, d2, entity.yaw, entity.pitch);
- if (flag && !world.addEntity(entity)) {
+ if (flag && !world.addEntity(entity, spawnReason)) { // CraftBukkit
return null;
} else {
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
@@ -435,8 +503,14 @@
}
}
+ // CraftBukkit start
public static void a(Entity entity, World world) {
- if (world.addEntity(entity) && entity.isVehicle()) {
+ a(entity, world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
+ public static void a(Entity entity, World world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+ if (world.addEntity(entity, reason) && entity.isVehicle()) {
+ // CraftBukkit end
Iterator iterator = entity.bF().iterator();
while (iterator.hasNext()) {