Actually passing the ownership between threads should not be encouraged for recyclable objects. You can ask AI (like Gemini) the following question:
> Is it a good practice to share a recyclable object (allocated by Netty > Recycler) among different threads? The key point for the ownership passing case: > If you have a pattern of frequent cross-thread recycling, you're bypassing > the performance benefits of the thread-local stacks and introducing overhead. Though this pattern has already been widely used in Pulsar, which trades the performance and fallibility for heap memory allocation. - The performance overhead is mainly the `pollLast()` and `relaxedOffer()` methods on a platform independent MPSC (multiple producers single consumer) queue. What's more, we can see many thread switching to guarantee thread safety on the fields of a recyclable object (e.g. the `ArrayList` used in `OpReadEntry#entries`) - The fallibility can be shown from my previous example. Using a recyclable object is much more error-prone than using an immutable object (Java record). - The saved heap memory is never measured, at least I've never seen a test report. Anyway, it's already the case here. The best efforts we can do without much refactoring is to keep the unique ownership for a recyclable object. Thanks, Yunze On Sun, Aug 3, 2025 at 1:35 PM Yunze Xu <x...@apache.org> wrote: > > Hi Penghui, > > Introducing the reference count adds unnecessary overhead and might > not make things better. Generally, a recyclable object should not be > used concurrently, though the recyclable object might be passed across > threads. We should avoid sharing the same recyclable object among > multiple threads in the code path, rather than adding a reference > count. > > I understand Java programmers hardly take object ownership (unique vs. > shared) into consideration because of GC, but when the Netty recycler > is involved, we have to face the ownership issue. It's not just for > the sake of performance, it's also for correctness. > > Given the following example: > - Thread A creates a recyclable object O and publishes the reference > to thread B and C > - Thread B executes a normal task on O and then recycles O > - Thread C detects if O has not been recycled by checking `O.field != > null`, and then read `O.field` for following logic. > > Firstly, there could be an ABA issue that `O.field != null` does not > mean O has not been recycled. It could also be that the previous > referenced O has been recycled and allocated from the pool again. Then > `O.field` should not be expected. > Secondly, there could be a thread safety issue. Generally, the access > to O must be protected by a lock for thread safety, but we usually > don't do that because the fields are only initialized once in the > factory method of O. However, there is usually a `recycle()` method > that reset all fields to null before recycling the object, which means > that in a certain time point, thread B modifies O.field while thread C > reads O.field. > > The root cause is that the ownership of O is shared between B and C. > If we can guarantee the ownership is unique, this issue will > disappear. > > Thanks, > Yunze > > On Tue, Jul 29, 2025 at 11:29 PM PengHui Li <peng...@apache.org> wrote: > > > > I think the question is a more general question about reusing objects, not > > only for failed recyclable objects. > > Can we add a reference counter to the recyclable objects (implement the > > ReferenceCounted interface), > > There could be some corner cases that were missed in the release of the > > counter. > > We can log and skip the object recycling. > > But it will prevent one object from being used by 2 references at the same > > time. > > > > - Penghui > > > > > > > > On Thu, Jul 17, 2025 at 9:17 AM Ran Gao <r...@apache.org> wrote: > > > > > Thanks for your explanation. Reusing the corrupted recycle object is > > > dangerous, and it's hard to investigate issues from the heap dump. > > > > > > Thanks, > > > Ran Gao > > > > > > On 2025/07/17 13:38:38 Lari Hotari wrote: > > > > Recycling can cause bugs that are very hard to detect. The main reason > > > to avoid recycling in exceptional cases is to prevent such bugs. One > > > possible scenario is when another thread continues to hold a reference to > > > an object instance. After the object instance has been recycled, it could > > > refer to a completely different object instance. We have encountered such > > > bugs in the past with Pulsar, which is why it's better to avoid the risk > > > of > > > these bugs altogether. > > > > > > > > Netty doesn't recycle all object instances when recycling is enabled. > > > There's currently a per-thread limit of 4,096 object instances by default. > > > Omitting recycling in exceptional paths won't add noticeable extra GC > > > pressure because many instances will be discarded in any case, due to > > > recycling cache overflow when using default Netty settings. > > > > > > > > -Lari > > > > > > > > On 2025/07/17 12:47:22 Yunze Xu wrote: > > > > > Hi all, > > > > > > > > > > I noticed there is a difference of opinion for whether to recycle > > > > > failed recyclable objects. [1][2][3] So I'd like to open the > > > > > discussion in the mail list. > > > > > > > > > > Netty recycler is an object pool to reduce GC pressure for frequently > > > > > created and discarded objects. If a recyclable object fails, e.g. when > > > > > an `OpAddEntry` encounters an unexpected exception, IMO, it's > > > > > reasonable not to recycle it. > > > > > > > > > > When a recyclable object is not recycled, it just won't be reused from > > > > > the object pool. The worst case is that the memory of the whole object > > > > > pool is exhausted. After that > > > > > > > > > > It's true that if such objects are not recycled, there will be a > > > > > "memory leak". However, the worst result is that the pool memory is > > > > > exhausted. In this case, it will fall back to allocating objects via > > > > > the `newObject` method with a dummy handle [4]. The implementation of > > > > > this method usually allocates memory from JVM. In such cases, the > > > > > recyclable object will eventually be garbage collected. > > > > > > > > > > It's actually not a memory leak. Recyclable objects usually have short > > > > > life timing, for example, an `OpAddEntry` object is created when > > > > > starting an asynchronous send and recycled after the send is done. The > > > > > object is never referenced by any other object, so even if it's > > > > > allocated from JVM, it will eventually be garbage collected. > > > > > > > > > > The benefit to skip recycling objects for failures is that for rare > > > > > cases, retaining the object could help diagnose issues that are hard > > > > > to reproduce. Still take `OpAddEntry` for example, if it encountered > > > > > an unexpected exception, recycling it could set all fields with null, > > > > > so it's hard to know which ledger it belongs to (and other useful > > > > > fields) from the heap dump. > > > > > > > > > > [1] https://github.com/apache/pulsar/pull/24515#discussion_r2212602435 > > > > > [2] https://github.com/apache/pulsar/pull/24522#discussion_r2210814071 > > > > > [3] https://github.com/apache/pulsar/pull/24522#discussion_r2213100905 > > > > > [4] > > > https://github.com/netty/netty/blob/ab2d2cf3ff3a6f055368405af7f6e9dd5b8d144e/common/src/main/java/io/netty/util/Recycler.java#L189 > > > > > > > > > > Thanks, > > > > > Yunze > > > > > > > > > > > >