You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Netty 4.2 introduces a new default buffer allocator - see the migration guide and in particular:
Netty 4.2 changes the default allocator to adaptive. To de-risk this change, it is recommended to first upgrade to 4.2 while explicitly configuring the pooled allocator, and then remove the allocator configuration as a second step. To explicitly configure the pooled allocator, set this system property: io.netty.allocator.type=pooled
I have confirmed that in my codebase using lmdbjava, setting the system property means lmdbjava continues to work with netty 4.2.
My inclination would be to add a new buffer proxy PROXY_NETTY_ADAPTIVE, rename the older proxy to PROXY_NETTY_POOLED and for backwards compatibility keep PROXY_NETTY. The latter could default to the same as PROXY_NETTY_POOLED or attempt to choose the default based on netty version (Netty does provide a Version class so this could be determined at runtime and cached but with some added complexity obviously).
I asked Claude to help with this, and I will append its response in a separate message.
What do people think of this idea?
Would there be an appetite to look at a PR to add netty 4.2 support? This would allow performance testing of the new adaptive allocator?
lmdbjava 0.9.1's ByteBufProxy.PROXY_NETTY is incompatible with Netty 4.2's default buffer allocator. The current implementation requires PooledUnsafeDirectByteBuf, but Netty 4.2 changed the default allocator from pooled to adaptive, which creates AdaptivePoolingAllocator$AdaptiveByteBuf instead.
Background
Netty 4.2 Breaking Change
Netty 4.2 introduced a significant change to the default buffer allocator:
Netty 4.1: Default is PooledByteBufAllocator → creates PooledUnsafeDirectByteBuf
Netty 4.2: Default is AdaptivePoolingAllocator → creates AdaptivePoolingAllocator$AdaptiveByteBuf
// Line 1398privateAbstractByteBufrootParent;
// Line 1395privateintstartIndex;
// Line 1399privateintlength;
// Lines 1504-1507@OverridepubliclongmemoryAddress() {
ensureAccessible();
returnrootParent().memoryAddress() + startIndex;
}
Unsafe Operations
The same Unsafe operations used for PooledUnsafeDirectByteBuf should work with AdaptiveByteBuf, as it delegates to an underlying direct buffer (rootParent) which has a native memory address.
Testing Considerations
The solution should be tested with:
PROXY_NETTY_POOLED
Netty 4.1.x with default allocator
Netty 4.2.x with -Dio.netty.allocator.type=pooled
Verify PooledUnsafeDirectByteBuf is allocated and works correctly
PROXY_NETTY_ADAPTIVE
Netty 4.2.x with default allocator (adaptive)
Verify AdaptivePoolingAllocator$AdaptiveByteBuf is allocated and works correctly
Test memory address calculation with rootParent + startIndex
PROXY_NETTY (Version-Aware)
Netty 4.1.x → should delegate to PROXY_NETTY_POOLED
Netty 4.2.x → should delegate to PROXY_NETTY_ADAPTIVE
Verify version detection works correctly
Confirm zero runtime overhead (detection happens once)
@wardle The main challenge with Netty support in LmdbJava is that I don't use Netty, didn't write the original integration, and lack familiarity with it more broadly. Unfortunately, no one has volunteered to maintain it either, which is why issues like #215 have been open for 2.5 years.
Given these constraints, I think it makes sense to minimize complexity by supporting just a single Netty buffer type and focusing on getting that implementation solid. From what I understand, adaptive buffers seem to be the preferred approach now. Does that match your understanding? If you'd be able to contribute a PR for this, that would be fantastic. And if anyone else is working with Netty and has thoughts on the best path forward, I'd of course welcome their thoughts too.
In Netty 4.1 the adaptive memory allocator was experimental. We have now made it the new default instead of the pooled allocator.
We believe that most workloads will observe reduced memory usage, with performance that is on par or slightly better than the pooled allocator. The adaptive allocator automatically tunes itself to perform well for the observed workload. It was also designed from the start to work well with virtual threads.
However, every application use their allocator in different ways, and stress them differently, so it is expected that the adaptive allocator will perform worse for some. Integrators who wish to keep using the pooled allocator like they did in Netty 4.1, can change the default allocator by setting this system property:
io.netty.allocator.type=pooled
From my understanding, and reading through some of the issues raised against the project, there is uncertainty as to whether there are always performance gains. My assumption is that during wider usage of 4.2 with a variety of workloads, this may become clearer. As such, it appears that multiple variants of allocators are here to stay. Hence the design outlined above that provides a reasonable default, but permits explicit override when the client knows better than the library, which to be fair is often the case.
An entirely reasonable option would be to wait and stick with 4.1 for the time being, and understanding of the balances might become clearer. The problem with a wholesale move would be breaking backwards compatibility such that there may be clients who remain on an older version of netty for whatever reason.
Clearly this has a potential interaction with the planned removal of Unsafe (#259) and a potential move to the more modern Java FFI. That said, lmdbjava currently benefits from a 'pluggable' buffer implementation, and currently its unclear to me why one might not have a continued ability to switch between different implementations depending on use-case; certainly any such design would a) fit into the current model, and b) permit users to make that decision regarding trading safety for speed. I guess my thinking was that 'netty-adaptive' is just another allocator along with all of the others.
#259 is not intended to remove the pluggable buffers. They will be around for a long time yet. My initial reaction was more reflecting the challenges of supporting Netty to date and not wanting to exacerbate it by further edge cases emerging with additional Netty buffer variants. I hope someone will volunteer to maintain the Netty integration.
For the time being I'll leave this ticket so others have an opportunity to comment.
Cross-linking: #290 fixes the related Netty issue #215 (ByteBuf.nioBuffer() returning zeros) and touches the same ByteBufProxy read path this ticket is about. Worth keeping the two together — whatever the adaptive out() ends up doing will also determine its nioBuffer() behaviour, so #261 and #215 are effectively the same corner of the code.
I spent some time trying to reproduce this on Netty 4.2.0 and want to share what I found, because part of the pasted writeup doesn't hold up — and there is a real, fixable issue underneath, just with a different cause.
The stated mechanism appears to be disproven
The writeup says "ByteBufProxy.PROXY_NETTY fails … allocate() expects PooledUnsafeDirectByteBuf, gets AdaptivePoolingAllocator$AdaptiveByteBuf." I can't reproduce that, and it looks structurally impossible:
ByteBufProxy.allocate() calls nettyAllocator.directBuffer(), and nettyAllocator is typed PooledByteBufAllocator (and PROXY_NETTY uses PooledByteBufAllocator.DEFAULT). A PooledByteBufAllocator never returns an AdaptiveByteBuf.
Empirically, on 4.2.0 with adaptive confirmed as the default (ByteBufAllocator.DEFAULT → AdaptiveByteBuf), PooledByteBufAllocator.DEFAULT still returns PooledUnsafeDirectByteBuf, PROXY_NETTY constructs fine, and the full test suite passes on 4.2 (918/918), including put/get round-trips with adaptive user buffers.
(Minor: the writeup names the offset field startIndex; in 4.2.0 it's actually adjustment.)
The real, reproducible failure
ByteBufProxy.in() calls buffer.memoryAddress() unconditionally, which throws for any buffer with hasMemoryAddress()==false — i.e. any heap buffer:
java.lang.UnsupportedOperationException
at io.netty.buffer.PooledHeapByteBuf.memoryAddress(...)
at org.lmdbjava.ByteBufProxy.in(ByteBufProxy.java:201)
at org.lmdbjava.Dbi.put(...)
Netty 4.2's adaptive allocator can hand back heap-backedAdaptiveByteBuf (e.g. heapBuffer()), so 4.2 apps hit this — but it's not 4.2-specific: a plain PooledHeapByteBuf fails identically on 4.1. That likely also explains why -Dio.netty.allocator.type=pooled "fixed" it for you: it steers your buffers back to direct.
#291 fixes this by copying address-less buffers into native scratch in in() (the same approach ByteArrayProxy already uses), keeping the zero-copy fast path for direct buffers.
Could you share your actual failure?
Since the "class name mismatch" mechanism doesn't reproduce here, I'd like to make sure I'm not missing a different failure you saw. Do you have the actual stack trace / a minimal repro from your codebase without the property set? In particular it'd help to know whether your buffers were heap or direct, and your JDK + whether sun.misc.Unsafe was available (there's a separate, unconfirmed path where allocate() rejects a non-unsafe PooledDirectByteBuf if unsafe is disabled — that would be a genuine "class name mismatch," but about unsafe, not the adaptive allocator).
Separately: the full suite passing on 4.2 suggests the pinned Netty 4.1.118 could likely be bumped after the "further testing" the pom mentions — happy to open that separately if useful.
Activity
wardle commentedon Oct 25, 2025
Claude's research output:
Support for Netty 4.2 AdaptivePoolingAllocator
Summary
lmdbjava 0.9.1's
ByteBufProxy.PROXY_NETTYis incompatible with Netty 4.2's default buffer allocator. The current implementation requiresPooledUnsafeDirectByteBuf, but Netty 4.2 changed the default allocator from pooled to adaptive, which createsAdaptivePoolingAllocator$AdaptiveByteBufinstead.Background
Netty 4.2 Breaking Change
Netty 4.2 introduced a significant change to the default buffer allocator:
PooledByteBufAllocator→ createsPooledUnsafeDirectByteBufAdaptivePoolingAllocator→ createsAdaptivePoolingAllocator$AdaptiveByteBufFrom the Netty 4.2 Migration Guide:
While users can revert via
-Dio.netty.allocator.type=pooled, this defeats the purpose of upgrading to Netty 4.2 and is a global JVM setting.Current lmdbjava Implementation
The current
ByteBufProxyimplementation (v0.9.1) hardcodes the buffer class name:The allocator validates this class name and retries up to 10 times before throwing
IllegalStateExceptionif the wrong buffer type is returned.The Problem
When using Netty 4.2 with default settings,
ByteBufProxy.PROXY_NETTYfails because:PooledUnsafeDirectByteBuf, getsAdaptivePoolingAllocator$AdaptiveByteBufField Comparison
memoryAddresslengthmemoryAddressfieldrootParent.memoryAddress() + startIndexPooledUnsafeDirectByteBuf:
memoryAddressas a direct field accessible via reflectionUnsafeAdaptiveByteBuf:
AbstractReferenceCountedByteBuflengthfield ✓rootParentfield (AbstractByteBuf) - contains the actual bufferstartIndexfield - offset within the chunkmemoryAddress()method computes:rootParent().memoryAddress() + startIndexProposed Solution
Add explicit support for both allocator types while maintaining backward compatibility:
Three New Proxy Constants
Implementation Strategy
1. PROXY_NETTY_POOLED (Explicit Pooled)
io.netty.buffer.PooledUnsafeDirectByteBufmemoryAddressfield directlylengthfield directlyUsage:
2. PROXY_NETTY_ADAPTIVE (Explicit Adaptive)
io.netty.buffer.AdaptivePoolingAllocator$AdaptiveByteBufrootParentfield (AbstractByteBuf)startIndexfieldlengthfieldrootParent.memoryAddress() + startIndexUsage:
3. PROXY_NETTY (Backward Compatible Version-Aware)
Detects Netty version once at class initialization and delegates to the correct implementation:
Benefits:
PROXY_NETTYcontinues to workUsage:
Impact
Who is affected:
Current workarounds:
-Dio.netty.allocator.type=pooled(not ideal - global setting, defeats upgrade purpose)ByteBufferProxy.PROXY_OPTIMAL(different performance characteristics)Migration Guide
For Existing Code (No Changes Required)
Existing code using
PROXY_NETTYwill continue to work:The version-aware
PROXY_NETTYautomatically selects the correct implementation.For Explicit Control
If you want explicit control over the allocator (e.g., using pooled on Netty 4.2):
Before (Netty 4.1):
After (Netty 4.2 with explicit pooled):
After (Netty 4.2 with explicit adaptive):
Upgrading from Netty 4.1 to 4.2
PROXY_NETTYadapts automaticallyPROXY_NETTY_ADAPTIVEto ensure adaptive allocator usagePROXY_NETTY_POOLEDif adaptive allocator causes issuesImplementation Notes
Key Fields to Access in AdaptiveByteBuf
From AdaptivePoolingAllocator.java:
Unsafe Operations
The same
Unsafeoperations used forPooledUnsafeDirectByteBufshould work withAdaptiveByteBuf, as it delegates to an underlying direct buffer (rootParent) which has a native memory address.Testing Considerations
The solution should be tested with:
PROXY_NETTY_POOLED
-Dio.netty.allocator.type=pooledPooledUnsafeDirectByteBufis allocated and works correctlyPROXY_NETTY_ADAPTIVE
AdaptivePoolingAllocator$AdaptiveByteBufis allocated and works correctlyrootParent+startIndexPROXY_NETTY (Version-Aware)
General Tests (All Proxies)
References
Related Issues
bp-alex commentedon Oct 25, 2025
@wardle The main challenge with Netty support in LmdbJava is that I don't use Netty, didn't write the original integration, and lack familiarity with it more broadly. Unfortunately, no one has volunteered to maintain it either, which is why issues like #215 have been open for 2.5 years.
Given these constraints, I think it makes sense to minimize complexity by supporting just a single Netty buffer type and focusing on getting that implementation solid. From what I understand, adaptive buffers seem to be the preferred approach now. Does that match your understanding? If you'd be able to contribute a PR for this, that would be fantastic. And if anyone else is working with Netty and has thoughts on the best path forward, I'd of course welcome their thoughts too.
wardle commentedon Oct 25, 2025
Unfortunately not. Here is a summary of some of the documentation:
From my understanding, and reading through some of the issues raised against the project, there is uncertainty as to whether there are always performance gains. My assumption is that during wider usage of 4.2 with a variety of workloads, this may become clearer. As such, it appears that multiple variants of allocators are here to stay. Hence the design outlined above that provides a reasonable default, but permits explicit override when the client knows better than the library, which to be fair is often the case.
An entirely reasonable option would be to wait and stick with 4.1 for the time being, and understanding of the balances might become clearer. The problem with a wholesale move would be breaking backwards compatibility such that there may be clients who remain on an older version of netty for whatever reason.
Clearly this has a potential interaction with the planned removal of Unsafe (#259) and a potential move to the more modern Java FFI. That said, lmdbjava currently benefits from a 'pluggable' buffer implementation, and currently its unclear to me why one might not have a continued ability to switch between different implementations depending on use-case; certainly any such design would a) fit into the current model, and b) permit users to make that decision regarding trading safety for speed. I guess my thinking was that 'netty-adaptive' is just another allocator along with all of the others.
bp-alex commentedon Oct 25, 2025
Thanks @wardle for the additional information.
#259 is not intended to remove the pluggable buffers. They will be around for a long time yet. My initial reaction was more reflecting the challenges of supporting Netty to date and not wanting to exacerbate it by further edge cases emerging with additional Netty buffer variants. I hope someone will volunteer to maintain the Netty integration.
For the time being I'll leave this ticket so others have an opportunity to comment.
bernardladenthin commentedon Jul 24, 2026
Cross-linking: #290 fixes the related Netty issue #215 (
ByteBuf.nioBuffer()returning zeros) and touches the sameByteBufProxyread path this ticket is about. Worth keeping the two together — whatever the adaptiveout()ends up doing will also determine itsnioBuffer()behaviour, so #261 and #215 are effectively the same corner of the code.bernardladenthin commentedon Jul 24, 2026
I spent some time trying to reproduce this on Netty 4.2.0 and want to share what I found, because part of the pasted writeup doesn't hold up — and there is a real, fixable issue underneath, just with a different cause.
The stated mechanism appears to be disproven
The writeup says "
ByteBufProxy.PROXY_NETTYfails … allocate() expectsPooledUnsafeDirectByteBuf, getsAdaptivePoolingAllocator$AdaptiveByteBuf." I can't reproduce that, and it looks structurally impossible:ByteBufProxy.allocate()callsnettyAllocator.directBuffer(), andnettyAllocatoris typedPooledByteBufAllocator(andPROXY_NETTYusesPooledByteBufAllocator.DEFAULT). APooledByteBufAllocatornever returns anAdaptiveByteBuf.ByteBufAllocator.DEFAULT→AdaptiveByteBuf),PooledByteBufAllocator.DEFAULTstill returnsPooledUnsafeDirectByteBuf,PROXY_NETTYconstructs fine, and the full test suite passes on 4.2 (918/918), includingput/getround-trips with adaptive user buffers.(Minor: the writeup names the offset field
startIndex; in 4.2.0 it's actuallyadjustment.)The real, reproducible failure
ByteBufProxy.in()callsbuffer.memoryAddress()unconditionally, which throws for any buffer withhasMemoryAddress()==false— i.e. any heap buffer:Netty 4.2's adaptive allocator can hand back heap-backed
AdaptiveByteBuf(e.g.heapBuffer()), so 4.2 apps hit this — but it's not 4.2-specific: a plainPooledHeapByteBuffails identically on 4.1. That likely also explains why-Dio.netty.allocator.type=pooled"fixed" it for you: it steers your buffers back to direct.#291 fixes this by copying address-less buffers into native scratch in
in()(the same approachByteArrayProxyalready uses), keeping the zero-copy fast path for direct buffers.Could you share your actual failure?
Since the "class name mismatch" mechanism doesn't reproduce here, I'd like to make sure I'm not missing a different failure you saw. Do you have the actual stack trace / a minimal repro from your codebase without the property set? In particular it'd help to know whether your buffers were heap or direct, and your JDK + whether
sun.misc.Unsafewas available (there's a separate, unconfirmed path whereallocate()rejects a non-unsafePooledDirectByteBufif unsafe is disabled — that would be a genuine "class name mismatch," but about unsafe, not the adaptive allocator).Separately: the full suite passing on 4.2 suggests the pinned Netty 4.1.118 could likely be bumped after the "further testing" the pom mentions — happy to open that separately if useful.