Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use entry.unguardedRecycle() instead of entry.recycle() in method MemoryRegionCache.freeEntry(...) #13727

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions buffer/src/main/java/io/netty/buffer/PoolThreadCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ public final void trim() {
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private void freeEntry(Entry entry, boolean finalizer) {
private void freeEntry(Entry entry, boolean finalizer) {
// Capture entry state before we recycle the entry object.
PoolChunk chunk = entry.chunk;
long handle = entry.handle;
Expand All @@ -438,7 +438,7 @@ private void freeEntry(Entry entry, boolean finalizer) {
if (!finalizer) {
// recycle now so PoolChunk can be GC'ed. This will only be done if this is not freed because of
// a finalizer.
entry.recycle();
entry.unguardedRecycle();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correctly always previously used by another (atomic) operation that shield it?
Given that the previous one isn't visible is not easy to tell by reading that snippet of code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franz1981 It seems this snippet of code is always called by the thread which consumes the MemoryRegionCache.queue, and the MemoryRegionCache.queue is a Mpsc queue, so I think it's safe to use entry.unguardedRecycle() here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who consumes the MemoryRegionCache.queue

who consume or offer vs it?
Because consumption is (although it is an impl detail of JMM, actually, so take it with the grain of salt):

slot read item 
loadload + loadstore (load-acquire the slot element)
storestore + loadstore (store-release the null value in the slot element)
set slot to null
// and potentially
storestore + loadstore (store-release the entry)
unguarded release

which should end up to

STATE_UPDATER.lazySet(this, STATE_AVAILABLE);

In short, the ordering it's not a problem, concurrency can be, but given that it's a single-consumer queue I see no harm, although it's brittle because it's not immediately visible in the method signature.

Did you have any benchmark (or micro) which show that as an hot spot we care about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@franz1981
The thread which releases byteBuf offer the MemoryRegionCache.queue.

I have not done the benchmark yet.

I noticed this because there is a similar entry.unguardedRecycle() calling in method PoolThreadCache.MemoryRegionCache.allocate:

Which has the similar logic with this PR's change, so I think it's better to make both 'recycle' operations calling the same entry.unguardedRecycle() method.

In current implementation, the entry is either recycled by the thread which created it, or by the thread which consumed the Mpsc queue, if the two threads are not the same, then the two threads are mutually exclusive to recycle the entry.
Every thread gets the entry from threadLocal, so there should be no concurrency problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should first really benchmark and see if this makes any difference

}

chunk.arena.freeChunk(chunk, handle, normCapacity, sizeClass, nioBuffer, finalizer);
Expand Down