Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Overview

PlantUML
@startuml
interface AutoCloseable {
void close()
}
interface ByteBufferSharing extends AutoCloseable {
ByteBuffer getBuffer()
ByteBuffer expandWriteBufferIfNeeded(int newCapacity)
ByteBuffer expandReadBufferIfNeeded(int newCapacity)
}

class ByteBufferVendor {
ByteBufferVendor(ByteBuffer bufferArg, BufferType bufferType, BufferPool bufferPool)
ByteBufferSharing open()
ByteBufferSharing open(long time, TimeUnit unit)
void destruct()
}

interface ByteBufferVendor.ByteBufferSharingInternal extends ByteBufferSharing {
void releaseBuffer()
}
class ByteBufferVendor.ByteBufferSharingInternalImpl implements ByteBufferVendor.ByteBufferSharingInternal {}

ByteBufferVendor "sharing" *-- ByteBufferVendor.ByteBufferSharingInternal
ByteBufferVendor "lock" *-- ReentrantLock
ByteBufferVendor "isDestructed" *-- AtomicBoolean
ByteBufferVendor "counter" *-- AtomicInteger

ByteBufferVendor.ByteBufferSharingInternalImpl "buffer\n0..1" --> "1" ByteBuffer
ByteBufferVendor.ByteBufferSharingInternalImpl "bufferPool\n*" --> "1" BufferPool
ByteBufferVendor.ByteBufferSharingInternalImpl "bufferType\n*" --> "1" BufferType

ByteBufferVendor .. ByteBuffer : derived\nassociation
@enduml

...

In the class diagram at the top of this page you'll see a dotted association between ByteBufferVendor and ByteBuffer labeled "derived association". That's meant to convey the fact that the whole point of the vendor is that it mediates access to a ByteBuffer. In UML terms, there is a derived association between the two, even though you won't see an explicit (direct) object reference in the code.

Following the Rules

A "resource" object returned from a ByteBufferSharing open() must be assigned in a try-with-resources variable declaration.

...

It's ok to pass a resource reference to a method so long as you are sure that method (and methods it calls) do not allow the reference value to escape. But it is, in general, not ok to return a resource reference from a method (caveats in the the Writing Your Own Resource-Returning Methods section below). An escape happens when the reference value:

If you are careful to avoid the first two issues above, then it's ok to pass a resource reference to another method. The resource is available for the lifetime of the method call. And you can be sure that the reference value has not leaked.

Ownership and Reference Counting

A ByteBufferSharing object is typically a field of some object that "owns" the underlying (logical) ByteBuffer. When the owning object is done with that (logical) ByteBuffer forever, it calls destruct() on the ByteBufferSharing. This is necessary because the ByteBuffer must be explicitly returned to a pool.

...

Thread C: try-with-resources block ends and reference count is 0, NOW the ByteBuffer can be returned to its pool

Writing Your Own Resource-Returning Methods

The ByteBufferSharing open() methods are special. They return a ByteBufferSharing object that constitutes a capability object—if you have the object, you can always perform the operation (in this case the operation is the various methods on the ByteBufferSharing interface).

In practice, you may want to write your own method that returns a ByteBufferSharing. That's fine so long as callers of your new method follow the same rules they'd follow for open() (see the the Following the Rules section above).

But you have to be careful when implementing your method. You'd like to ensure that callers of your method don't risk losing the resource between your method returning, and the resource reference value being assigned in the caller's try-with-resources.

...

Code Block
languagejava
firstline1
titleCalling Code
try (final ByteBufferSharing sharing = foo()) {

  // access ByteBuffer through sharing reference here

}

Implementer's Notes

When you first encounter ByteBufferVendor.ByteBufferSharingInternalImpl you may wonder why we chose to have both a counter and an isDestructed flag. Couldn't we have just allowed a count of zero to trigger destruction?

The reason we needed both is apparent if you look at the second example in the the Ownership and Reference Counting section. We designed the framework so that the ultimate owner of the resource, the object that has a field of type ByteBufferSharing, can "go away" (and call destruct()) while the byte buffer is still in use by other threads. When that happens we set the isDestructed flag and disallow further open() calls. But, critically, we allow an in-flight try-with-resources block to continue its work. We avoid returning the buffer to the pool until the resource owner has destruct()ed the resource and all try-with-resources blocks have finished.