Julio J. Gomez Diaz created PROTON-2994:
-------------------------------------------
Summary: Allow a pooled/reusable codec instead of the per-thread
AmqpCodec ThreadLocal
Key: PROTON-2994
URL: https://issues.apache.org/jira/browse/PROTON-2994
Project: Qpid Proton
Issue Type: Improvement
Components: proton-j
Affects Versions: proton-j-0.35.0
Reporter: Julio J. Gomez Diaz
h2. Description
{{org.apache.qpid.proton.message.impl.MessageImpl}} keeps its
{{EncoderImpl}}/{{DecoderImpl}} in a {{private static final
ThreadLocal<EncoderDecoderPair> tlsCodec}}, created via
{{ThreadLocal.withInitial(...)}}. This was introduced by PROTON-467 to avoid
the expensive {{registerAllTypes()}} on every {{encode}}/{{decode}} call, and
the reasoning still holds — but the ThreadLocal shape has two drawbacks that
matter on JDK 21+ with virtual threads:
# *It allocates per thread and is never reclaimed.* There is no {{remove()}}
anywhere in {{MessageImpl}} (or {{EncoderDecoderPair}}), so once a thread calls
{{encode}}/{{decode}} it holds the pair until the thread dies. On a platform
thread pool that is amortised; on virtual threads each new carrier/runtime
thread that encodes pays the full creation cost, and short-lived threads keep
the pair reachable until they terminate.
# *Measured cost.* On a fresh virtual thread a single {{Message.encode}}
allocates ~16.5 KB ({{gc.alloc.rate.norm}}, JMH), vs ~224 B on a warm platform
thread. Reproducer and numbers available (see below).
h2. Precedent
Apache Fory ships exactly this pattern and, for virtual threads, recommends the
pool over the ThreadLocal:
* {{Fory.builder().buildThreadLocalFory()}} → {{ThreadLocalFory}} (the shape
used here).
* {{Fory.builder().buildThreadSafeFory()}} → {{ThreadPoolFory}}, fixed pool of
{{4 * availableProcessors()}}.
* {{Fory.builder().buildThreadSafeForyPool(int)}} → explicit fixed pool size.
{{ThreadPoolFory}} picks a preferred slot by {{identityHashCode(thread) %
poolSize}} and falls back to any free slot ({{tryBorrowPreferredSlots}}),
releasing on completion — thread-agnostic, bounded memory. Fory's "Virtual
Threads" guide explicitly recommends the pool variant. Kryo has the equivalent
{{com.esotericsoftware.kryo.kryo5.util.Pool<T>}} ({{obtain()}}/{{free()}}).
Relevant prior discussion in this project:
* *PROTON-467* — introduced the ThreadLocal (Fixed).
* *PROTON-1410* — "Move MessageImpl::TLS to an utility class" (Won't Fix). In
that discussion tabish121 already floated the direction _"if the Encoders were
say made to not require being managed as thread locals for instance, e.g. we
made them stateless"_ — which is the same idea from the other end.
h2. Proposal (opt-in, default-preserving)
Keep {{tlsCodec}} as the default. Add an opt-in way to use a pooled/borrowable
pair:
* A static hook on {{MessageImpl}}, e.g.
{{MessageImpl.setCodecProvider(CodecProvider)}} (or a
{{setEncoderDecoderPool(...)}}), where the provider supplies an
{{EncoderDecoderPair}} for the duration of an {{encode}}/{{decode}} call and
returns it afterwards. When unset, behaviour is unchanged ({{tlsCodec}}).
* A bounded pool implementation of that provider (fixed size, borrow/release),
so memory is capped by the pool size rather than by thread count.
This is additive; existing callers see no change.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]