-
Notifications
You must be signed in to change notification settings - Fork 1
Description
It would be appreciated if you could check and consider this.
Environment
- BetterView Version: 2.1.2 (paper)
- Server: PaperMC 1.21.11
- Occurrence: Error logs are output several times per second for about an hour
Description
The plugin repeatedly throws io.netty.util.IllegalReferenceCountException: refCnt: 0, increment: 1 errors during normal operation. This occurs in the BetterViewManager.tickPlayer() method when processing chunk queue entries.
Error Log
[16:15:42] [Netty Epoll IO #2/ERROR]: [BetterView] Error while ticking player dev.booky.betterview.platform.PaperPlayer@122461be in level dev.booky.betterview.platform.PaperLevel@4733d124
io.netty.util.IllegalReferenceCountException: refCnt: 0, increment: 1
at io.netty.util.internal.ReferenceCountUpdater.retain0(ReferenceCountUpdater.java:117)
at io.netty.util.internal.ReferenceCountUpdater.retain(ReferenceCountUpdater.java:104)
at io.netty.buffer.AbstractReferenceCountedByteBuf.retain(AbstractReferenceCountedByteBuf.java:121)
at io.netty.buffer.AbstractPooledDerivedByteBuf.init(AbstractPooledDerivedByteBuf.java:64)
at io.netty.buffer.PooledSlicedByteBuf.newInstance0(PooledSlicedByteBuf.java:52)
at io.netty.buffer.PooledSlicedByteBuf.newInstance(PooledSlicedByteBuf.java:46)
at io.netty.buffer.PooledByteBuf.retainedSlice(PooledByteBuf.java:158)
at io.netty.buffer.PooledByteBuf.retainedSlice(PooledByteBuf.java:153)
at betterview-paper-2.1.2.jar//dev.booky.betterview.common.BetterViewPlayer.checkQueueEntry(BetterViewPlayer.java:327)
at java.base/java.util.ArrayDeque.bulkRemove(ArrayDeque.java:926)
at java.base/java.util.ArrayDeque.removeIf(ArrayDeque.java:900)
at betterview-paper-2.1.2.jar//dev.booky.betterview.common.BetterViewManager.tickPlayer(BetterViewManager.java:137)
...
Probable cause
The error occurs at line 327 in BetterViewPlayer.checkQueueEntry():
ByteBuf finalChunkBuf = chunkBuf.isReadable()
? chunkBuf.retainedSlice() // <- Exception thrown here
: this.level.getEmptyChunkBuf(chunkPos);The problem appears to be in the retain() and release() methods of the ChunkQueueEntry record (lines 464-473).
public ChunkQueueEntry retain() {
this.future.thenApply(ReferenceCountUtil::retain); // ❌ Problem
return this;
}
public ChunkQueueEntry release() {
this.future.thenApply(ReferenceCountUtil::release); // ❌ Problem
return this;
}thenApply() return value is discarded: thenApply returns a new CompletableFuture, but it's ignored, so the retain/release operations are not actually applied to the original buffer.
Doesn't this create a risk of race conditions and early release?
Amendment proposal
Replace thenApply() with whenComplete() to ensure retain/release operations are properly executed:
public ChunkQueueEntry retain() {
this.future.whenComplete((buf, throwable) -> {
if (buf != null && throwable == null) {
ReferenceCountUtil.retain(buf);
}
});
return this;
}
public ChunkQueueEntry release() {
this.future.whenComplete((buf, throwable) -> {
if (buf != null && throwable == null) {
ReferenceCountUtil.release(buf);
}
});
return this;
}Reproduction
Difficult to reproduce consistently, as it appears to be a race condition that manifests under specific timing conditions with asynchronous chunk loading.