/** * Opens the service context. * * @return a future to be completed once the service context has been opened */ public CompletableFuture<Void> open() { return primaryElection.getTerm() .thenAccept(this::changeRole) .thenRun(() -> service.init(this)); }
/** * Creates a service session. * * @param sessionId the session to create * @param memberId the owning node ID * @return the service session */ public PrimaryBackupSession createSession(long sessionId, MemberId memberId) { PrimaryBackupSession session = new PrimaryBackupSession(SessionId.from(sessionId), memberId, service.serializer(), this); if (sessions.putIfAbsent(sessionId, session) == null) { service.register(session); } return session; }
maxTimeout, sessionTimestamp, service.serializer(), this, raft, session.setLastUpdated(sessionTimestamp); session.open(); service.register(sessions.addSession(session)); service.restore(new DefaultBackupInput(reader, service.serializer()));
try { currentSession = session; result = OperationResult.succeeded(currentIndex, eventIndex, service.apply(commit)); } catch (Exception e) { result = OperationResult.failed(currentIndex, eventIndex, e);
@Override public CompletableFuture<RestoreResponse> restore(RestoreRequest request) { logRequest(request); if (request.term() != context.currentTerm()) { return CompletableFuture.completedFuture(logResponse(RestoreResponse.error())); } HeapBuffer buffer = HeapBuffer.allocate(); try { Collection<PrimaryBackupSession> sessions = context.getSessions(); buffer.writeInt(sessions.size()); for (Session session : sessions) { buffer.writeLong(session.sessionId().id()); buffer.writeString(session.memberId().id()); } context.service().backup(new DefaultBackupOutput(buffer, context.service().serializer())); buffer.flip(); byte[] bytes = buffer.readBytes(buffer.remaining()); return CompletableFuture.completedFuture( RestoreResponse.ok(context.currentIndex(), context.currentTimestamp(), bytes)) .thenApply(this::logResponse); } finally { buffer.release(); } }
/** * Requests a restore from the primary. */ private void requestRestore(MemberId primary) { context.protocol().restore(primary, RestoreRequest.request(context.descriptor(), context.currentTerm())) .whenCompleteAsync((response, error) -> { if (error == null && response.status() == PrimaryBackupResponse.Status.OK) { context.resetIndex(response.index(), response.timestamp()); Buffer buffer = HeapBuffer.wrap(response.data()); int sessions = buffer.readInt(); for (int i = 0; i < sessions; i++) { context.getOrCreateSession(buffer.readLong(), MemberId.from(buffer.readString())); } context.service().restore(new DefaultBackupInput(buffer, context.service().serializer())); operations.clear(); } }, context.threadContext()); } }
public Serializer serializer() { return service.serializer(); }
/** * Sets the current timestamp. * * @param timestamp the updated timestamp * @return the current timestamp */ public long setTimestamp(long timestamp) { this.currentTimestamp = timestamp; service.tick(WallClockTimestamp.from(timestamp)); return currentTimestamp; }
/** * Registers the given session. * * @param index The index of the registration. * @param timestamp The timestamp of the registration. * @param session The session to register. */ public long openSession(long index, long timestamp, RaftSession session) { log.debug("Opening session {}", session.sessionId()); // Update the state machine index/timestamp. tick(index, timestamp); // Set the session timestamp to the current service timestamp. session.setLastUpdated(currentTimestamp); // Expire sessions that have timed out. expireSessions(currentTimestamp); // Add the session to the sessions list. session.open(); service.register(sessions.addSession(session)); // Commit the index, causing events to be sent to clients if necessary. commit(); // Complete the future. return session.sessionId().id(); }
/** * Expires the session with the given ID. * * @param sessionId the session ID */ public void expireSession(long sessionId) { PrimaryBackupSession session = sessions.remove(sessionId); if (session != null) { log.debug("Expiring session {}", session.sessionId()); session.expire(); service.expire(session.sessionId()); } }
/** * Takes a snapshot of the service state. */ public void takeSnapshot(SnapshotWriter writer) { log.debug("Taking snapshot {}", writer.snapshot().index()); // Serialize sessions to the in-memory snapshot and request a snapshot from the state machine. writer.writeLong(primitiveId.id()); writer.writeString(primitiveType.name()); writer.writeString(serviceName); writer.writeLong(currentIndex); writer.writeLong(currentTimestamp); writer.writeLong(timestampDelta); writer.writeInt(sessions.getSessions().size()); for (RaftSession session : sessions.getSessions()) { writer.writeLong(session.sessionId().id()); writer.writeString(session.memberId().id()); writer.writeString(session.readConsistency().name()); writer.writeLong(session.minTimeout()); writer.writeLong(session.maxTimeout()); writer.writeLong(session.getLastUpdated()); writer.writeLong(session.getRequestSequence()); writer.writeLong(session.getCommandSequence()); writer.writeLong(session.getEventIndex()); writer.writeLong(session.getLastCompleted()); } service.backup(new DefaultBackupOutput(writer, service.serializer())); }
/** * Resets the current index to the given index and timestamp. * * @param index the index to which to reset the current index * @param timestamp the timestamp to which to reset the current timestamp */ public void resetIndex(long index, long timestamp) { currentOperation = OperationType.COMMAND; operationIndex = index; currentIndex = index; currentTimestamp = timestamp; setCommitIndex(index); service.tick(new WallClockTimestamp(currentTimestamp)); }
/** * Expires sessions that have timed out. */ private void expireSessions(long timestamp) { // Iterate through registered sessions. for (RaftSession session : sessions.getSessions(primitiveId)) { if (session.isTimedOut(timestamp)) { log.debug("Session expired in {} milliseconds: {}", timestamp - session.getLastUpdated(), session); session = sessions.removeSession(session.sessionId()); if (session != null) { session.expire(); service.expire(session.sessionId()); } } } }
/** * Gets or creates a session. * * @param sessionId the session identifier * @return the session */ private Session getOrCreateSession(SessionId sessionId) { Session session = sessions.get(sessionId); if (session == null) { session = new LocalSession(sessionId, name(), type(), null, service.serializer()); sessions.put(session.sessionId(), session); service.register(session); } return session; }
/** * Applies the given commit to the state machine. */ private OperationResult applyCommand(long index, long sequence, long timestamp, PrimitiveOperation operation, RaftSession session) { long eventIndex = session.getEventIndex(); Commit<byte[]> commit = new DefaultCommit<>(index, operation.id(), operation.value(), session, timestamp); OperationResult result; try { currentSession = session; // Execute the state machine operation and get the result. byte[] output = service.apply(commit); // Store the result for linearizability and complete the command. result = OperationResult.succeeded(index, eventIndex, output); } catch (Exception e) { // If an exception occurs during execution of the command, store the exception. result = OperationResult.failed(index, eventIndex, e); } finally { currentSession = null; } // Once the operation has been applied to the state machine, commit events published by the command. // The state machine context will build a composite future for events published to all sessions. commit(); // Register the result in the session to ensure retries receive the same output for the command. session.registerResult(sequence, result); // Update the session timestamp and command sequence number. session.setCommandSequence(sequence); // Complete the command. return result; }
@Override public CompletableFuture<ProxySession<S>> connect() { if (connectFuture == null) { synchronized (this) { if (connectFuture == null) { session.consumer().consume(1, this::consume); service.init(context); connectFuture = session.connect().thenApply(v -> this); } } } return connectFuture; }
/** * Executes scheduled callbacks based on the provided time. */ private void tick(long index, long timestamp) { this.currentIndex = index; // If the entry timestamp is less than the current state machine timestamp // and the delta is not yet set, set the delta and do not change the current timestamp. // If the entry timestamp is less than the current state machine timestamp // and the delta is set, update the current timestamp to the entry timestamp plus the delta. // If the entry timestamp is greater than or equal to the current timestamp, update the current // timestamp and reset the delta. if (timestamp < currentTimestamp) { if (timestampDelta == 0) { timestampDelta = currentTimestamp - timestamp; } else { currentTimestamp = timestamp + timestampDelta; } } else { currentTimestamp = timestamp; timestampDelta = 0; } // Set the current operation type to COMMAND to allow events to be sent. setOperation(OperationType.COMMAND); service.tick(WallClockTimestamp.from(timestamp)); }