Apply a lot of synchronization to ChunkHolder (#941)

This is basically the main "chunk" class for internal FAWE. Chunk operations should (and are) almost always single-threaded operations, however, under certain circumstances it is possible for the chunk to be "called" (flushed: written to the world and sent to the player) from a separate thread. This would specifically occur from SingleThreadQueueExtent when there are a lot of chunks being loaded in memory by FAWE (where the chunk would then be submitted to a multi-threaded queue). It would therefore be possible for a thread accessing the chunk to attempt to access it in the middle of the call, which can lead to a number of issues, and it is my opinion that the most frequent of these is the NPE seen during lighting operations, where new chunks can be accessed/loaded very quickly, increasing the likelihood for the aforementioned synchronisation issue to occur.

Co-authored-by: Matt <4009945+MattBDev@users.noreply.github.com>
This commit is contained in:
dordsor21 2021-03-03 14:26:00 +00:00 committed by GitHub
parent 6bd866bdf7
commit 7a0dc39eb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -59,11 +59,11 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
@Override
public void recycle() {
public synchronized void recycle() {
delegate = NULL;
}
public IBlockDelegate getDelegate() {
public synchronized IBlockDelegate getDelegate() {
return delegate;
}
@ -98,11 +98,13 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return delegate.set(this).getBiomes();
}
@Override public char[][] getLight() {
@Override
public char[][] getLight() {
return delegate.set(this).getLight();
}
@Override public char[][] getSkyLight() {
@Override
public char[][] getSkyLight() {
return delegate.set(this).getSkyLight();
}
@ -139,34 +141,36 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
@Override
public CompoundTag getEntity(UUID uuid) {
public synchronized CompoundTag getEntity(UUID uuid) {
return delegate.get(this).getEntity(uuid);
}
@Override public void setCreateCopy(boolean createCopy) {
@Override
public void setCreateCopy(boolean createCopy) {
this.createCopy = createCopy;
}
@Override public boolean isCreateCopy() {
@Override
public boolean isCreateCopy() {
return createCopy;
}
@Override
public void setLightingToGet(char[][] lighting) {
public synchronized void setLightingToGet(char[][] lighting) {
delegate.setLightingToGet(this, lighting);
}
@Override
public void setSkyLightingToGet(char[][] lighting) {
public synchronized void setSkyLightingToGet(char[][] lighting) {
delegate.setSkyLightingToGet(this, lighting);
}
@Override
public void setHeightmapToGet(HeightMapType type, int[] data) {
public synchronized void setHeightmapToGet(HeightMapType type, int[] data) {
delegate.setHeightmapToGet(this, type, data);
}
public void flushLightToGet(boolean heightmaps) {
public synchronized void flushLightToGet(boolean heightmaps) {
delegate.flushLightToGet(this, heightmaps);
}
@ -796,22 +800,22 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
};
@Override
public Map<BlockVector3, CompoundTag> getTiles() {
public synchronized Map<BlockVector3, CompoundTag> getTiles() {
return delegate.get(this).getTiles();
}
@Override
public Set<CompoundTag> getEntities() {
public synchronized Set<CompoundTag> getEntities() {
return delegate.get(this).getEntities();
}
@Override
public boolean hasSection(int layer) {
public synchronized boolean hasSection(int layer) {
return chunkExisting != null && chunkExisting.hasSection(layer);
}
@Override
public void filterBlocks(Filter filter, ChunkFilterBlock block, @Nullable Region region, boolean full) {
public synchronized void filterBlocks(Filter filter, ChunkFilterBlock block, @Nullable Region region, boolean full) {
final IChunkGet get = getOrCreateGet();
final IChunkSet set = getOrCreateSet();
set.setFastMode(fastmode);
@ -823,7 +827,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
@Override
public boolean trim(boolean aggressive) {
public synchronized boolean trim(boolean aggressive) {
if (chunkSet != null) {
final boolean result = chunkSet.trim(aggressive);
if (result) {
@ -847,7 +851,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
@Override
public boolean trim(boolean aggressive, int layer) {
public synchronized boolean trim(boolean aggressive, int layer) {
return this.trim(aggressive);
}
@ -859,7 +863,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
/**
* Get or create the existing part of this chunk.
*/
public final IChunkGet getOrCreateGet() {
public synchronized final IChunkGet getOrCreateGet() {
if (chunkExisting == null) {
chunkExisting = newWrappedGet();
}
@ -869,7 +873,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
/**
* Get or create the settable part of this chunk.
*/
public final IChunkSet getOrCreateSet() {
public synchronized final IChunkSet getOrCreateSet() {
if (chunkSet == null) {
chunkSet = newWrappedSet();
}
@ -881,7 +885,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* - The purpose of wrapping is to allow different extents to intercept / alter behavior
* - e.g., caching, optimizations, filtering
*/
private IChunkSet newWrappedSet() {
private synchronized IChunkSet newWrappedSet() {
return extent.getCachedSet(chunkX, chunkZ);
}
@ -890,12 +894,12 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* - The purpose of wrapping is to allow different extents to intercept / alter behavior
* - e.g., caching, optimizations, filtering
*/
private IChunkGet newWrappedGet() {
private synchronized IChunkGet newWrappedGet() {
return extent.getCachedGet(chunkX, chunkZ);
}
@Override
public <V extends IChunk> void init(IQueueExtent<V> extent, int chunkX, int chunkZ) {
public synchronized <V extends IChunk> void init(IQueueExtent<V> extent, int chunkX, int chunkZ) {
this.extent = extent;
this.chunkX = chunkX;
this.chunkZ = chunkZ;
@ -913,13 +917,17 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
public synchronized T call() {
if (chunkSet != null) {
chunkSet.setBitMask(bitMask);
return this.call(chunkSet, this::recycle);
return this.call(chunkSet, () -> {
synchronized (this) {
this.recycle();
}
});
}
return null;
}
@Override
public T call(IChunkSet set, Runnable finalize) {
public synchronized T call(IChunkSet set, Runnable finalize) {
if (set != null) {
IChunkGet get = getOrCreateGet();
get.trim(false);
@ -955,79 +963,82 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
@Override
public boolean setBiome(int x, int y, int z, BiomeType biome) {
public synchronized boolean setBiome(int x, int y, int z, BiomeType biome) {
return delegate.setBiome(this, x, y, z, biome);
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
public synchronized <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
return delegate.setBlock(this, x, y, z, block);
}
@Override
public BiomeType getBiomeType(int x, int y, int z) {
public synchronized BiomeType getBiomeType(int x, int y, int z) {
return delegate.getBiome(this, x, y, z);
}
@Override
public BlockState getBlock(int x, int y, int z) {
public synchronized BlockState getBlock(int x, int y, int z) {
return delegate.getBlock(this, x, y, z);
}
@Override
public BaseBlock getFullBlock(int x, int y, int z) {
public synchronized BaseBlock getFullBlock(int x, int y, int z) {
return delegate.getFullBlock(this, x, y, z);
}
@Override
public void setSkyLight(int x, int y, int z, int value) {
public synchronized void setSkyLight(int x, int y, int z, int value) {
delegate.setSkyLight(this, x, y, z, value);
}
@Override public void setHeightMap(HeightMapType type, int[] heightMap) {
@Override
public synchronized void setHeightMap(HeightMapType type, int[] heightMap) {
delegate.setHeightMap(this, type, heightMap);
}
@Override public void removeSectionLighting(int layer, boolean sky) {
@Override
public synchronized void removeSectionLighting(int layer, boolean sky) {
delegate.removeSectionLighting(this, layer, sky);
}
@Override public void setFullBright(int layer) {
@Override
public synchronized void setFullBright(int layer) {
delegate.setFullBright(this, layer);
}
@Override
public void setBlockLight(int x, int y, int z, int value) {
public synchronized void setBlockLight(int x, int y, int z, int value) {
delegate.setBlockLight(this, x, y, z, value);
}
@Override
public void setLightLayer(int layer, char[] toSet) {
public synchronized void setLightLayer(int layer, char[] toSet) {
delegate.setLightLayer(this, layer, toSet);
}
@Override
public void setSkyLightLayer(int layer, char[] toSet) {
public synchronized void setSkyLightLayer(int layer, char[] toSet) {
delegate.setSkyLightLayer(this, layer, toSet);
}
@Override
public int getSkyLight(int x, int y, int z) {
public synchronized int getSkyLight(int x, int y, int z) {
return delegate.getSkyLight(this, x, y, z);
}
@Override
public int getEmmittedLight(int x, int y, int z) {
public synchronized int getEmmittedLight(int x, int y, int z) {
return delegate.getEmmittedLight(this, x, y, z);
}
@Override
public int getBrightness(int x, int y, int z) {
public synchronized int getBrightness(int x, int y, int z) {
return delegate.getBrightness(this, x, y, z);
}
@Override
public int getOpacity(int x, int y, int z) {
public synchronized int getOpacity(int x, int y, int z) {
return delegate.getOpacity(this, x, y, z);
}