Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); RATIS-1897. Make TransactionContext available in DataApi.write(..). by szetszwo · Pull Request #930 · apache/ratis · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -239,7 +239,7 @@ CompletableFuture<WriteReplyProto> submitCommit(
uc = files.get(relative).asUnderConstruction();
} catch (FileNotFoundException e) {
return FileStoreCommon.completeExceptionally(
index, "Failed to write to " + relative, e);
index, "Failed to submitCommit to " + relative, e);
}

return uc.submitCommit(offset, size, converter, committer, getId(), index)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,6 @@ static <T> CompletableFuture<T> completeExceptionally(

static <T> CompletableFuture<T> completeExceptionally(
String message, Throwable cause) {
return JavaUtils.completeExceptionally(
new IOException(message).initCause(cause));
return JavaUtils.completeExceptionally(new IOException(message, cause));
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,8 +26,8 @@
import org.apache.ratis.proto.ExamplesProtos.StreamWriteRequestProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestHeaderProto;
import org.apache.ratis.proto.ExamplesProtos.WriteRequestProto;
import org.apache.ratis.proto.RaftProtos;
import org.apache.ratis.proto.RaftProtos.LogEntryProto;
import org.apache.ratis.proto.RaftProtos.StateMachineLogEntryProto;
import org.apache.ratis.protocol.Message;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftGroupId;
Expand DownExpand Up@@ -97,52 +97,65 @@ public TransactionContext startTransaction(RaftClientRequest request) throws IOE
final TransactionContext.Builder b = TransactionContext.newBuilder()
.setStateMachine(this)
.setClientRequest(request);

if (proto.getRequestCase() == FileStoreRequestProto.RequestCase.WRITE) {
final WriteRequestProto write = proto.getWrite();
final FileStoreRequestProto newProto = FileStoreRequestProto.newBuilder()
.setWriteHeader(write.getHeader()).build();
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData());
b.setLogData(newProto.toByteString()).setStateMachineData(write.getData())
.setStateMachineContext(newProto);
} else {
b.setLogData(content);
b.setLogData(content)
.setStateMachineContext(proto);
}
return b.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
try {
proto = FileStoreRequestProto.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
}
public TransactionContext startTransaction(LogEntryProto entry, RaftProtos.RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.setStateMachineContext(getProto(entry))
.build();
}

@Override
public CompletableFuture<Integer> write(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}

final WriteRequestHeaderProto h = proto.getWriteHeader();
final CompletableFuture<Integer> f = files.write(entry.getIndex(),
h.getPath().toStringUtf8(), h.getClose(), h.getSync(), h.getOffset(),
smLog.getStateMachineEntry().getStateMachineData());
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData());
// sync only if closing the file
return h.getClose()? f: null;
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry) {
final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final ByteString data = smLog.getLogData();
final FileStoreRequestProto proto;
static FileStoreRequestProto getProto(TransactionContext context, LogEntryProto entry) {
if (context != null) {
final FileStoreRequestProto proto = (FileStoreRequestProto) context.getStateMachineContext();
if (proto != null) {
return proto;
}
}
return getProto(entry);
}

static FileStoreRequestProto getProto(LogEntryProto entry) {
try {
proto = FileStoreRequestProto.parseFrom(data);
return FileStoreRequestProto.parseFrom(entry.getStateMachineLogEntry().getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(
entry.getIndex(), "Failed to parse data, entry=" + entry, e);
throw new IllegalArgumentException("Failed to parse data, entry=" + entry, e);
}
}

@Override
public CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
final FileStoreRequestProto proto = getProto(context, entry);
if (proto.getRequestCase() != FileStoreRequestProto.RequestCase.WRITEHEADER) {
return null;
}
Expand DownExpand Up@@ -206,20 +219,14 @@ public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
final long index = entry.getIndex();
updateLastAppliedTermIndex(entry.getTerm(), index);

final StateMachineLogEntryProto smLog = entry.getStateMachineLogEntry();
final FileStoreRequestProto request;
try {
request = FileStoreRequestProto.parseFrom(smLog.getLogData());
} catch (InvalidProtocolBufferException e) {
return FileStoreCommon.completeExceptionally(index,
"Failed to parse logData in" + smLog, e);
}
final FileStoreRequestProto request = getProto(trx, entry);

switch(request.getRequestCase()) {
case DELETE:
return delete(index, request.getDelete());
case WRITEHEADER:
return writeCommit(index, request.getWriteHeader(), smLog.getStateMachineEntry().getStateMachineData().size());
return writeCommit(index, request.getWriteHeader(),
entry.getStateMachineLogEntry().getStateMachineEntry().getStateMachineData().size());
case STREAM:
return streamCommit(request.getStream());
case WRITE:
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -121,6 +121,14 @@ <OUTPUT, THROWABLE extends Throwable> OUTPUT runSequentially(
*/
CompletableFuture<Long> appendEntry(LogEntryProto entry);

/**
* Append asynchronously an entry.
* Used by the leader.
*/
default CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return appendEntry(entry);
}

/**
* The same as append(Arrays.asList(entries)).
*
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,15 @@ default CompletableFuture<ByteString> read(LogEntryProto entry) {
throw new UnsupportedOperationException("This method is NOT supported.");
}

/**
* Read asynchronously the state machine data from this state machine.
*
* @return a future for the read task.
*/
default CompletableFuture<ByteString> read(LogEntryProto entry, TransactionContext context) {
return read(entry);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
Expand All@@ -88,6 +97,15 @@ default CompletableFuture<?> write(LogEntryProto entry) {
return CompletableFuture.completedFuture(null);
}

/**
* Write asynchronously the state machine data in the given log entry to this state machine.
*
* @return a future for the write task
*/
default CompletableFuture<?> write(LogEntryProto entry, TransactionContext context) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a @Nullable annotation here to notify user the context will be null when the peer is a follower?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Follower currently creates context in RaftServerImpl.applyLogToStateMachine. We should create a context instead of passing null and then the context can be reused. Let me think about it

return write(entry);
}

/**
* Create asynchronously a {@link DataStream} to stream state machine data.
* The state machine may use the first message (i.e. request.getMessage()) as the header to create the stream.
Expand DownExpand Up@@ -483,14 +501,30 @@ default FollowerEventApi followerEvent() {
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return null if the request should be rejected.
* Otherwise, return a transaction with the content to be written to the log.
* @return a transaction with the content to be written to the log.
* @throws IOException thrown by the state machine while validation
*
* @see TransactionContext.Builder
*/
TransactionContext startTransaction(RaftClientRequest request) throws IOException;

/**
* Start a transaction for the given log entry for non-leaders.
* This method can be invoked in parallel when there are multiple requests.
* The implementation should prepare a {@link StateMachineLogEntryProto},
* and then build a {@link TransactionContext}.
* The implementation should also be light-weighted.
*
* @return a transaction with the content to be written to the log.
*/
default TransactionContext startTransaction(LogEntryProto entry, RaftPeerRole role) {
return TransactionContext.newBuilder()
.setStateMachine(this)
.setLogEntry(entry)
.setServerRole(role)
.build();
}

/**
* This is called before the transaction passed from the StateMachine is appended to the raft log.
* This method is called with the same strict serial order as the transaction order in the raft log.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,6 +135,7 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.JmxRegister;
import org.apache.ratis.util.LifeCycle;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.ProtoUtils;
import org.apache.ratis.util.TimeDuration;
Expand DownExpand Up@@ -222,6 +223,7 @@ public long[] getFollowerNextIndices() {
private final DataStreamMap dataStreamMap;
private final RaftServerConfigKeys.Read.Option readOption;

private final TransactionManager transactionManager = new TransactionManager();
private final RetryCacheImpl retryCache;
private final CommitInfoCache commitInfoCache = new CommitInfoCache();
private final WriteIndexCache writeIndexCache;
Expand DownExpand Up@@ -1784,6 +1786,7 @@ private CompletableFuture<Message> replyPendingRequest(
}

return stateMachineFuture.whenComplete((reply, exception) -> {
transactionManager.remove(logIndex);
final RaftClientReply.Builder b = newReplyBuilder(invocationId, logIndex);
final RaftClientReply r;
if (exception == null) {
Expand All@@ -1801,6 +1804,27 @@ private CompletableFuture<Message> replyPendingRequest(
});
}

TransactionContext getTransactionContext(LogEntryProto entry, Boolean createNew) {
if (!entry.hasStateMachineLogEntry()) {
return null;
}

final Optional<LeaderStateImpl> leader = getRole().getLeaderState();
if (leader.isPresent()) {
final TransactionContext context = leader.get().getTransactionContext(entry.getIndex());
if (context != null) {
return context;
}
}

if (!createNew) {
return transactionManager.get(entry.getIndex());
}
return transactionManager.computeIfAbsent(entry.getIndex(),
// call startTransaction only once
MemoizedSupplier.valueOf(() -> stateMachine.startTransaction(entry, getInfo().getCurrentRole())));
}

CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws RaftLogIOException {
if (!next.hasStateMachineLogEntry()) {
stateMachine.event().notifyTermIndexUpdated(next.getTerm(), next.getIndex());
Expand All@@ -1813,14 +1837,7 @@ CompletableFuture<Message> applyLogToStateMachine(LogEntryProto next) throws Raf
stateMachine.event().notifyConfigurationChanged(next.getTerm(), next.getIndex(), next.getConfigurationEntry());
role.getLeaderState().ifPresent(leader -> leader.checkReady(next));
} else if (next.hasStateMachineLogEntry()) {
// check whether there is a TransactionContext because we are the leader.
TransactionContext trx = role.getLeaderState()
.map(leader -> leader.getTransactionContext(next.getIndex()))
.orElseGet(() -> TransactionContext.newBuilder()
.setServerRole(role.getCurrentRole())
.setStateMachine(stateMachine)
.setLogEntry(next)
.build());
TransactionContext trx = getTransactionContext(next, true);
final ClientInvocationId invocationId = ClientInvocationId.valueOf(next.getStateMachineLogEntry());
writeIndexCache.add(invocationId.getClientId(), ((TransactionContextImpl) trx).getLogIndexFuture());

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -177,11 +177,16 @@ private static RaftLog initRaftLog(RaftGroupMemberId memberId, RaftServerImpl se
if (RaftServerConfigKeys.Log.useMemory(prop)) {
log = new MemoryRaftLog(memberId, getSnapshotIndexFromStateMachine, prop);
} else {
log = new SegmentedRaftLog(memberId, server,
server.getStateMachine(),
server::notifyTruncatedLogEntry,
server::submitUpdateCommitEvent,
storage, getSnapshotIndexFromStateMachine, prop);
log = SegmentedRaftLog.newBuilder()
.setMemberId(memberId)
.setServer(server)
.setNotifyTruncatedLogEntry(server::notifyTruncatedLogEntry)
.setGetTransactionContext(server::getTransactionContext)
.setSubmitUpdateCommitEvent(server::submitUpdateCommitEvent)
.setStorage(storage)
.setSnapshotIndexSupplier(getSnapshotIndexFromStateMachine)
.setProperties(prop)
.build();
}
log.open(log.getSnapshotIndex(), logConsumer);
return log;
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.ratis.server.impl;

import org.apache.ratis.statemachine.TransactionContext;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;

/**
* Managing {@link TransactionContext}.
*/
class TransactionManager {
private final ConcurrentMap<Long, Supplier<TransactionContext>> contexts = new ConcurrentHashMap<>();

TransactionContext get(long index) {
return Optional.ofNullable(contexts.get(index)).map(Supplier::get).orElse(null);
}

TransactionContext computeIfAbsent(long index, Supplier<TransactionContext> constructor) {
return contexts.computeIfAbsent(index, i -> constructor).get();
}

void remove(long index) {
contexts.remove(index);
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ private long appendImpl(long term, TransactionContext operation) throws StateMac
throw new StateMachineException(memberId, new RaftLogIOException(
"Log entry size " + entrySize + " exceeds the max buffer limit of " + maxBufferSize));
}
appendEntry(e).whenComplete((returned, t) -> {
appendEntry(e, operation).whenComplete((returned, t) -> {
if (t != null) {
LOG.error(name + ": Failed to write log entry " + LogProtoUtils.toLogEntryString(e), t);
} else if (returned != nextIndex) {
Expand DownExpand Up@@ -343,10 +343,15 @@ public final CompletableFuture<Long> purge(long suggestedIndex) {

@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry) {
return runner.runSequentially(() -> appendEntryImpl(entry));
return appendEntry(entry, null);
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry);
@Override
public final CompletableFuture<Long> appendEntry(LogEntryProto entry, TransactionContext context) {
return runner.runSequentially(() -> appendEntryImpl(entry, context));
}

protected abstract CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context);

@Override
public final List<CompletableFuture<Long>> append(List<LogEntryProto> entries) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@
import org.apache.ratis.server.raftlog.RaftLogBase;
import org.apache.ratis.server.raftlog.LogEntryHeader;
import org.apache.ratis.server.storage.RaftStorageMetadata;
import org.apache.ratis.statemachine.TransactionContext;
import org.apache.ratis.util.AutoCloseableLock;
import org.apache.ratis.util.Preconditions;

Expand DownExpand Up@@ -165,7 +166,7 @@ public TermIndex getLastEntryTermIndex() {
}

@Override
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry) {
protected CompletableFuture<Long> appendEntryImpl(LogEntryProto entry, TransactionContext context) {
checkLogState();
try(AutoCloseableLock writeLock = writeLock()) {
validateLogEntry(entry);
Expand Down
Loading