io.atomix.copycat.server.state.ServerStateMachine Maven / Gradle / Ivy
/*
* Copyright 2015 the original author or authors.
*
* Licensed 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 io.atomix.copycat.server.state;
import io.atomix.catalyst.util.Assert;
import io.atomix.catalyst.util.concurrent.ComposableFuture;
import io.atomix.catalyst.util.concurrent.Futures;
import io.atomix.catalyst.util.concurrent.ThreadContext;
import io.atomix.copycat.client.Command;
import io.atomix.copycat.client.error.InternalException;
import io.atomix.copycat.client.error.UnknownSessionException;
import io.atomix.copycat.server.StateMachine;
import io.atomix.copycat.server.storage.entry.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
/**
* Raft server state machine.
*
* @author this.lastApplied) {
this.lastApplied = lastApplied;
for (ServerSession session : executor.context().sessions().sessions.values()) {
session.setVersion(lastApplied);
}
}
}
/**
* Returns the current thread context.
*
* @return The current thread context.
*/
private ThreadContext getContext() {
return ThreadContext.currentContextOrThrow();
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @return The result.
*/
CompletableFuture> apply(Entry entry) {
return apply(entry, false);
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @param expectResult Indicates whether this call expects a result.
* @return The result.
*/
CompletableFuture> apply(Entry entry, boolean expectResult) {
try {
if (entry instanceof CommandEntry) {
return apply((CommandEntry) entry, expectResult);
} else if (entry instanceof QueryEntry) {
return apply((QueryEntry) entry);
} else if (entry instanceof RegisterEntry) {
return apply((RegisterEntry) entry);
} else if (entry instanceof KeepAliveEntry) {
return apply((KeepAliveEntry) entry);
} else if (entry instanceof UnregisterEntry) {
return apply((UnregisterEntry) entry);
} else if (entry instanceof NoOpEntry) {
return apply((NoOpEntry) entry);
} else if (entry instanceof ConnectEntry) {
return apply((ConnectEntry) entry);
} else if (entry instanceof ConfigurationEntry) {
return apply((ConfigurationEntry) entry);
}
return Futures.exceptionalFuture(new InternalException("unknown state machine operation"));
} finally {
setLastApplied(entry.getIndex());
}
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @return The result.
*/
private CompletableFuture apply(ConfigurationEntry entry) {
long previousConfiguration = configuration;
configuration = entry.getIndex();
if (previousConfiguration > 0) {
cleaner.clean(previousConfiguration);
}
return CompletableFuture.completedFuture(null);
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @return The result.
*/
private CompletableFuture apply(ConnectEntry entry) {
cleaner.clean(entry.getIndex());
return CompletableFuture.completedFuture(null);
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @return The result.
*/
private CompletableFuture apply(RegisterEntry entry) {
ServerSession session = new ServerSession(entry.getIndex(), executor.context(), entry.getTimeout());
executor.context().sessions().registerSession(session).setTimestamp(entry.getTimestamp());
// Allow the executor to execute any scheduled events.
executor.tick(entry.getTimestamp());
// Expire any remaining expired sessions.
suspectSessions(entry.getTimestamp());
ThreadContext context = getContext();
long index = entry.getIndex();
// Set last applied only after the operation has been submitted to the state machine executor.
CompletableFuture future = new ComposableFuture<>();
executor.executor().execute(() -> {
stateMachine.register(session);
context.execute(() -> future.complete(index));
});
return future;
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
*/
private CompletableFuture apply(KeepAliveEntry entry) {
ServerSession session = executor.context().sessions().getSession(entry.getSession());
// Allow the executor to execute any scheduled events.
executor.tick(entry.getTimestamp());
// Expire any remaining expired sessions.
suspectSessions(entry.getTimestamp());
CompletableFuture future;
// If the server session is null, the session either never existed or already expired.
if (session == null) {
LOGGER.warn("Unknown session: " + entry.getSession());
future = Futures.exceptionalFuture(new UnknownSessionException("unknown session: " + entry.getSession()));
}
// If the session exists, don't allow it to expire even if its expiration has passed since we still
// managed to receive a keep alive request from the client before it was removed.
else {
ThreadContext context = getContext();
// Set the session as trusted. Sessions only timeout via RegisterEntry.
session.trust();
// The keep alive request contains the
session.setTimestamp(entry.getTimestamp());
// Store the command/event sequence and event version instead of acquiring a reference to the entry.
long commandSequence = entry.getCommandSequence();
long eventVersion = entry.getEventVersion();
executor.executor().execute(() -> session.clearResponses(commandSequence).resendEvents(eventVersion));
future = new CompletableFuture<>();
context.execute(() -> future.complete(null));
}
// Immediately clean the keep alive entry from the log.
cleaner.clean(entry.getIndex());
return future;
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @return The result.
*/
private CompletableFuture apply(UnregisterEntry entry) {
ServerSession session = executor.context().sessions().unregisterSession(entry.getSession());
// Allow the executor to execute any scheduled events.
executor.tick(entry.getTimestamp());
// Expire any remaining expired sessions.
suspectSessions(entry.getTimestamp());
CompletableFuture future;
// If the server session is null, the session either never existed or already expired.
if (session == null) {
LOGGER.warn("Unknown session: " + entry.getSession());
future = Futures.exceptionalFuture(new UnknownSessionException("unknown session: " + entry.getSession()));
}
// If the session exists, don't allow it to expire even if its expiration has passed since we still
// managed to receive a keep alive request from the client before it was removed.
else {
ThreadContext context = getContext();
if (session.isSuspect()) {
executor.executor().execute(() -> {
session.expire();
stateMachine.expire(session);
stateMachine.close(session);
});
} else {
executor.executor().execute(() -> {
session.close();
stateMachine.close(session);
});
}
// Clean the session registration entry from the log.
cleaner.clean(session.id());
future = new CompletableFuture<>();
context.execute(() -> future.complete(null));
}
// Immediately clean the unregister entry from the log.
cleaner.clean(entry.getIndex());
return future;
}
/**
* Applies an entry to the state machine.
*
* @param entry The entry to apply.
* @param synchronous Whether the call expects a result.
* @return The result.
*/
private CompletableFuture
© 2015 - 2025 Weber Informatics LLC | Privacy Policy