/*
* 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.netty;
import alluxio.RpcUtils;
import alluxio.StorageTierAssoc;
import alluxio.WorkerStorageTierAssoc;
import alluxio.exception.BlockDoesNotExistException;
import alluxio.exception.ExceptionMessage;
import alluxio.exception.InvalidWorkerStateException;
import alluxio.exception.status.AlluxioStatusException;
import alluxio.network.protocol.RPCProtoMessage;
import alluxio.proto.dataserver.Protocol;
import alluxio.util.IdUtils;
import alluxio.util.proto.ProtoMessage;
import alluxio.worker.block.BlockLockManager;
import alluxio.worker.block.BlockWorker;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutorService;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Netty handler that handles short circuit read requests.
*/
@NotThreadSafe
class DataServerShortCircuitReadHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOG =
LoggerFactory.getLogger(DataServerShortCircuitReadHandler.class);
/** Executor service for block opens. */
private final ExecutorService mBlockOpenExecutor;
private final StorageTierAssoc mStorageTierAssoc = new WorkerStorageTierAssoc();
/** The block worker. */
private final BlockWorker mWorker;
/** The lock Id of the block being read. */
private long mLockId;
private long mSessionId;
/**
* Creates an instance of {@link DataServerShortCircuitReadHandler}.
*
* @param blockWorker the block worker
*/
DataServerShortCircuitReadHandler(ExecutorService service, BlockWorker blockWorker) {
mBlockOpenExecutor = service;
mWorker = blockWorker;
mLockId = BlockLockManager.INVALID_LOCK_ID;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (!(msg instanceof RPCProtoMessage)) {
ctx.fireChannelRead(msg);
return;
}
ProtoMessage message = ((RPCProtoMessage) msg).getMessage();
if (message.isLocalBlockOpenRequest()) {
handleBlockOpenRequest(ctx, message.asLocalBlockOpenRequest());
} else if (message.isLocalBlockCloseRequest()) {
handleBlockCloseRequest(ctx, message.asLocalBlockCloseRequest());
} else {
ctx.fireChannelRead(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
// The RPC handlers do not throw exceptions. All the exception seen here is either
// network exception or some runtime exception (e.g. NullPointerException).
LOG.error("Failed to handle RPCs.", throwable);
ctx.close();
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) {
if (mLockId != BlockLockManager.INVALID_LOCK_ID) {
try {
mWorker.unlockBlock(mLockId);
} catch (BlockDoesNotExistException e) {
LOG.warn("Failed to unlock lock {} with error {}.", mLockId, e.getMessage());
}
mWorker.cleanupSession(mSessionId);
}
ctx.fireChannelUnregistered();
}
/**
* Runnable for handling the expensive open block call logic.
*/
final class BlockOpenRequestHandler implements Runnable {
Protocol.LocalBlockOpenRequest mRequest;
ChannelHandlerContext mContext;
private BlockOpenRequestHandler(ChannelHandlerContext ctx, Protocol.LocalBlockOpenRequest req) {
mContext = ctx;
mRequest = req;
}
@Override
public void run() {
RpcUtils.nettyRPCAndLog(LOG, new RpcUtils.NettyRPCCallable<Void>() {
@Override
public Void call() throws Exception {
if (mLockId == BlockLockManager.INVALID_LOCK_ID) {
mSessionId = IdUtils.createSessionId();
// TODO(calvin): Update the locking logic so this can be done better
if (mRequest.getPromote()) {
try {
mWorker.moveBlock(mSessionId, mRequest.getBlockId(), mStorageTierAssoc.getAlias(0));
} catch (Exception e) {
LOG.warn("Failed to promote block {}: {}", mRequest.getBlockId(), e.getMessage());
}
}
mLockId = mWorker.lockBlock(mSessionId, mRequest.getBlockId());
mWorker.accessBlock(mSessionId, mRequest.getBlockId());
} else {
LOG.warn("Lock block {} without releasing previous block lock {}.",
mRequest.getBlockId(), mLockId);
throw new InvalidWorkerStateException(
ExceptionMessage.LOCK_NOT_RELEASED.getMessage(mLockId));
}
Protocol.LocalBlockOpenResponse response = Protocol.LocalBlockOpenResponse.newBuilder()
.setPath(mWorker.readBlock(mSessionId, mRequest.getBlockId(), mLockId))
.build();
mContext.writeAndFlush(new RPCProtoMessage(new ProtoMessage(response)));
return null;
}
@Override
public void exceptionCaught(Throwable e) {
if (mLockId != BlockLockManager.INVALID_LOCK_ID) {
try {
mWorker.unlockBlock(mLockId);
} catch (BlockDoesNotExistException ee) {
LOG.error("Failed to unlock block {}.", mRequest.getBlockId(), e);
}
mLockId = BlockLockManager.INVALID_LOCK_ID;
}
mContext.writeAndFlush(
RPCProtoMessage.createResponse(AlluxioStatusException.fromThrowable(e)));
}
@Override
public String toString() {
return String.format("Open block: %s", mRequest.toString());
}
});
}
}
/**
* Handles {@link Protocol.LocalBlockOpenRequest}. Since the open can be expensive, the work is
* delegated to a threadpool. No exceptions should be thrown.
*
* @param ctx the channel handler context
* @param request the local block open request
*/
private void handleBlockOpenRequest(final ChannelHandlerContext ctx,
final Protocol.LocalBlockOpenRequest request) {
BlockOpenRequestHandler handler = new BlockOpenRequestHandler(ctx, request);
mBlockOpenExecutor.submit(handler);
}
/**
* Handles {@link Protocol.LocalBlockCloseRequest}. No exceptions should be thrown.
*
* @param ctx the channel handler context
* @param request the local block close request
*/
private void handleBlockCloseRequest(final ChannelHandlerContext ctx,
final Protocol.LocalBlockCloseRequest request) {
RpcUtils.nettyRPCAndLog(LOG, new RpcUtils.NettyRPCCallable<Void>() {
@Override
public Void call() throws Exception {
if (mLockId != BlockLockManager.INVALID_LOCK_ID) {
mWorker.unlockBlock(mLockId);
mLockId = BlockLockManager.INVALID_LOCK_ID;
} else {
LOG.warn("Close a closed block {}.", request.getBlockId());
}
ctx.writeAndFlush(RPCProtoMessage.createOkResponse(null));
return null;
}
@Override
public void exceptionCaught(Throwable e) {
ctx.writeAndFlush(RPCProtoMessage.createResponse(AlluxioStatusException.fromThrowable(e)));
mLockId = BlockLockManager.INVALID_LOCK_ID;
}
@Override
public String toString() {
return String.format("Close block: %s", request.toString());
}
});
}
}