/*
* 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 tachyon.worker;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import org.apache.log4j.Logger;
import com.mellanox.jxio.Msg;
import tachyon.Constants;
import tachyon.client.TachyonByteBuffer;
import tachyon.conf.WorkerConf;
import tachyon.util.CommonUtils;
/**
* The message type used to send data request and response for remote data.
*/
public class DataServerMessage {
public static final short DATA_SERVER_REQUEST_MESSAGE = 1;
public static final short DATA_SERVER_RESPONSE_MESSAGE = 2;
/**
* Create a default block request message, just allocate the message header, and no attribute is
* set. The message is not ready to be sent.
*
* @return the created block request message
*/
public static DataServerMessage createBlockRequestMessage() {
DataServerMessage ret = new DataServerMessage(false, DATA_SERVER_REQUEST_MESSAGE);
ret.mHeader = ByteBuffer.allocate(HEADER_LENGTH);
return ret;
}
/**
* Create a block request message specified by the block's id, and the message is ready to be
* sent.
*
* @param blockId
* The id of the block
* @return The created block request message
*/
public static DataServerMessage createBlockRequestMessage(long blockId) {
return createBlockRequestMessage(blockId, 0, -1);
}
/**
* Create a block request message specified by the block's id, the offset and the length. The
* message is ready to be sent. If <code>len</code> is -1, it means request the data from offset
* to the block's end.
*
* @param blockId
* The id of the block
* @param offset
* The requested data's offset in the block
* @param len
* The length of the requested data. If it's -1, it means request the data from offset to
* the block's end.
* @return The created block request message
*/
public static DataServerMessage createBlockRequestMessage(long blockId, long offset, long len) {
DataServerMessage ret = new DataServerMessage(true, DATA_SERVER_REQUEST_MESSAGE);
ret.mHeader = ByteBuffer.allocate(HEADER_LENGTH);
ret.mBlockId = blockId;
ret.mOffset = offset;
ret.mLength = len;
ret.generateHeader();
ret.mData = ByteBuffer.allocate(0);
ret.mIsMessageReady = true;
return ret;
}
/**
* Create a block response message specified by the block's id. If <code>toSend</code> is true, it
* will prepare the data to be sent, otherwise the message is used to receive data.
*
* @param toSend
* If true the message is to send the data, otherwise it's used to receive data.
* @param blockId
* The id of the block
* @return The created block response message
*/
public static DataServerMessage createBlockResponseMessage(boolean toSend, long blockId) {
return createBlockResponseMessage(toSend, blockId, 0, -1);
}
/**
* Create a block response message specified by the block's id, the offset and the length. If
* <code>toSend</code> is true, it will prepare the data to be sent, otherwise the message is used
* to receive data. If <code>len</code> is -1, it means response the data from offset to the
* block's end.
*
* @param toSend
* If true the message is to send the data, otherwise it's used to receive data
* @param blockId
* The id of the block
* @param offset
* The responded data's offset in the block
* @param len
* The length of the responded data. If it's -1, it means respond the data from offset to
* the block's end.
* @return The created block response message
*/
public static DataServerMessage createBlockResponseMessage(boolean toSend, long blockId,
long offset, long len) {
DataServerMessage ret = new DataServerMessage(toSend, DATA_SERVER_RESPONSE_MESSAGE);
if (toSend) {
ret.mBlockId = blockId;
try {
if (offset < 0) {
throw new IOException("Offset can not be negative: " + offset);
}
if (len < 0 && len != -1) {
throw new IOException("Length can not be negative except -1: " + len);
}
String filePath = CommonUtils.concat(WorkerConf.get().DATA_FOLDER, blockId);
ret.LOG.info("Try to response remote requst by reading from " + filePath);
RandomAccessFile file = new RandomAccessFile(filePath, "r");
long fileLength = file.length();
String error = null;
if (offset > fileLength) {
error = String.format("Offset(%d) is larger than file length(%d)", offset, fileLength);
}
if (error == null && len != -1 && offset + len > fileLength) {
error =
String.format("Offset(%d) plus length(%d) is larger than file length(%d)", offset,
len, fileLength);
}
if (error != null) {
file.close();
throw new IOException(error);
}
if (len == -1) {
len = fileLength - offset;
}
ret.mHeader = ByteBuffer.allocate(HEADER_LENGTH);
ret.mOffset = offset;
ret.mLength = len;
FileChannel channel = file.getChannel();
ret.mTachyonData = null;
ret.mData = channel.map(FileChannel.MapMode.READ_ONLY, offset, len);
channel.close();
file.close();
ret.mIsMessageReady = true;
ret.generateHeader();
ret.LOG.info("Response remote requst by reading from " + filePath + " preparation done.");
} catch (Exception e) {
// TODO This is a trick for now. The data may have been removed before remote retrieving.
ret.mBlockId = -ret.mBlockId;
ret.mLength = 0;
ret.mHeader = ByteBuffer.allocate(HEADER_LENGTH);
ret.mData = ByteBuffer.allocate(0);
ret.mIsMessageReady = true;
ret.generateHeader();
ret.LOG.error("The file is not here : " + e.getMessage(), e);
}
} else {
ret.mHeader = ByteBuffer.allocate(HEADER_LENGTH);
ret.mData = null;
}
return ret;
}
private final Logger LOG = Logger.getLogger(Constants.LOGGER_TYPE);
private final boolean IS_TO_SEND_DATA;
private final short mMsgType;
private boolean mIsMessageReady;
private ByteBuffer mHeader;
private static final int HEADER_LENGTH = 26;
private long mBlockId;
private long mOffset;
private long mLength;
private int mLockId = -1;
private TachyonByteBuffer mTachyonData = null;
private ByteBuffer mData = null;
/**
* New a DataServerMessage. Notice that it's not ready.
*
* @param isToSendData
* true if this is a send message, otherwise this is a recv message
* @param msgType
* The message type
*/
private DataServerMessage(boolean isToSendData, short msgType) {
IS_TO_SEND_DATA = isToSendData;
mMsgType = msgType;
mIsMessageReady = false;
}
/**
* Check if the message is ready. If not ready, it will throw a runtime exception.
*/
public void checkReady() {
if (!mIsMessageReady) {
CommonUtils.runtimeException("Message is not ready.");
}
}
/**
* Close the message.
*/
public void close() {
if (mMsgType == DATA_SERVER_RESPONSE_MESSAGE) {
try {
if (mTachyonData != null) {
mTachyonData.close();
}
} catch (Exception e) {
LOG.error(e.getMessage());
}
}
}
/**
* Return whether the message finishes sending or not. It will check if the message is a send
* message, so don't call this on a recv message.
*
* @return true if the message finishes sending, false otherwise
*/
public boolean finishSending() {
isSend(true);
return mHeader.remaining() == 0 && mData.remaining() == 0;
}
private void generateHeader() {
mHeader.clear();
mHeader.putShort(mMsgType);
mHeader.putLong(mBlockId);
mHeader.putLong(mOffset);
mHeader.putLong(mLength);
mHeader.flip();
}
/**
* Get the id of the block. Make sure the message is ready before calling this method.
*
* @return The id of the block
*/
public long getBlockId() {
checkReady();
return mBlockId;
}
/**
* Get the length of the message's requested or responded data. Make sure the message is ready
* before calling this method.
*
* @return The length of the message's requested or responded data
*/
public long getLength() {
checkReady();
return mLength;
}
/**
* Get the id of the block's locker.
*
* @return The id of the block's locker
*/
int getLockId() {
return mLockId;
}
/**
* Get the offset of the message's data in the block. Make sure the message is ready before
* calling this method.
*
* @return The offset of the message's data in the block
*/
public long getOffset() {
checkReady();
return mOffset;
}
/**
* Get the read only buffer of the message's data. Make sure the message is ready before calling
* this method.
*
* @return The read only buffer of the message's data
*/
public ByteBuffer getReadOnlyData() {
checkReady();
ByteBuffer ret = mData.asReadOnlyBuffer();
ret.flip();
return ret;
}
/**
* @return true if the message is ready, false otherwise
*/
public boolean isMessageReady() {
return mIsMessageReady;
}
private void isSend(boolean isSend) {
if (IS_TO_SEND_DATA != isSend) {
if (IS_TO_SEND_DATA) {
CommonUtils.runtimeException("Try to recv on send message");
} else {
CommonUtils.runtimeException("Try to send on recv message");
}
}
}
/**
* Use this message to receive from the specified socket channel. Make sure this is a recv
* message and the message type is matched.
*
* @param socketChannel
* The socket channel to receive from
* @return The number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream
* @throws IOException
*/
public int recv(SocketChannel socketChannel) throws IOException {
isSend(false);
int numRead = 0;
if (mHeader.remaining() > 0) {
numRead = socketChannel.read(mHeader);
if (mHeader.remaining() == 0) {
mHeader.flip();
short msgType = mHeader.getShort();
assert (mMsgType == msgType);
mBlockId = mHeader.getLong();
mOffset = mHeader.getLong();
mLength = mHeader.getLong();
// TODO make this better to truncate the file.
assert mLength < Integer.MAX_VALUE;
if (mMsgType == DATA_SERVER_RESPONSE_MESSAGE) {
if (mLength == -1) {
mData = ByteBuffer.allocate(0);
} else {
mData = ByteBuffer.allocate((int) mLength);
}
}
LOG.info(String.format("data" + mData + ", blockId(%d), offset(%d), dataLength(%d)",
mBlockId, mOffset, mLength));
if (mMsgType == DATA_SERVER_REQUEST_MESSAGE || mLength <= 0) {
mIsMessageReady = true;
}
}
} else {
numRead = socketChannel.read(mData);
if (mData.remaining() == 0) {
mIsMessageReady = true;
}
}
return numRead;
}
/**
* Send this message to the specified socket channel. Make sure this is a send message.
*
* @param socketChannel
* The socket channel to send to
* @throws IOException
*/
public void send(SocketChannel socketChannel) throws IOException {
isSend(true);
socketChannel.write(mHeader);
if (mHeader.remaining() == 0) {
socketChannel.write(mData);
}
}
/**
* Set the id of the block's locker.
*
* @param lockId
* The id of the block's locker
*/
void setLockId(int lockId) {
mLockId = lockId;
}
private void powerHeader() {
if (mHeader.remaining() == 0) {
mHeader.flip();
short msgType = mHeader.getShort();
assert (mMsgType == msgType);
mBlockId = mHeader.getLong();
mOffset = mHeader.getLong();
mLength = mHeader.getLong();
if (mMsgType == DATA_SERVER_RESPONSE_MESSAGE) {
if (mLength == -1) {
mData = ByteBuffer.allocate(0);
} else {
mData = ByteBuffer.allocate((int) mLength);
}
}
if (mMsgType == DATA_SERVER_REQUEST_MESSAGE || mLength <= 0) {
mIsMessageReady = true;
}
}
}
public int recv(InputStream input) throws IOException {
isSend(false);
int numRead = input.read(mHeader.array());
if (numRead == -1)
return numRead;
mHeader.position(numRead);
powerHeader();
numRead = input.read(mData.array());
mData.position(numRead);
if (mData.remaining() == 0) {
mIsMessageReady = true;
}
return numRead;
}
public void copy(ByteBuffer buffer) {
isSend(true);
try {
buffer.put(mHeader);
if (mHeader.remaining() == 0) {
int remaining = buffer.remaining();
int lim = Math.min(mData.position() + remaining, mData.capacity());
mData.limit(lim);
buffer.put(mData.slice());
mData.position(lim);
mData.limit(mData.capacity());
}
} catch (Exception e) {
LOG.error(e.getMessage());
}
}
}