/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.worker; import alluxio.network.protocol.RPCMessage; import alluxio.network.protocol.RPCResponse; import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import javax.annotation.concurrent.NotThreadSafe; /** * The message type used to send data request and response for remote data. */ @NotThreadSafe public final class DataServerMessage { private static final Logger LOG = LoggerFactory.getLogger(DataServerMessage.class); // The size of the prefix of the header: frame length (long), messageType (int) private static final int HEADER_PREFIX_LENGTH = 12; // The request header is: HEADER_PREFIX, blockId (long), offset (long), length (long), // lockId (long), sessionId (long) private static final int REQUEST_HEADER_LENGTH = HEADER_PREFIX_LENGTH + 40; // The response header is: HEADER_PREFIX, blockId (long), offset (long), length (long), // status (short) private static final int RESPONSE_HEADER_LENGTH = HEADER_PREFIX_LENGTH + 26; // The error response header is: HEADER_PREFIX, status (short) private static final int ERROR_RESPONSE_HEADER_LENGTH = HEADER_PREFIX_LENGTH + 2; /** * Creates a default block request message, just allocates 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, RPCMessage.Type.RPC_BLOCK_READ_REQUEST); ret.mHeader = ByteBuffer.allocate(REQUEST_HEADER_LENGTH); return ret; } /** * Creates a block request message for a part of the block by the block id, the offset and the * length. The message is ready to be sent. If {@code len} is -1, it means requesting the data * from offset to the end of the block. * * @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. * @param lockId The lockId of the locked block * @param sessionId The id of requester's session * @return The created block request message */ public static DataServerMessage createBlockRequestMessage(long blockId, long offset, long len, long lockId, long sessionId) { DataServerMessage ret = new DataServerMessage(true, RPCMessage.Type.RPC_BLOCK_READ_REQUEST); ret.mHeader = ByteBuffer.allocate(REQUEST_HEADER_LENGTH); ret.mBlockId = blockId; ret.mOffset = offset; ret.mLength = len; ret.mLockId = lockId; ret.mSessionId = sessionId; ret.generateHeader(); ret.mData = ByteBuffer.allocate(0); ret.mIsMessageReady = true; return ret; } /** * Creates a block response message specified by the block's id. If {@code toSend} 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 * @param data The data of the message * @return The created block response message */ public static DataServerMessage createBlockResponseMessage(boolean toSend, long blockId, ByteBuffer data) { return createBlockResponseMessage(toSend, blockId, 0, -1, data); } /** * Creates a block response message specified by the block's id, the offset and the length. If * {@code toSend} is true, it will prepare the data to be sent, otherwise the message is used * to receive data. If {@code len} 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. * @param data The data of the message * @return The created block response message */ public static DataServerMessage createBlockResponseMessage(boolean toSend, long blockId, long offset, long len, ByteBuffer data) { DataServerMessage ret = new DataServerMessage(toSend, RPCMessage.Type.RPC_BLOCK_READ_RESPONSE); if (toSend) { if (data != null) { ret.mHeader = ByteBuffer.allocate(RESPONSE_HEADER_LENGTH); ret.mBlockId = blockId; ret.mOffset = offset; ret.mLength = len; ret.mStatus = RPCResponse.Status.SUCCESS; ret.mData = data; ret.mIsMessageReady = true; ret.generateHeader(); } else { ret.mBlockId = blockId; ret.mLength = 0; ret.mHeader = ByteBuffer.allocate(RESPONSE_HEADER_LENGTH); ret.mData = ByteBuffer.allocate(0); ret.mIsMessageReady = true; ret.mStatus = RPCResponse.Status.FILE_DNE; LOG.error("The file is not here! blockId:{}", blockId); ret.generateHeader(); } } else { ret.mHeader = ByteBuffer.allocate(RESPONSE_HEADER_LENGTH); ret.mData = null; } return ret; } private final boolean mToSendData; private final RPCMessage.Type mMessageType; private boolean mIsMessageReady; private ByteBuffer mHeader; private long mBlockId; private long mOffset; private long mLength; private RPCResponse.Status mStatus; private long mLockId = -1L; private long mSessionId; 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, RPCMessage.Type msgType) { mToSendData = isToSendData; mMessageType = msgType; mIsMessageReady = false; } /** * Checks if the message is ready. If not ready, it will throw a runtime exception. */ public void checkReady() { Preconditions.checkState(mIsMessageReady, "Message is not ready."); } /** * Closes the message. */ public void close() { } /** * Returns 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(); // The header must match the Netty RPC messages. if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_REQUEST) { mHeader.putLong(REQUEST_HEADER_LENGTH); // frame length } else { // The response message has a payload. mHeader.putLong(RESPONSE_HEADER_LENGTH + mLength); // frame length } mHeader.putInt(mMessageType.getId()); // RPC message type mHeader.putLong(mBlockId); mHeader.putLong(mOffset); mHeader.putLong(mLength); if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_REQUEST) { // The request message has a lockId and a sessionId mHeader.putLong(mLockId); mHeader.putLong(mSessionId); } else if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_RESPONSE) { // The response message has a status. mHeader.putShort(mStatus.getId()); } mHeader.flip(); } /** * Gets 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; } /** * Gets 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; } /** * Gets the id of the block's locker. * * @return The id of the block's locker */ public long getLockId() { return mLockId; } /** * Gets 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; } /** * Gets the sessionId of the worker making the request. Make sure the message is ready before * calling this method. * * @return The session id of the requester */ public long getSessionId() { checkReady(); return mSessionId; } /** * Gets the status of the response. Make sure the message is ready before calling this method. * * @return The {@link alluxio.network.protocol.RPCResponse.Status} of the response */ public RPCResponse.Status getStatus() { checkReady(); return mStatus; } /** * Gets 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 (mToSendData != isSend) { if (mToSendData) { throw new RuntimeException("Try to recv on send message"); } else { throw new 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 */ public int recv(SocketChannel socketChannel) throws IOException { isSend(false); int numRead; if (mHeader.remaining() > 0) { numRead = socketChannel.read(mHeader); if (numRead == -1 && mHeader.position() >= ERROR_RESPONSE_HEADER_LENGTH) { // Stream ended, but the full header is not received. This means an error response was // returned. mHeader.flip(); // frame length mHeader.getLong(); int receivedMessageType = mHeader.getInt(); if (receivedMessageType == RPCMessage.Type.RPC_ERROR_RESPONSE.getId()) { // This message is expected to be an error response with a status. mStatus = RPCResponse.Status.fromShort(mHeader.getShort()); throw new IOException(mStatus.getMessage()); } else { // Unexpected message type. throw new IOException("Received an unexpected message type: " + receivedMessageType); } } else if (mHeader.remaining() == 0) { // The full header for a request message or a response message is received. mHeader.flip(); // frame length mHeader.getLong(); int receivedMessageType = mHeader.getInt(); Preconditions.checkState(mMessageType.getId() == receivedMessageType, "Unexpected message type (" + receivedMessageType + ") received. expected: " + mMessageType.getId()); mBlockId = mHeader.getLong(); mOffset = mHeader.getLong(); mLength = mHeader.getLong(); if (mMessageType.getId() == RPCMessage.Type.RPC_BLOCK_READ_REQUEST.getId()) { // Additional fields for block read request mLockId = mHeader.getLong(); mSessionId = mHeader.getLong(); } // TODO(hy): Make this better to truncate the file. Preconditions.checkState(mLength < Integer.MAX_VALUE, "received length is too large: " + mLength); if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_RESPONSE) { // The response message has a status. mStatus = RPCResponse.Status.fromShort(mHeader.getShort()); if (mStatus == RPCResponse.Status.SUCCESS) { mData = ByteBuffer.allocate((int) mLength); } else { mData = ByteBuffer.allocate(0); } } LOG.info("data {}, blockId:{} offset:{} dataLength:{}", mData, mBlockId, mOffset, mLength); if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_REQUEST) { mIsMessageReady = true; } else if (mMessageType == RPCMessage.Type.RPC_BLOCK_READ_RESPONSE && (mLength <= 0 || mStatus != RPCResponse.Status.SUCCESS)) { // There is no more to read from the socket. mIsMessageReady = true; } } } else { numRead = socketChannel.read(mData); if (mData.remaining() == 0) { mIsMessageReady = true; } } return numRead; } /** * Sends this message to the specified socket channel. Make sure this is a send message. * * @param socketChannel The socket channel to send to */ public void send(SocketChannel socketChannel) throws IOException { Preconditions.checkNotNull(socketChannel, "socketChannel"); isSend(true); socketChannel.write(mHeader); if (mHeader.remaining() == 0) { socketChannel.write(mData); } } /** * Sets the id of the block's locker. * * @param lockId The id of the block's locker */ public void setLockId(long lockId) { mLockId = lockId; } }