org.apache.flink.runtime.state.gemini.engine.rm.GPooledNettyByteBuffer Maven / Gradle / Ivy
/*
* 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.flink.runtime.state.gemini.engine.rm;
import org.apache.flink.runtime.state.gemini.engine.exceptions.GeminiRuntimeException;
import org.apache.flink.runtime.state.gemini.engine.page.bmap.UnsafeHelp;
import org.apache.flink.shaded.guava18.com.google.common.collect.Lists;
import org.apache.flink.shaded.netty4.io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;
import java.nio.ByteBuffer;
import java.util.LinkedList;
/**
* GByteBuffer.
*/
public class GPooledNettyByteBuffer extends AbstractGByteBufferReference {
private static final Logger LOG = LoggerFactory.getLogger(GPooledNettyByteBuffer.class);
private static final int STACK_LIMIT = 15;
private static final short ALLOCATED_THREAD_MASK = (~(0)) << 2;
private static final short SYNC_FREE_MASK = (short) 0x01;
private static final short RETAINED_BY_OTHER_THREAD_MASK = (short) 0x02;
private GPooledNettyByteBufferContext context;
private short flag;
public GPooledNettyByteBuffer(ByteBuf byteBuf, int len, Allocator allocator) {
super(allocator);
if (LOG.isDebugEnabled()) {
context = new GPooledNettyByteBufferContextExtended(byteBuf, len, allocator);
} else {
context = new GPooledNettyByteBufferContext(byteBuf, len, allocator);
}
setAllocatedThread();
retain();
}
@Override
public int capacity() {
return context.len;
}
@Override
public ByteBuffer getByteBuffer() {
return context.byteBuffer;
}
@Override
public void retain() {
if (!sameThreadWithAllocated()) {
setRetainedByOtherThread();
}
if (LOG.isDebugEnabled()) {
context.recordRetainStack(Thread.currentThread().getStackTrace());
}
super.retain();
}
@Override
public void release() {
if (!sameThreadWithAllocated()) {
setRetainedByOtherThread();
}
if (LOG.isDebugEnabled()) {
context.recordReleaseStack(Thread.currentThread().getStackTrace());
}
super.release();
}
@Override
public void doFree() throws GeminiRuntimeException {
context.clean();
}
@Override
public boolean isFreed() {
return context.cleaned();
}
static class GPooledNettyByteBufferContext implements FinalizableCleaner {
private static final Unsafe unsafe;
private static final long freedOffset;
static {
try {
unsafe = UnsafeHelp.getUnsafe();
freedOffset = unsafe.objectFieldOffset
(GPooledNettyByteBufferContext.class.getDeclaredField("freed"));
} catch (Exception ex) {
throw new Error(ex);
}
}
private volatile ByteBuf byteBuf;
private ByteBuffer byteBuffer;
private Allocator allocator;
private final int len;
private volatile boolean freed;
GPooledNettyByteBufferContext(ByteBuf byteBuf, int len, Allocator allocator) {
this.byteBuf = byteBuf;
this.byteBuffer = byteBuf.nioBuffer(0, len);
this.len = len;
this.allocator = allocator;
this.freed = false;
}
@Override
public void clean() {
boolean updated = unsafe.compareAndSwapInt(this, freedOffset, 0, 1);
if (!updated) {
return;
}
if (byteBuf != null) {
if (!byteBuf.release()) {
LOG.error("FATAL BUG!!! LEAK! pls contact to dev. byteBuf ref =" + byteBuf.refCnt());
}
byteBuf = null;
this.allocator.statSize(-len);
}
}
@Override
public boolean cleaned() {
return freed;
}
/**
* Record the call stack when retain. Debug use.
* @param stack
*/
void recordRetainStack(StackTraceElement[] stack) {
}
/**
* Record the call stack when release. Debug use.
* @param stack
*/
void recordReleaseStack(StackTraceElement[] stack) {
}
@Override
public void cleanedByLeakDetector() {
}
}
/**
* This class is a extended version of GPooledNettyByteBufferContext, used for debug.
*/
static class GPooledNettyByteBufferContextExtended extends GPooledNettyByteBufferContext {
private LinkedList retainStack = Lists.newLinkedList();
private LinkedList releaseStack = Lists.newLinkedList();
GPooledNettyByteBufferContextExtended(ByteBuf byteBuf, int len, Allocator allocator) {
super(byteBuf, len, allocator);
}
/**
* Record the call stack when retain. Debug use.
* @param stack
*/
@Override
void recordRetainStack(StackTraceElement[] stack) {
if (retainStack.size() >= STACK_LIMIT) {
retainStack.removeFirst();
}
retainStack.addLast(stack);
}
/**
* Record the call stack when release. Debug use.
* @param stack
*/
@Override
void recordReleaseStack(StackTraceElement[] stack) {
if (releaseStack.size() >= STACK_LIMIT) {
releaseStack.removeFirst();
}
releaseStack.addLast(stack);
}
@Override
public void cleanedByLeakDetector() {
LOG.debug("Printing retain stack trace:");
int i = 0;
for (StackTraceElement[] stack : retainStack) {
LOG.debug("Printing retain stack trace {}:", i);
for (int j = 1; j < stack.length; j++) {
StackTraceElement s = stack[j];
LOG.debug("\tat " + s.getClassName() + "." + s.getMethodName()
+ "(" + s.getFileName() + ":" + s.getLineNumber() + ")");
}
i += 1;
}
LOG.debug("Printing release stack trace:");
i = 0;
for (StackTraceElement[] stack : releaseStack) {
LOG.debug("Printing release stack trace {}:", i);
for (int j = 1; j < stack.length; j++) {
StackTraceElement s = stack[j];
LOG.debug("\tat " + s.getClassName() + "." + s.getMethodName()
+ "(" + s.getFileName() + ":" + s.getLineNumber() + ")");
}
i += 1;
}
LOG.debug("---------------------------------");
}
}
@Override
public GPooledNettyByteBufferContext getCleaner() {
return context;
}
short threadShortID(Thread thread) {
return (short) (thread.getId() << 2);
}
private void setAllocatedThread() {
short id = threadShortID(Thread.currentThread());
short fg = flag;
this.flag = (short) (fg & (~ALLOCATED_THREAD_MASK) | id);
}
boolean sameThreadWithAllocated() {
return (threadShortID(Thread.currentThread()) == getAllocatedThread());
}
private short getAllocatedThread() {
return (short) (flag & ALLOCATED_THREAD_MASK);
}
void setSyncFree() {
short fg = flag;
this.flag = (short) (fg | SYNC_FREE_MASK);
}
@Override
public boolean canSyncFree() {
return (flag & SYNC_FREE_MASK) != 0;
}
private void setRetainedByOtherThread() {
short fg = flag;
this.flag = (short) (fg | RETAINED_BY_OTHER_THREAD_MASK);
}
boolean getRetainedByOtherThread() {
return (flag & RETAINED_BY_OTHER_THREAD_MASK) != 0;
}
@Override
public boolean isPooled() {
return true;
}
@Override
public void setWaitSeqId() {
setRetainedByOtherThread();
}
}