/** * Copyright 2016 Yahoo Inc. * * Licensed 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. */ /* * 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 com.yahoo.pulsar.common.api; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.buffer.AbstractReferenceCountedByteBuf; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.Recycler; import io.netty.util.Recycler.Handle; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetectorFactory; import io.netty.util.ResourceLeakTracker; /** * ByteBuf that holds 2 buffers. Similar to {@see CompositeByteBuf} but doesn't allocate list to hold them. */ @SuppressWarnings("unchecked") public final class DoubleByteBuf extends AbstractReferenceCountedByteBuf { private ByteBuf b1; private ByteBuf b2; private final Handle recyclerHandle; private static final Recycler<DoubleByteBuf> RECYCLER = new Recycler<DoubleByteBuf>() { @Override protected DoubleByteBuf newObject(Recycler.Handle handle) { return new DoubleByteBuf(handle); } }; private DoubleByteBuf(Handle recyclerHandle) { super(Integer.MAX_VALUE); this.recyclerHandle = recyclerHandle; } public static ByteBuf get(ByteBuf b1, ByteBuf b2) { DoubleByteBuf buf = RECYCLER.get(); buf.setRefCnt(1); // Make sure the buffers are not deallocated as long as we hold them. Also, buffers can get retained/releases // outside of DoubleByteBuf scope buf.b1 = b1.retain(); buf.b2 = b2.retain(); buf.setIndex(0, b1.readableBytes() + b2.readableBytes()); return toLeakAwareBuffer(buf); } public ByteBuf getFirst() { return b1; } public ByteBuf getSecond() { return b2; } @Override public boolean isDirect() { return b1.isDirect() && b2.isDirect(); } @Override public boolean hasArray() { // There's no single array available return false; } @Override public byte[] array() { throw new UnsupportedOperationException(); } @Override public int arrayOffset() { throw new UnsupportedOperationException(); } @Override public boolean hasMemoryAddress() { return false; } @Override public long memoryAddress() { throw new UnsupportedOperationException(); } @Override public int capacity() { return b1.capacity() + b2.capacity(); } @Override public int readableBytes() { return b1.readableBytes() + b2.readableBytes(); } @Override public int writableBytes() { return 0; } @Override public DoubleByteBuf capacity(int newCapacity) { throw new UnsupportedOperationException(); } @Override public ByteBufAllocator alloc() { return PooledByteBufAllocator.DEFAULT; } @Override public ByteOrder order() { return ByteOrder.BIG_ENDIAN; } @Override public byte getByte(int index) { if (index < b1.writerIndex()) { return b1.getByte(index); } else { return b2.getByte(index - b1.writerIndex()); } } @Override protected byte _getByte(int index) { throw new UnsupportedOperationException(); } @Override protected short _getShort(int index) { throw new UnsupportedOperationException(); } @Override protected int _getUnsignedMedium(int index) { throw new UnsupportedOperationException(); } @Override protected int _getInt(int index) { throw new UnsupportedOperationException(); } @Override protected long _getLong(int index) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { return getBytes(index, Unpooled.wrappedBuffer(dst), dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { checkDstIndex(index, length, dstIndex, dst.capacity()); if (length == 0) { return this; } int b1Length = Math.min(length, b1.readableBytes() - index); if (b1Length > 0) { b1.getBytes(b1.readerIndex() + index, dst, dstIndex, b1Length); dstIndex += b1Length; length -= b1Length; index = 0; } else { index -= b1.readableBytes(); } if (length > 0) { int b2Length = Math.min(length, b2.readableBytes() - index); b2.getBytes(b2.readerIndex() + index, dst, dstIndex, b2Length); } return this; } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf getBytes(int index, OutputStream out, int length) throws IOException { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setByte(int index, int value) { throw new UnsupportedOperationException(); } @Override protected void _setByte(int index, int value) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setShort(int index, int value) { throw new UnsupportedOperationException(); } @Override protected void _setShort(int index, int value) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setMedium(int index, int value) { throw new UnsupportedOperationException(); } @Override protected void _setMedium(int index, int value) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setInt(int index, int value) { return (DoubleByteBuf) super.setInt(index, value); } @Override protected void _setInt(int index, int value) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setLong(int index, long value) { throw new UnsupportedOperationException(); } @Override protected void _setLong(int index, long value) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setBytes(int index, ByteBuffer src) { throw new UnsupportedOperationException(); } @Override public DoubleByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { throw new UnsupportedOperationException(); } @Override public int setBytes(int index, InputStream in, int length) throws IOException { throw new UnsupportedOperationException(); } @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { throw new UnsupportedOperationException(); } @Override public ByteBuf copy(int index, int length) { throw new UnsupportedOperationException(); } @Override public int nioBufferCount() { return b1.nioBufferCount() + b2.nioBufferCount(); } @Override public ByteBuffer internalNioBuffer(int index, int length) { throw new UnsupportedOperationException(); } @Override public ByteBuffer nioBuffer(int index, int length) { ByteBuffer dst = ByteBuffer.allocate(length); ByteBuf b = Unpooled.wrappedBuffer(dst); b.writerIndex(0); getBytes(index, b, length); return dst; } @Override public ByteBuffer[] nioBuffers(int index, int length) { return new ByteBuffer[] { nioBuffer(index, length) }; } @Override public DoubleByteBuf discardReadBytes() { throw new UnsupportedOperationException(); } @Override public String toString() { String result = super.toString(); result = result.substring(0, result.length() - 1); return result + ", components=2)"; } @Override public ByteBuffer[] nioBuffers() { return nioBuffers(readerIndex(), readableBytes()); } @Override protected void deallocate() { // Double release of buffer for the initial ref-count and the internal retain() when the DoubleByteBuf was // created b1.release(2); b2.release(2); b1 = b2 = null; RECYCLER.recycle(this, recyclerHandle); } @Override public ByteBuf unwrap() { return null; } private static final Logger log = LoggerFactory.getLogger(DoubleByteBuf.class); private static final ResourceLeakDetector<DoubleByteBuf> leakDetector = ResourceLeakDetectorFactory.instance() .newResourceLeakDetector(DoubleByteBuf.class); private static final Constructor<ByteBuf> simpleLeakAwareByteBufConstructor; private static final Constructor<ByteBuf> advancedLeakAwareByteBufConstructor; static { Constructor<ByteBuf> _simpleLeakAwareByteBufConstructor = null; Constructor<ByteBuf> _advancedLeakAwareByteBufConstructor = null; try { Class<?> simpleLeakAwareByteBufClass = Class.forName("io.netty.buffer.SimpleLeakAwareByteBuf"); _simpleLeakAwareByteBufConstructor = (Constructor<ByteBuf>) simpleLeakAwareByteBufClass .getDeclaredConstructor(ByteBuf.class, ResourceLeakTracker.class); _simpleLeakAwareByteBufConstructor.setAccessible(true); Class<?> advancedLeakAwareByteBufClass = Class.forName("io.netty.buffer.AdvancedLeakAwareByteBuf"); _advancedLeakAwareByteBufConstructor = (Constructor<ByteBuf>) advancedLeakAwareByteBufClass .getDeclaredConstructor(ByteBuf.class, ResourceLeakTracker.class); _advancedLeakAwareByteBufConstructor.setAccessible(true); } catch (Exception e) { log.error("Failed to use reflection to enable leak detection"); } finally { simpleLeakAwareByteBufConstructor = _simpleLeakAwareByteBufConstructor; advancedLeakAwareByteBufConstructor = _advancedLeakAwareByteBufConstructor; } } private static ByteBuf toLeakAwareBuffer(DoubleByteBuf buf) { try { ResourceLeakTracker<DoubleByteBuf> leak; switch (ResourceLeakDetector.getLevel()) { case DISABLED: break; case SIMPLE: leak = leakDetector.track(buf); if (leak != null) { return simpleLeakAwareByteBufConstructor.newInstance(buf, leak); } break; case ADVANCED: case PARANOID: leak = leakDetector.track(buf); if (leak != null) { return advancedLeakAwareByteBufConstructor.newInstance(buf, leak); } break; } return buf; } catch (Throwable t) { // Catch reflection exception throw new RuntimeException(t); } } }