/**
* $Id: MapedFile.java 1831 2013-05-16 01:39:51Z shijia.wxr $
*/
package com.alibaba.rocketmq.store;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.rocketmq.common.MixAll;
import com.alibaba.rocketmq.common.UtilALl;
/**
* Pagecache�ļ����ʷ�װ
*
* @author vintage.wang@gmail.com shijia.wxr@taobao.com
*/
public class MapedFile extends ReferenceResource {
private static final Logger log = LoggerFactory.getLogger(MixAll.StoreLoggerName);
public static final int OS_PAGE_SIZE = 1024 * 4;
// ��ǰJVM��ӳ��������ڴ��ܴ�С
private static final AtomicLong TotalMapedVitualMemory = new AtomicLong(0);
// ��ǰJVM��mmap�������
private static final AtomicInteger TotalMapedFiles = new AtomicInteger(0);
// ӳ����ļ���
private final String fileName;
// ӳ�����ʼƫ����
private final long fileFromOffset;
// ӳ����ļ���С������
private final int fileSize;
// ӳ����ļ�
private final File file;
// ӳ���FileChannel����
private final FileChannel fileChannel;
// ӳ����ڴ����position��Զ����
private final MappedByteBuffer mappedByteBuffer;
// ��ǰд��ʲôλ��
private final AtomicInteger wrotePostion = new AtomicInteger(0);
// Flush��ʲôλ��
private final AtomicInteger committedPosition = new AtomicInteger(0);
// ���һ����Ϣ�洢ʱ��
private volatile long storeTimestamp = 0;
private boolean firstCreateInQueue = false;
public MapedFile(final String fileName, final int fileSize) throws IOException {
this.fileName = fileName;
this.fileSize = fileSize;
this.file = new File(fileName);
this.fileFromOffset = Long.parseLong(this.file.getName());
boolean ok = false;
ensureDirOK(this.file.getParent());
try {
this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
TotalMapedVitualMemory.addAndGet(fileSize);
TotalMapedFiles.incrementAndGet();
ok = true;
}
catch (FileNotFoundException e) {
log.error("create file channel " + this.fileName + " Failed. ", e);
throw e;
}
catch (IOException e) {
log.error("map file " + this.fileName + " Failed. ", e);
throw e;
}
finally {
if (!ok && this.fileChannel != null) {
this.fileChannel.close();
}
}
}
public static void ensureDirOK(final String dirName) {
if (dirName != null) {
File f = new File(dirName);
if (!f.exists()) {
boolean result = f.mkdirs();
log.info(dirName + " mkdir " + (result ? "OK" : "Failed"));
}
}
}
public long getLastModifiedTimestamp() {
return this.file.lastModified();
}
public String getFileName() {
return fileName;
}
/**
* �ļ���ʼƫ����
*/
public long getFileFromOffset() {
return this.fileFromOffset;
}
/**
* ��ȡ�ļ���С
*/
public int getFileSize() {
return fileSize;
}
public FileChannel getFileChannel() {
return fileChannel;
}
public boolean isFull() {
return this.fileSize == this.wrotePostion.get();
}
/**
* ��MapedBuffer����Ϣ<br>
*
* @param msg
* Ҫ�ӵ���Ϣ
* @param cb
* ��������Ϣ�������л��������������MapedFile Offset�����Խ��ж�̬���л�
* @return �Ƿ�ɹ���д���������
*/
public AppendMessageResult appendMessage(final Object msg, final AppendMessageCallback cb) {
assert msg != null;
assert cb != null;
int currentPos = this.wrotePostion.get();
// ��ʾ�п���ռ�
if (currentPos < this.fileSize) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(currentPos);
AppendMessageResult result =
cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, msg);
this.wrotePostion.addAndGet(result.getWroteBytes());
this.storeTimestamp = result.getStoreTimestamp();
return result;
}
// �ϲ�Ӧ��Ӧ�ñ�֤�����ߵ�����
log.error("MapedFile.appendMessage return null, wrotePostion: " + currentPos + " fileSize: "
+ this.fileSize);
return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
}
/**
* ��洢�������ݣ�һ����SLAVE�洢�ṹ��ʹ��
*
* @return ����д���˶�������
*/
public boolean appendMessage(final byte[] data) {
int currentPos = this.wrotePostion.get();
// ��ʾ�п���ռ�
if ((currentPos + data.length) <= this.fileSize) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(currentPos);
byteBuffer.put(data);
this.wrotePostion.addAndGet(data.length);
return true;
}
return false;
}
private boolean isAbleToFlush(final int flushLeastPages) {
int flush = this.committedPosition.get();
int write = this.wrotePostion.get();
// �����ǰ�ļ��Ѿ�д����Ӧ������ˢ��
if (this.isFull()) {
return true;
}
// ֻ��δˢ����������ָ��page��Ŀ��ˢ��
if (flushLeastPages > 0) {
return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
}
return write > flush;
}
/**
* ��Ϣˢ��
*
* @param flushLeastPages
* ����ˢ����page
* @return
*/
public int commit(final int flushLeastPages) {
if (this.isAbleToFlush(flushLeastPages)) {
if (this.hold()) {
int value = this.wrotePostion.get();
this.mappedByteBuffer.force();
this.committedPosition.set(value);
this.release();
}
else {
log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get());
this.committedPosition.set(this.wrotePostion.get());
}
}
return this.getCommittedPosition();
}
public SelectMapedBufferResult selectMapedBuffer(int pos, int size) {
// ����Ϣ
if ((pos + size) <= this.wrotePostion.get()) {
// ��MapedBuffer��
if (this.hold()) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(pos);
ByteBuffer byteBufferNew = byteBuffer.slice();
byteBufferNew.limit(size);
return new SelectMapedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
}
else {
log.warn("matched, but hold failed, request pos: " + pos + ", fileFromOffset: "
+ this.fileFromOffset);
}
}
// ��������Ƿ�
else {
log.warn("selectMapedBuffer request pos invalid, request pos: " + pos + ", size: " + size
+ ", fileFromOffset: " + this.fileFromOffset);
}
// �Ƿ���������mmap��Դ�Ѿ����ͷ�
return null;
}
/**
* ��������
*/
public SelectMapedBufferResult selectMapedBuffer(int pos) {
if (pos < this.wrotePostion.get() && pos >= 0) {
if (this.hold()) {
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
byteBuffer.position(pos);
int size = this.wrotePostion.get() - pos;
ByteBuffer byteBufferNew = byteBuffer.slice();
byteBufferNew.limit(size);
return new SelectMapedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
}
}
// �Ƿ���������mmap��Դ�Ѿ����ͷ�
return null;
}
public static void clean(final ByteBuffer buffer) {
if (buffer == null || !buffer.isDirect() || buffer.capacity() == 0)
return;
invoke(invoke(viewed(buffer), "cleaner"), "clean");
}
private static Object invoke(final Object target, final String methodName, final Class<?>... args) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Method method = method(target, methodName, args);
method.setAccessible(true);
return method.invoke(target);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
});
}
private static Method method(Object target, String methodName, Class<?>[] args) throws NoSuchMethodException {
try {
return target.getClass().getMethod(methodName, args);
}
catch (NoSuchMethodException e) {
return target.getClass().getDeclaredMethod(methodName, args);
}
}
private static ByteBuffer viewed(ByteBuffer buffer) {
ByteBuffer viewedBuffer = (ByteBuffer) invoke(buffer, "viewedBuffer");
if (viewedBuffer == null)
return buffer;
else
return viewed(viewedBuffer);
}
@Override
public boolean cleanup(final long currentRef) {
// ���û�б�shutdown������unmap�ļ��������crash
if (this.isAvailable()) {
log.error("this file[REF:" + currentRef + "] " + this.fileName + " have not shutdown, stop unmaping.");
return false;
}
// ����Ѿ�cleanup���ٴβ���������crash
if (this.isCleanupOver()) {
log.error("this file[REF:" + currentRef + "] " + this.fileName + " have cleanup, do not do it again.");
// ���뷵��true
return true;
}
clean(this.mappedByteBuffer);
TotalMapedVitualMemory.addAndGet(this.fileSize * (-1));
TotalMapedFiles.decrementAndGet();
log.info("unmap file[REF:" + currentRef + "] " + this.fileName + " OK");
return true;
}
/**
* ������Դ��destroy�����shutdown���̱߳�����ͬһ��
*
* @return �Ƿ�destory�ɹ����ϲ������Ҫ��ʧ���������ʧ�ܺ�������
*/
public boolean destroy(final long intervalForcibly) {
this.shutdown(intervalForcibly);
if (this.isCleanupOver()) {
try {
this.fileChannel.close();
log.info("close file channel " + this.fileName + " OK");
long beginTime = System.currentTimeMillis();
boolean result = this.file.delete();
log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName
+ (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePostion() + " M:"
+ this.getCommittedPosition() + ", " + UtilALl.computeEclipseTimeMilliseconds(beginTime));
}
catch (Exception e) {
log.warn("close file channel " + this.fileName + " Failed. ", e);
}
return true;
}
else {
log.warn("destroy maped file[REF:" + this.getRefCount() + "] " + this.fileName
+ " Failed. cleanupOver: " + this.cleanupOver);
}
return false;
}
public int getWrotePostion() {
return wrotePostion.get();
}
public int getCommittedPosition() {
return committedPosition.get();
}
public void setWrotePostion(int pos) {
this.wrotePostion.set(pos);
}
public void setCommittedPosition(int pos) {
this.committedPosition.set(pos);
}
public static int getTotalmapedfiles() {
return TotalMapedFiles.get();
}
public static long getTotalMapedVitualMemory() {
return TotalMapedVitualMemory.get();
}
public MappedByteBuffer getMappedByteBuffer() {
return mappedByteBuffer;
}
/**
* ��������������ʱ���ã�����ȫ��ֻ������ʱ��reload��������ʱ����
*/
public ByteBuffer sliceByteBuffer() {
return this.mappedByteBuffer.slice();
}
public long getStoreTimestamp() {
return storeTimestamp;
}
public boolean isFirstCreateInQueue() {
return firstCreateInQueue;
}
public void setFirstCreateInQueue(boolean firstCreateInQueue) {
this.firstCreateInQueue = firstCreateInQueue;
}
}