/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.drill.exec.store.parquet;
import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
import java.util.HashMap;
import org.apache.drill.exec.memory.BufferAllocator;
import org.apache.drill.exec.ops.OperatorContext;
import org.apache.parquet.bytes.ByteBufferAllocator;
/**
* {@link ByteBufferAllocator} implementation that uses Drill's {@link BufferAllocator} to allocate and release
* {@link ByteBuffer} objects.<br>
* To properly release an allocated {@link ByteBuf}, this class keeps track of it's corresponding {@link ByteBuffer}
* that was passed to the Parquet library.
*/
public class ParquetDirectByteBufferAllocator implements ByteBufferAllocator {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ParquetDirectByteBufferAllocator.class);
private final BufferAllocator allocator;
private final HashMap<Key, ByteBuf> allocatedBuffers = new HashMap<>();
public ParquetDirectByteBufferAllocator(OperatorContext o){
allocator = o.getAllocator();
}
public ParquetDirectByteBufferAllocator(BufferAllocator allocator) {
this.allocator = allocator;
}
@Override
public ByteBuffer allocate(int sz) {
ByteBuf bb = allocator.buffer(sz);
ByteBuffer b = bb.nioBuffer(0, sz);
final Key key = new Key(b);
allocatedBuffers.put(key, bb);
logger.debug("ParquetDirectByteBufferAllocator: Allocated {} bytes. Allocated ByteBuffer id: {}", sz, key.hash);
return b;
}
@Override
public void release(ByteBuffer b) {
final Key key = new Key(b);
final ByteBuf bb = allocatedBuffers.get(key);
// The ByteBuffer passed in may already have been freed or not allocated by this allocator.
// If it is not found in the allocated buffers, do nothing
if(bb != null) {
logger.debug("ParquetDirectByteBufferAllocator: Freed byte buffer. Allocated ByteBuffer id: {}", key.hash);
bb.release();
allocatedBuffers.remove(key);
}
}
@Override
public boolean isDirect() {
return true;
}
/**
* ByteBuffer wrapper that computes a fixed hashcode.
* <br><br>
* Parquet only handles {@link ByteBuffer} objects, so we need to use them as keys to keep track of their corresponding
* {@link ByteBuf}, but {@link ByteBuffer} is mutable and it can't be used as a {@link HashMap} key as it is.<br>
* This class solves this by providing a fixed hashcode for {@link ByteBuffer} and uses reference equality in case
* of collisions (we don't need to compare the content of {@link ByteBuffer} because the object passed to
* {@link #release(ByteBuffer)} will be the same object returned from a previous {@link #allocate(int)}.
*/
private class Key {
final int hash;
final ByteBuffer buffer;
Key(final ByteBuffer buffer) {
this.buffer = buffer;
// remember, we can't use buffer.hashCode()
this.hash = System.identityHashCode(buffer);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Key)) {
return false;
}
final Key key = (Key) obj;
return hash == key.hash && buffer == key.buffer;
}
}
}