
io.aeron.cluster.service.Cluster Maven / Gradle / Ivy
Show all versions of aeron-all Show documentation
/*
* Copyright 2014-2024 Real Logic Limited.
*
* 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
*
* https://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.aeron.cluster.service;
import io.aeron.Aeron;
import io.aeron.DirectBufferVector;
import io.aeron.ExclusivePublication;
import io.aeron.Image;
import io.aeron.cluster.client.AeronCluster;
import io.aeron.cluster.client.ClusterException;
import io.aeron.cluster.codecs.CloseReason;
import io.aeron.logbuffer.BufferClaim;
import io.aeron.logbuffer.Header;
import org.agrona.DirectBuffer;
import org.agrona.concurrent.IdleStrategy;
import org.agrona.concurrent.status.AtomicCounter;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Interface for a {@link ClusteredService} to interact with cluster hosting it.
*
* This object should only be used to send messages to the cluster or schedule timers in response to other messages
* and timers. Sending messages and timers should not happen from cluster lifecycle methods like
* {@link ClusteredService#onStart(Cluster, Image)}, {@link ClusteredService#onRoleChange(Cluster.Role)} or
* {@link ClusteredService#onTakeSnapshot(ExclusivePublication)}, or {@link ClusteredService#onTerminate(Cluster)},
* except the session lifecycle methods {@link ClusteredService#onSessionOpen(ClientSession, long)},
* {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)},
* and {@link ClusteredService#onNewLeadershipTermEvent(long, long, long, long, int, int, TimeUnit, int)}.
*/
public interface Cluster
{
/**
* Role of the node in the cluster.
*/
enum Role
{
/**
* The cluster node is a follower in the current leadership term.
*/
FOLLOWER(0),
/**
* The cluster node is a candidate to become a leader in an election.
*/
CANDIDATE(1),
/**
* The cluster node is the leader for the current leadership term.
*/
LEADER(2);
static final Role[] ROLES = values();
private final int code;
Role(final int code)
{
if (code != ordinal())
{
throw new IllegalArgumentException(name() + " - code must equal ordinal value: code=" + code);
}
this.code = code;
}
/**
* The code which matches the role in the cluster.
*
* @return the code which matches the role in the cluster.
*/
public final int code()
{
return code;
}
/**
* Get the role from a code read from a counter.
*
* @param code for the {@link Role}.
* @return the {@link Role} of the cluster node.
*/
public static Role get(final long code)
{
if (code < 0 || code > (ROLES.length - 1))
{
throw new IllegalStateException("Invalid role counter code: " + code);
}
return ROLES[(int)code];
}
/**
* Get the role by reading the code from a counter.
*
* @param counter containing the value of the role.
* @return the role for the cluster member.
*/
public static Role get(final AtomicCounter counter)
{
if (counter.isClosed())
{
return FOLLOWER;
}
return get(counter.get());
}
}
/**
* The unique id for the hosting member of the cluster. Useful only for debugging purposes.
*
* @return unique id for the hosting member of the cluster.
*/
int memberId();
/**
* The role the cluster node is playing.
*
* @return the role the cluster node is playing.
*/
Role role();
/**
* Position the log has reached in bytes as of the current message.
*
* @return position the log has reached in bytes as of the current message.
*/
long logPosition();
/**
* Get the {@link Aeron} client used by the cluster.
*
* @return the {@link Aeron} client used by the cluster.
*/
Aeron aeron();
/**
* Get the {@link ClusteredServiceContainer.Context} under which the container is running.
*
* @return the {@link ClusteredServiceContainer.Context} under which the container is running.
*/
ClusteredServiceContainer.Context context();
/**
* Get the {@link ClientSession} for a given cluster session id.
*
* @param clusterSessionId to be looked up.
* @return the {@link ClientSession} that matches the clusterSessionId.
*/
ClientSession getClientSession(long clusterSessionId);
/**
* Get the current collection of cluster client sessions.
*
* The {@link java.util.Iterator} on this class does not support nested iteration. It reuses the iterator to
* avoid allocation.
*
* @return the current collection of cluster client sessions.
*/
Collection clientSessions();
/**
* For each iterator over {@link ClientSession}s using the most efficient method possible.
*
* @param action to be taken for each {@link ClientSession} in turn.
*/
void forEachClientSession(Consumer super ClientSession> action);
/**
* Request the close of a {@link ClientSession} by sending the request to the consensus module.
*
* @param clusterSessionId to be closed.
* @return true if the event to close a session was sent or false if back pressure was applied.
* @throws ClusterException if the clusterSessionId is not recognised.
*/
boolean closeClientSession(long clusterSessionId);
/**
* Cluster time as {@link #timeUnit()}s since 1 Jan 1970 UTC.
*
* @return time as {@link #timeUnit()}s since 1 Jan 1970 UTC.
*/
long time();
/**
* The unit of time applied when timestamping and {@link #time()} operations.
*
* @return the unit of time applied when timestamping and {@link #time()} operations.
*/
TimeUnit timeUnit();
/**
* Schedule a timer for a given deadline and provide a correlation id to identify the timer when it expires or
* for cancellation. This action is asynchronous and will race with the timer expiring.
*
* If the correlationId is for an existing scheduled timer then it will be rescheduled to the new deadline. However,
* it is best to generate correlationIds in a monotonic fashion and be aware of potential clashes with other
* services in the same cluster. Service isolation can be achieved by using the upper bits for service id.
*
* Timers should only be scheduled or cancelled in the context of processing a
* {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)},
* {@link ClusteredService#onTimerEvent(long, long)},
* {@link ClusteredService#onSessionOpen(ClientSession, long)}, or
* {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)}.
* If applied to other events then they are not guaranteed to be reliable.
*
* Callers of this method should loop until the method succeeds.
*
*
{@code
* private Cluster cluster;
* // Lines omitted...
*
* cluster.idleStrategy().reset();
* while (!cluster.scheduleTimer(correlationId, deadline))
* {
* cluster.idleStrategy().idle();
* }
* }
*
* The cluster's idle strategy must be used in the body of the loop to allow for the clustered service to be
* shutdown if required.
*
* @param correlationId to identify the timer when it expires. {@link Long#MAX_VALUE} not supported.
* @param deadline time after which the timer will fire. {@link Long#MAX_VALUE} not supported.
* @return true if the event to schedule a timer request has been sent or false if back-pressure is applied.
* @see #cancelTimer(long)
*/
boolean scheduleTimer(long correlationId, long deadline);
/**
* Cancel a previously scheduled timer. This action is asynchronous and will race with the timer expiring.
*
* Timers should only be scheduled or cancelled in the context of processing a
* {@link ClusteredService#onSessionMessage(ClientSession, long, DirectBuffer, int, int, Header)},
* {@link ClusteredService#onTimerEvent(long, long)},
* {@link ClusteredService#onSessionOpen(ClientSession, long)}, or
* {@link ClusteredService#onSessionClose(ClientSession, long, CloseReason)}.
* If applied to other events then they are not guaranteed to be reliable.
*
* Callers of this method should loop until the method succeeds, see {@link
* io.aeron.cluster.service.Cluster#scheduleTimer(long, long)} for an example.
*
* @param correlationId for the timer provided when it was scheduled. {@link Long#MAX_VALUE} not supported.
* @return true if the event to cancel request has been sent or false if back-pressure is applied.
* @see #scheduleTimer(long, long)
*/
boolean cancelTimer(long correlationId);
/**
* Offer a message as ingress to the cluster for sequencing. This will happen efficiently over IPC to the
* consensus module and have the cluster session of as the
* {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME}.
*
* Callers of this method should loop until the method succeeds.
*
*
{@code
* private Cluster cluster;
* // Lines omitted...
*
* cluster.idleStrategy().reset();
* do
* {
* final long position = cluster.offer(buffer, offset, length);
* if (position > 0)
* {
* break;
* }
* else if (Publication.ADMIN_ACTION != position && Publication.BACK_PRESSURED != position)
* {
* throw new ClusterException("Internal offer failed: " + position);
* }
*
* cluster.idleStrategy.idle();
* }
* while (true);
* }
*
* The cluster's idle strategy must be used in the body of the loop to allow for the clustered service to be
* shutdown if required.
*
* @param buffer containing the message to be offered.
* @param offset in the buffer at which the encoded message begins.
* @param length in the buffer of the encoded message.
* @return positive value if successful.
* @see io.aeron.Publication#offer(DirectBuffer, int, int)
*/
long offer(DirectBuffer buffer, int offset, int length);
/**
* Offer a message as ingress to the cluster for sequencing. This will happen efficiently over IPC to the
* consensus module and have the cluster session of as the
* {@link io.aeron.cluster.service.ClusteredServiceContainer.Configuration#SERVICE_ID_PROP_NAME}.
*
* The first vector must be left free to be filled in for the session message header.
*
* Callers of this method should loop until the method succeeds, see
* {@link io.aeron.cluster.service.Cluster#offer(DirectBuffer, int, int)} for an example.
*
* @param vectors containing the message parts with the first left to be filled.
* @return positive value if successful.
* @see io.aeron.Publication#offer(DirectBufferVector[])
*/
long offer(DirectBufferVector[] vectors);
/**
* Try to claim a range in the publication log into which a message can be written with zero copy semantics.
* Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.
*
* On successful claim, the Cluster session header will be written to the start of the claimed buffer section.
* Clients MUST write into the claimed buffer region at offset + {@link AeronCluster#SESSION_HEADER_LENGTH}.
*
{@code
* final DirectBuffer srcBuffer = acquireMessage();
*
* if (cluster.tryClaim(length, bufferClaim))
* {
* try
* {
* final MutableDirectBuffer buffer = bufferClaim.buffer();
* final int offset = bufferClaim.offset();
* // ensure that data is written at the correct offset
* buffer.putBytes(offset + AeronCluster.SESSION_HEADER_LENGTH, srcBuffer, 0, length);
* }
* finally
* {
* bufferClaim.commit();
* }
* }
* }
*
* Callers of this method should loop until the method succeeds, see
* {@link io.aeron.cluster.service.Cluster#offer(DirectBuffer, int, int)} for an example.
*
* @param length of the range to claim, in bytes.
* @param bufferClaim to be populated if the claim succeeds.
* @return positive value if successful.
* @throws IllegalArgumentException if the length is greater than {@link io.aeron.Publication#maxPayloadLength()}.
* @see io.aeron.Publication#tryClaim(int, BufferClaim)
* @see BufferClaim#commit()
* @see BufferClaim#abort()
*/
long tryClaim(int length, BufferClaim bufferClaim);
/**
* {@link IdleStrategy} which should be used by the service when it experiences back-pressure on egress,
* closing sessions, making timer requests, or any long-running actions.
*
* @return the {@link IdleStrategy} which should be used by the service when it experiences back-pressure.
*/
IdleStrategy idleStrategy();
}