package org.corfudb.runtime.clients;
import com.codahale.metrics.Timer;
import com.google.common.collect.Range;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import org.corfudb.protocols.logprotocol.LogEntry;
import org.corfudb.protocols.wireprotocol.*;
import org.corfudb.runtime.CorfuRuntime;
import org.corfudb.runtime.exceptions.*;
import org.corfudb.util.serializer.Serializers;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* A client to a LogUnit.
* <p>
* This class provides access to operations on a remote log unit.
* Created by mwei on 12/10/15.
*/
public class LogUnitClient implements IClient {
@Setter
@Getter
IClientRouter router;
public String getHost() { return router.getHost(); }
public Integer getPort() { return router.getPort(); }
/** The handler and handlers which implement this client. */
@Getter
public ClientMsgHandler msgHandler = new ClientMsgHandler(this)
.generateHandlers(MethodHandles.lookup(), this);
/** Handle an WRITE_OK message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @return True, since this indicates success.
*/
@ClientHandler(type=CorfuMsgType.WRITE_OK)
private static Object handleOK(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r) {
return true;
}
/** Handle an ERROR_TRIMMED message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws Exception
*/
@ClientHandler(type=CorfuMsgType.ERROR_TRIMMED)
private static Object handleTrimmed(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new Exception("Trimmed");
}
/** Handle an ERROR_OVERWRITE message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws OverwriteException
*/
@ClientHandler(type=CorfuMsgType.ERROR_OVERWRITE)
private static Object handleOverwrite(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new OverwriteException();
}
/** Handle an ERROR_DATA_OUTRANKED message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws OverwriteException
*/
@ClientHandler(type=CorfuMsgType.ERROR_DATA_OUTRANKED)
private static Object handleDataOutranked(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new DataOutrankedException();
}
@ClientHandler(type=CorfuMsgType.ERROR_VALUE_ADOPTED)
private static Object handleValueAdoptedResponse(CorfuPayloadMsg<ReadResponse> msg,
ChannelHandlerContext ctx, IClientRouter r) {
throw new ValueAdoptedException(msg.getPayload());
}
/** Handle an ERROR_REPLEX_OVERWRITE message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws OverwriteException
*/
@ClientHandler(type=CorfuMsgType.ERROR_REPLEX_OVERWRITE)
private static Object handleReplexOverwrite(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new ReplexOverwriteException();
}
/** Handle an ERROR_OOS message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws OutOfSpaceException
*/
@ClientHandler(type=CorfuMsgType.ERROR_OOS)
private static Object handleOOS(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new OutOfSpaceException();
}
/** Handle an ERROR_RANK message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws Exception
*/
@ClientHandler(type=CorfuMsgType.ERROR_RANK)
private static Object handleOutranked(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new Exception("rank");
}
/** Handle an ERROR_NOENTRY message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
* @throws Exception
*/
@ClientHandler(type=CorfuMsgType.ERROR_NOENTRY)
private static Object handleNoEntry(CorfuMsg msg, ChannelHandlerContext ctx, IClientRouter r)
throws Exception
{
throw new Exception("Tried to write commit on a non-existent entry");
}
/** Handle a READ_RESPONSE message.
*
* @param msg Incoming Message
* @param ctx Context
* @param r Router
*/
@ClientHandler(type=CorfuMsgType.READ_RESPONSE)
private static Object handleReadResponse(CorfuPayloadMsg<ReadResponse> msg,
ChannelHandlerContext ctx, IClientRouter r) {
return msg.getPayload();
}
/**
* Handle a ERROR_DATA_CORRUPTION message
* @param msg Incoming Message
* @param ctx Context
* @param r Router
*/
@ClientHandler(type=CorfuMsgType.ERROR_DATA_CORRUPTION)
private static Object handleReadDataCorruption(CorfuPayloadMsg<ReadResponse> msg,
ChannelHandlerContext ctx, IClientRouter r)
{
throw new DataCorruptionException();
}
/**
* Handle a TAIL_RESPONSE message
* @param msg Incoming Message
* @param ctx Context
* @param r Router
*/
@ClientHandler(type=CorfuMsgType.TAIL_RESPONSE)
private static Object handleTailResponse(CorfuPayloadMsg<Long> msg,
ChannelHandlerContext ctx, IClientRouter r) {
return msg.getPayload();
}
/**
* Asynchronously write to the logging unit.
*
* @param address The address to write to.
* @param streams The streams, if any, that this write belongs to.
* @param rank The rank of this write (used for quorum
* replication).
* @param writeObject The object, pre-serialization, to write.
* @param backpointerMap The map of backpointers to write.
* @return A CompletableFuture which will complete with the WriteResult once the
* write completes.
*/
public CompletableFuture<Boolean> write(long address, Set<UUID> streams, IMetadata.DataRank rank,
Object writeObject, Map<UUID, Long> backpointerMap) {
Timer.Context context = getTimerContext("writeObject");
ByteBuf payload = Unpooled.buffer();
Serializers.CORFU.serialize(writeObject, payload);
WriteRequest wr = new WriteRequest(WriteMode.NORMAL, null, payload);
wr.setRank(rank);
wr.setBackpointerMap(backpointerMap);
wr.setGlobalAddress(address);
CompletableFuture<Boolean> cf = router.sendMessageAndGetCompletable(CorfuMsgType.WRITE.payloadMsg(wr));
return cf.thenApply(x -> { context.stop(); return x; });
}
/**
* Asynchronously write an empty payload to the logging unit with ranked address space.
* Used from the quorum replication when filling holes or during the first phase of the recovery write.
*
* @param address The address to write to.
* @param type The data type
* @param streams The streams, if any, that this write belongs to.
* @param rank The rank of this write]
*/
public CompletableFuture<Boolean> writeEmptyData(long address, DataType type, Set<UUID> streams, IMetadata.DataRank rank) {
Timer.Context context = getTimerContext("writeObject");
LogEntry entry = new LogEntry(LogEntry.LogEntryType.NOP);
ByteBuf payload = Unpooled.buffer();
Serializers.CORFU.serialize(entry, payload);
WriteRequest wr = new WriteRequest(WriteMode.NORMAL, type, null, payload);
wr.setRank(rank);
wr.setGlobalAddress(address);
CompletableFuture<Boolean> cf = router.sendMessageAndGetCompletable(CorfuMsgType.WRITE.payloadMsg(wr));
return cf.thenApply(x -> { context.stop(); return x; });
}
/**
* Asynchronously write to the logging unit.
* @param payload The log data to write to the logging unit.
* @return A CompletableFuture which will complete with the WriteResult once the
* write completes.
*/
public CompletableFuture<Boolean> write(ILogData payload) {
return router.sendMessageAndGetCompletable(CorfuMsgType.WRITE.payloadMsg(new WriteRequest(payload)));
}
/**
* Asynchronously read from the logging unit.
*
* @param address The address to read from.
* @return A CompletableFuture which will complete with a ReadResult once the read
* completes.
*/
public CompletableFuture<ReadResponse> read(long address) {
Timer.Context context = getTimerContext("read");
CompletableFuture<ReadResponse> cf = router.sendMessageAndGetCompletable(
CorfuMsgType.READ_REQUEST.payloadMsg(new ReadRequest(address)));
return cf.thenApply(x -> { context.stop(); return x; });
}
public CompletableFuture<ReadResponse> read(UUID stream, Range<Long> offsetRange) {
Timer.Context context = getTimerContext("readRange");
CompletableFuture<ReadResponse> cf = router.sendMessageAndGetCompletable(
CorfuMsgType.READ_REQUEST.payloadMsg(new ReadRequest(offsetRange, stream)));
return cf.thenApply(x -> { context.stop(); return x; });
}
/**
* Get the global tail maximum address the log unit has written.
* @return A CompletableFuture which will complete with the globalTail once
* received.
*/
public CompletableFuture<Long> getTail() {
return router.sendMessageAndGetCompletable(CorfuMsgType.TAIL_REQUEST.msg());
}
/**
* Send a hint to the logging unit that a stream can be trimmed.
*
* @param stream The stream to trim.
* @param prefix The prefix of the stream, as a global physical offset, to trim.
*/
public void trim(UUID stream, long prefix) {
router.sendMessage(CorfuMsgType.TRIM.payloadMsg(new TrimRequest(stream, prefix)));
}
/**
* Fill a hole at a given address.
*
* @param address The address to fill a hole at.
*/
public CompletableFuture<Boolean> fillHole(long address) {
Timer.Context context = getTimerContext("fillHole");
CompletableFuture<Boolean> cf = router.sendMessageAndGetCompletable(
CorfuMsgType.FILL_HOLE.payloadMsg(new FillHoleRequest(null, address)));
return cf.thenApply(x -> { context.stop(); return x; });
}
public CompletableFuture<Boolean> fillHole(UUID streamID, long address) {
Timer.Context context = getTimerContext("fillHole");
CompletableFuture<Boolean> cf = router.sendMessageAndGetCompletable(
CorfuMsgType.FILL_HOLE.payloadMsg(new FillHoleRequest(streamID, address)));
return cf.thenApply(x -> { context.stop(); return x; });
}
/**
* Force the garbage collector to begin garbage collection.
*/
public void forceGC() {
router.sendMessage(CorfuMsgType.FORCE_GC.msg());
}
/**
* Force the compactor to recalculate the contiguous tail.
*/
public void forceCompact() {
router.sendMessage(CorfuMsgType.FORCE_COMPACT.msg());
}
/**
* Change the default garbage collection interval.
*
* @param millis The new garbage collection interval, in milliseconds.
*/
public void setGCInterval(long millis) {
router.sendMessage(CorfuMsgType.GC_INTERVAL.payloadMsg(millis));
}
private Timer.Context getTimerContext(String opName) {
Timer t = CorfuRuntime.getMetrics().timer(
CorfuRuntime.getMpLUC() +
getHost() + ":" + getPort().toString() + "-" + opName);
return t.time();
}
}