-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Weak reference concurrent pool #10787
Weak reference concurrent pool #10787
Conversation
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference.
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. back link may be hard
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire removed ThreadLocal cache
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added Leak test
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
…tty-12/WeakReference-ConcurrentPool
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
@sbordet @lorban OK it was not as simple as it looked at first.... but I think I found the race (could be acquired and freed before it was held after release). I think I have fixed it with a spin test (scary). It now works for multiplexed pools as well. I removed the cache because I think it is an unnecessary complexity. |
…g configurable Signed-off-by: Ludovic Orban <[email protected]>
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
@lorban I reverted to use a weak pool by default (with system property to change default). Whilst we are an experimental branch, we want to test it as much as possible. Perhaps once we merge, we switch the default (and have a better way of setting it). |
jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/ConcurrentPoolTest.java
Show resolved
Hide resolved
@gregw that's cool, and I currently have no strong opinion about whether we should set the pool as weak by default or not. The reason why I made weak ref configurable is to measure the perf impact of this PR, so I want to compare in order:
I don't mind your modifications; as I can configure an |
Perf report (so far): This change has no measurable impact, no matter if weak referencing is enabled or not. The JIT inlines the entire buffer acquisition code in a single blob and most of the time is spent, in order: iterating the list, CASing the BiInteger in tryAcquire, updating the ref counter of the RetainableByteBuffer. Basically, the cost stayed where it was expected to be, so we can concentrate on making sure the feature works. 👍 |
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings no race in tests
@lorban so I'm doing a risk analysis for this, and I think the fundamental approach is a lot safer than before, as we rely on less moving parts. The only problem I can see is the spin wait to see a string link before it is cleaned. If we get that wrong, then we spin forever! My concern is what if between one spin and the next, the entry is acquired, released, acquired and released? Do we need a similar spin on the hold method, so that it waits to see the previous free? That will prevent multiple acquire/release cycles from happening during a spin.... actually I think I will implement that now! |
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings no race in tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this logic is correct, and unless I missed something, the race condition that you fix with a spin loop in hoder can only happen under one cirmunstance: when a release makes the multiplex counter go to 0 at the same time an acquire moves it to 1.
In that case, hold()
must execute first so the concurrent free()
call stays in the spin loop to wait for that moment before doing its job.
But I see no reason that would make hold()
spin as I can't see a race condition that would trigger this need.
Wether I'm right or wrong, these new states and their transitions should be very carefully documented.
{ | ||
Pool.Entry<P> getEntry(); | ||
|
||
void hold(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about renaming this method harden()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer release()
as it matches where it is called from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbordet but it is not a release. A release only reduces the multiplexed reference count and only if that goes to zero do we create a non-weak reference. So it is called from release, but is not the same.
Maybe lastRelease
or fullyReleased
? I don't mind harden
|
||
void hold(); | ||
|
||
void free(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about renaming this method weaken()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer acquire()
as it matches where it is called from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbordet again it is not an acquire. perhaps firstAcquire
? I don't mind weaken
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
Outdated
Show resolved
Hide resolved
{ | ||
Pool.Entry<P> getEntry(); | ||
|
||
void hold(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer release()
as it matches where it is called from.
|
||
void hold(); | ||
|
||
void free(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer acquire()
as it matches where it is called from.
holder = pool.weak ? new WeakHolder<>(this) : new StrongHolder<>(this); | ||
} | ||
|
||
private Holder<E> getHolder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just reference the final field and remove the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
Outdated
Show resolved
Hide resolved
* @param maxDirectMemory the max direct memory in bytes, -1 for unlimited memory or 0 to use default heuristic | ||
* @param weakPool true if the pooled buffers should be weakly referenced upon acquisition, false otherwise | ||
*/ | ||
public ArrayByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory, boolean weakPool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't really like the telescopic additional "weakPool" parameter -- seems an implementation detail that leaks many layers up, and if we change our mind it'll be part of the public APIs for a long time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbordet Isn't this at odds with your previous comment about preferring "explicit configuration". How would you configure this if not by constructor parameter? Should we just have a different classes for Weak pools?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gregw the comments above are now out of date, and the weakPool
parameter is never used, so please remove it.
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
@gregw @sbordet I think we now reached the point where we all agree that this idea is technically sound, will work, and is worth implementing. Besides naming (I'll skip that subject), there are still two points of disagreement or uncertainties:
Here are my current positions:
Thoughts? |
+1
On naming I can live with any of hold/free, strengthen/weaken, lastRelease/firstAcquire but not release/acquire
I think that is a reasonable default mechanism. However, I do think we should at least include something like my DebugWeakHolder, so that extra info can be obtained without the need to change code. However, I'd probably change it to make it a static determination of debug, to give the JIT every opportunity to optimize away virtual calls (does that work?). But for now, I think we should make this debug mechanism private and not extensible, as once protected it is part of the API.
+1 So long as we provide alternate classes that do simple or null pooling, then it is best to make our main ConcurrentPool implement the best algorithm we know and to not try to be too configurable unless there is good reason. |
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
@gregw I don't think |
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference. free on acquire added warnings
* Removed unused boolean parameter "weakPool" in ArrayByteBufferPool and updated javadocs. * Improved implementation of ConcurrentPool.Entry.tryEnable(). * Added javadocs for ConcurrentPool.Holder.free() and hold(). Signed-off-by: Simone Bordet <[email protected]>
* @param maxDirectMemory the max direct memory in bytes, -1 for unlimited memory or 0 to use default heuristic | ||
* @param weakPool true if the pooled buffers should be weakly referenced upon acquisition, false otherwise | ||
*/ | ||
public ArrayByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory, boolean weakPool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gregw the comments above are now out of date, and the weakPool
parameter is never used, so please remove it.
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java
Outdated
Show resolved
Hide resolved
if (!acquire) | ||
getHolder().hold(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In tryAcquire()
, the call to free()
is done after the CAS. I think we should build the Holder
with a strong reference by default, and only when we successfully CAS to 1 we call free()
.
In this way the two behaviors (here and in tryAcquire()
) are normalized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbordet Your change was a little different to this comment, but I think it is good.
jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
Outdated
Show resolved
Hide resolved
…ConcurrentPool'. Signed-off-by: Simone Bordet <[email protected]>
The Pool is now a list of Holder instances, each with a WeakReference to an Entry and an optional hard reference.