I am writing a messaging facade that takes in arbitrary POJOs and sends them across the wire in JSON format, with the following workflow:
- User invokes
MessagingFacade.sendMessage(Object)
- Borrow a ByteBuffer from a pool, we will serailize the message into this buffer
- Convert POJO into JSON by invoking
JsonSerializer.serialize(Object, ByteBuffer)
- Send the encoded message over the wire
Transport.send(ByteBuffer)
, keep a reference to a promise that will be notified when the message has been sent - Attach a callback to the promise to return the ByteBuffer to the pool once it has been written down the wire
- Return from
MessagingFacade.sendMessage(Object)
invocation
This is a relatively trivial use-case for pooling ByteBuffers, given we can invoke clear()
to reset the state when the object is returned to the pool.
Rather than writing my own object pool, I have attempted to utilize those already offered in Apache commons-pool
. However, there seems to be a rather large caveat with GenericObjectPool
and SoftReferenceObjectPool
...
When borrowing / returning objects, these two pools are using hashCode()
and / or equals()
to identify the associated PooledObject<ByteBuffer>
. This has a very real implications on ByteBuffer
, given the equals()
and hashCode()
implementations involve evaluating the contents of the underlying byte
array:
- Cleaning of Object - When the
ByteBuffer
is returned to the pool, it has its state "cleaned". This simply involves callingByteBuffer.clear()
. This does not zero-out all the bytes in the array, which means thatequals()
andhashCode()
give different results when returning theByteBuffer
, versus when it was borrowed. - Speed of Evaluation - Given I'm pooling instances of
ByteBuffer
with a capacity of my maximum message size (1MB), evaluating bothhashCode()
andequals()
has to linearly traverse this very large array
The Apache commons-pool implementations do not seem suitable for any use-case where either (a) the class has expensive equals()
and hashCode()
implementations or (b) hashCode()
does not yield stable results after being cleaned.
The only viable option to make GenericObjectPool
or SoftReferenceObjectPool
work for this use case seems to be to wrap the ByteBuffer
in another class which uses identity equality / hash-code logic.
This is working, but feels a little cumbersome given how vanilla this use-case is. Are there better alternatives to this approach?
One final note; due to the instability of equals()
and hashCode()
one will actually get exceptions from the GenericObjectPool
, because the pool believes you are attempting to return an object that was never borrrowed from the pool:
java.lang.IllegalStateException: Returned object not currently part of this pool
at org.apache.commons.pool2.impl.GenericObjectPool.returnObject(GenericObjectPool.java:537)
...