use of io.atomix.protocols.raft.session.RaftSession in project atomix by atomix.
the class LeaderRole method onCommand.
@Override
public CompletableFuture<CommandResponse> onCommand(final CommandRequest request) {
raft.checkThread();
logRequest(request);
if (transferring) {
return CompletableFuture.completedFuture(logResponse(CommandResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.ILLEGAL_MEMBER_STATE).build()));
}
// Get the client's server session. If the session doesn't exist, return an unknown session error.
RaftSession session = raft.getSessions().getSession(request.session());
if (session == null) {
return CompletableFuture.completedFuture(logResponse(CommandResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.UNKNOWN_SESSION).build()));
}
final long sequenceNumber = request.sequenceNumber();
// If a command with the given sequence number is already pending, return the existing future to ensure
// duplicate requests aren't committed as duplicate entries in the log.
PendingCommand existingCommand = session.getCommand(sequenceNumber);
if (existingCommand != null) {
if (sequenceNumber == session.nextRequestSequence()) {
session.removeCommand(sequenceNumber);
commitCommand(existingCommand.request(), existingCommand.future());
session.setRequestSequence(sequenceNumber);
drainCommands(session);
}
log.trace("Returning pending result for command sequence {}", sequenceNumber);
return existingCommand.future();
}
final CompletableFuture<CommandResponse> future = new CompletableFuture<>();
// to force it to be resent by the client.
if (sequenceNumber > session.nextRequestSequence()) {
if (session.getCommands().size() < MAX_PENDING_COMMANDS) {
log.trace("Registered sequence command {} > {}", sequenceNumber, session.nextRequestSequence());
session.registerCommand(request.sequenceNumber(), new PendingCommand(request, future));
return future;
} else {
return CompletableFuture.completedFuture(logResponse(CommandResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.COMMAND_FAILURE).withLastSequence(session.getRequestSequence()).build()));
}
}
// return null.
if (sequenceNumber <= session.getCommandSequence()) {
OperationResult result = session.getResult(sequenceNumber);
if (result != null) {
completeOperation(result, CommandResponse.builder(), null, future);
} else {
future.complete(CommandResponse.builder().withStatus(RaftResponse.Status.ERROR).withError(RaftError.Type.PROTOCOL_ERROR).build());
}
} else // Otherwise, commit the command and update the request sequence number.
{
commitCommand(request, future);
session.setRequestSequence(sequenceNumber);
}
return future.thenApply(this::logResponse);
}
use of io.atomix.protocols.raft.session.RaftSession in project atomix by atomix.
the class RaftServiceContext method keepAliveSessions.
/**
* Keeps all sessions alive using the given timestamp.
*
* @param index the index of the timestamp
* @param timestamp the timestamp with which to reset session timeouts
*/
public void keepAliveSessions(long index, long timestamp) {
log.debug("Resetting session timeouts");
this.currentIndex = index;
this.currentTimestamp = Math.max(currentTimestamp, timestamp);
for (RaftSession session : sessions.getSessions()) {
session.setLastUpdated(timestamp);
}
}
use of io.atomix.protocols.raft.session.RaftSession in project atomix by atomix.
the class RaftSessions method expireSession.
/**
* Expires and removes a session from the sessions list.
*
* @param session The session to remove.
*/
void expireSession(RaftSession session) {
final RaftSession singletonSession = sessionManager.removeSession(session.sessionId());
if (singletonSession != null) {
singletonSession.expire();
listeners.forEach(l -> l.onExpire(singletonSession));
}
}
use of io.atomix.protocols.raft.session.RaftSession in project atomix by atomix.
the class RaftServiceManager method applyKeepAlive.
/**
* Applies a session keep alive entry to the state machine.
* <p>
* Keep alive entries are applied to the internal state machine to reset the timeout for a specific session.
* If the session indicated by the KeepAliveEntry is still held in memory, we mark the session as trusted,
* indicating that the client has committed a keep alive within the required timeout. Additionally, we check
* all other sessions for expiration based on the timestamp provided by this KeepAliveEntry. Note that sessions
* are never completely expired via this method. Leaders must explicitly commit an UnregisterEntry to expire
* a session.
* <p>
* When a KeepAliveEntry is committed to the internal state machine, two specific fields provided in the entry
* are used to update server-side session state. The {@code commandSequence} indicates the highest command for
* which the session has received a successful response in the proper sequence. By applying the {@code commandSequence}
* to the server session, we clear command output held in memory up to that point. The {@code eventVersion} indicates
* the index up to which the client has received event messages in sequence for the session. Applying the
* {@code eventVersion} to the server-side session results in events up to that index being removed from memory
* as they were acknowledged by the client. It's essential that both of these fields be applied via entries committed
* to the Raft log to ensure they're applied on all servers in sequential order.
* <p>
* Keep alive entries are retained in the log until the next time the client sends a keep alive entry or until the
* client's session is expired. This ensures for sessions that have long timeouts, keep alive entries cannot be cleaned
* from the log before they're replicated to some servers.
*/
private long[] applyKeepAlive(Indexed<KeepAliveEntry> entry) {
// Store the session/command/event sequence and event index instead of acquiring a reference to the entry.
long[] sessionIds = entry.entry().sessionIds();
long[] commandSequences = entry.entry().commandSequenceNumbers();
long[] eventIndexes = entry.entry().eventIndexes();
// Iterate through session identifiers and keep sessions alive.
List<Long> successfulSessionIds = new ArrayList<>(sessionIds.length);
for (int i = 0; i < sessionIds.length; i++) {
long sessionId = sessionIds[i];
long commandSequence = commandSequences[i];
long eventIndex = eventIndexes[i];
RaftSession session = raft.getSessions().getSession(sessionId);
if (session != null) {
if (session.getService().keepAlive(entry.index(), entry.entry().timestamp(), session, commandSequence, eventIndex)) {
successfulSessionIds.add(sessionId);
}
}
}
// Iterate through services and complete keep-alives, causing sessions to be expired if necessary.
for (RaftServiceContext service : raft.getServices()) {
service.completeKeepAlive(entry.index(), entry.entry().timestamp());
}
return Longs.toArray(successfulSessionIds);
}
use of io.atomix.protocols.raft.session.RaftSession in project atomix by atomix.
the class RaftSessionsTest method createSession.
private RaftSession createSession(long sessionId) {
RaftServiceContext context = mock(RaftServiceContext.class);
when(context.serviceType()).thenReturn(new TestPrimitiveType());
when(context.serviceName()).thenReturn("test");
when(context.serviceId()).thenReturn(PrimitiveId.from(1));
RaftContext server = mock(RaftContext.class);
when(server.getProtocol()).thenReturn(mock(RaftServerProtocol.class));
RaftServiceManager manager = mock(RaftServiceManager.class);
when(manager.executor()).thenReturn(mock(ThreadContext.class));
when(server.getServiceManager()).thenReturn(manager);
return new RaftSession(SessionId.from(sessionId), NodeId.from("1"), "test", new TestPrimitiveType(), ReadConsistency.LINEARIZABLE, 100, 5000, System.currentTimeMillis(), context, server, mock(ThreadContextFactory.class));
}
Aggregations