jwa
jwa

Reputation: 3281

Effectively Pooling Instances of ByteBuffer

I am writing a messaging facade that takes in arbitrary POJOs and sends them across the wire in JSON format, with the following workflow:

  1. User invokes MessagingFacade.sendMessage(Object)
  2. Borrow a ByteBuffer from a pool, we will serailize the message into this buffer
  3. Convert POJO into JSON by invoking JsonSerializer.serialize(Object, ByteBuffer)
  4. 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
  5. Attach a callback to the promise to return the ByteBuffer to the pool once it has been written down the wire
  6. 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:

  1. Cleaning of Object - When the ByteBuffer is returned to the pool, it has its state "cleaned". This simply involves calling ByteBuffer.clear(). This does not zero-out all the bytes in the array, which means that equals() and hashCode() give different results when returning the ByteBuffer, versus when it was borrowed.
  2. Speed of Evaluation - Given I'm pooling instances of ByteBuffer with a capacity of my maximum message size (1MB), evaluating both hashCode() and equals() 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)
 ...

Upvotes: 2

Views: 1326

Answers (1)

Phil Steitz
Phil Steitz

Reputation: 644

The limitation you are referring to was identified in the JIRA issues POOL-283 and POOL-284, which were resolved in version 2.4.1 of Commons Pool. Try upgrading to version 2.4.1.

Upvotes: 2

Related Questions