/*
* 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.keyvalue;
import alluxio.Constants;
import alluxio.RpcUtils;
import alluxio.RpcUtils.RpcCallableThrowsIOException;
import alluxio.Sessions;
import alluxio.client.keyvalue.ByteBufferKeyValuePartitionReader;
import alluxio.client.keyvalue.Index;
import alluxio.client.keyvalue.PayloadReader;
import alluxio.exception.AlluxioException;
import alluxio.exception.BlockDoesNotExistException;
import alluxio.exception.InvalidWorkerStateException;
import alluxio.thrift.AlluxioTException;
import alluxio.thrift.KeyValueWorkerClientService;
import alluxio.util.io.BufferUtils;
import alluxio.worker.block.BlockWorker;
import alluxio.worker.block.io.BlockReader;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
/**
* RPC service handler on worker side to read a local key-value block.
*/
// TODO(binfan): move logic outside and make this a simple wrapper.
@ThreadSafe
public final class KeyValueWorkerClientServiceHandler implements KeyValueWorkerClientService.Iface {
private static final Logger LOG =
LoggerFactory.getLogger(KeyValueWorkerClientServiceHandler.class);
/** BlockWorker handler for access block info. */
private final BlockWorker mBlockWorker;
/**
* @param blockWorker the {@link BlockWorker}
*/
public KeyValueWorkerClientServiceHandler(BlockWorker blockWorker) {
mBlockWorker = Preconditions.checkNotNull(blockWorker);
}
@Override
public long getServiceVersion() {
return Constants.KEY_VALUE_WORKER_SERVICE_VERSION;
}
/**
* Gets the value for {@code key} in the given block, or null if key is not found.
*
* @param blockId block Id
* @param key key to fetch
* @return value or null if not found
* @throws AlluxioTException if an exception occurs
*/
@Override
public ByteBuffer get(final long blockId, final ByteBuffer key) throws AlluxioTException {
return RpcUtils.call(LOG, new RpcCallableThrowsIOException<ByteBuffer>() {
@Override
public ByteBuffer call() throws AlluxioException, IOException {
ByteBuffer value = getInternal(blockId, key);
if (value == null) {
return ByteBuffer.allocate(0);
}
return copyAsNonDirectBuffer(value);
}
});
}
private ByteBuffer copyAsNonDirectBuffer(ByteBuffer directBuffer) {
// Thrift assumes the ByteBuffer returned has array() method, which is not true if the
// ByteBuffer is direct. We make a non-direct copy of the ByteBuffer to return.
return BufferUtils.cloneByteBuffer(directBuffer);
}
/**
* Internal logic to get value from the given block.
*
* @param blockId Block Id
* @param keyBuffer bytes of key
* @return key found in the key-value block or null if not found
* @throws BlockDoesNotExistException if the worker is not serving this block
*/
private ByteBuffer getInternal(long blockId, ByteBuffer keyBuffer)
throws BlockDoesNotExistException, IOException {
final long sessionId = Sessions.KEYVALUE_SESSION_ID;
final long lockId = mBlockWorker.lockBlock(sessionId, blockId);
try {
return getReader(sessionId, lockId, blockId).get(keyBuffer);
} catch (InvalidWorkerStateException e) {
// We shall never reach here
LOG.error("Reaching invalid state to get a key", e);
} finally {
mBlockWorker.unlockBlock(lockId);
}
return null;
}
private ByteBufferKeyValuePartitionReader getReader(long sessionId, long lockId, long blockId)
throws InvalidWorkerStateException, BlockDoesNotExistException, IOException {
BlockReader blockReader = mBlockWorker.readBlockRemote(sessionId, blockId, lockId);
ByteBuffer fileBuffer = blockReader.read(0, blockReader.getLength());
ByteBufferKeyValuePartitionReader reader = new ByteBufferKeyValuePartitionReader(fileBuffer);
// TODO(binfan): clean fileBuffer which is a direct byte buffer
blockReader.close();
return reader;
}
@Override
public List<ByteBuffer> getNextKeys(final long blockId, final ByteBuffer key, final int numKeys)
throws AlluxioTException {
return RpcUtils.call(LOG, new RpcCallableThrowsIOException<List<ByteBuffer>>() {
@Override
public List<ByteBuffer> call() throws AlluxioException, IOException {
final long sessionId = Sessions.KEYVALUE_SESSION_ID;
final long lockId = mBlockWorker.lockBlock(sessionId, blockId);
try {
ByteBufferKeyValuePartitionReader reader = getReader(sessionId, lockId, blockId);
Index index = reader.getIndex();
PayloadReader payloadReader = reader.getPayloadReader();
List<ByteBuffer> ret = Lists.newArrayListWithExpectedSize(numKeys);
ByteBuffer currentKey = key;
for (int i = 0; i < numKeys; i++) {
ByteBuffer nextKey = index.nextKey(currentKey, payloadReader);
if (nextKey == null) {
break;
}
ret.add(copyAsNonDirectBuffer(nextKey));
currentKey = nextKey;
}
return ret;
} catch (InvalidWorkerStateException e) {
// We shall never reach here
LOG.error("Reaching invalid state to get all keys", e);
} finally {
mBlockWorker.unlockBlock(lockId);
}
return Collections.emptyList();
}
});
}
// TODO(cc): Try to remove the duplicated try-catch logic in other methods like getNextKeys.
@Override
public int getSize(final long blockId) throws AlluxioTException {
return RpcUtils.call(LOG, new RpcCallableThrowsIOException<Integer>() {
@Override
public Integer call() throws AlluxioException, IOException {
final long sessionId = Sessions.KEYVALUE_SESSION_ID;
final long lockId = mBlockWorker.lockBlock(sessionId, blockId);
try {
return getReader(sessionId, lockId, blockId).size();
} catch (InvalidWorkerStateException e) {
// We shall never reach here
LOG.error("Reaching invalid state to get size", e);
} finally {
mBlockWorker.unlockBlock(lockId);
}
return 0;
}
});
}
}