All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pushtechnology.diffusion.examples.runnable.CoordinatingSessionExample Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2018, 2023 DiffusionData Ltd.
 *
 * 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 com.pushtechnology.diffusion.examples.runnable;

import static com.pushtechnology.diffusion.client.Diffusion.newTopicSpecification;
import static com.pushtechnology.diffusion.client.Diffusion.updateConstraints;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pushtechnology.diffusion.client.features.ClusterRoutingException;
import com.pushtechnology.diffusion.client.features.IncompatibleTopicException;
import com.pushtechnology.diffusion.client.features.InvalidUpdateStreamException;
import com.pushtechnology.diffusion.client.features.NoSuchTopicException;
import com.pushtechnology.diffusion.client.features.TopicUpdate;
import com.pushtechnology.diffusion.client.features.UnsatisfiedConstraintException;
import com.pushtechnology.diffusion.client.features.UpdateConstraint;
import com.pushtechnology.diffusion.client.features.UpdateStream;
import com.pushtechnology.diffusion.client.session.PermissionsException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.client.topics.details.TopicType;

/**
 * Example showing how session locks and the {@link TopicUpdate} feature can
 * be used to coordinate topic control between sessions.
 * 

* Session locks are used to ensure a single instance of the client is * responsible for updating a path at any one time. Responsibility is * transferred when a session loses connection. The client handles transient * issues caused by cluster repartitioning. * * @author DiffusionData Limited * @since 6.2 */ public final class CoordinatingSessionExample extends AbstractClient { private static final Logger LOG = LoggerFactory.getLogger(CoordinatingSessionExample.class); private static final long PERIOD = 5000; private static final TimeUnit UNIT = TimeUnit.MILLISECONDS; private final ScheduledExecutorService executor = Executors .newSingleThreadScheduledExecutor(); private final Random random = new Random(); /** * Constructor. * * @param url The URL to connect to * @param principal The principal to connect as */ public CoordinatingSessionExample(String url, String principal) { super(url, principal); } @Override public void onConnected(Session session) { updatePath(session, "topic"); } private void updatePath(Session session, String path) { session // The topic path being updated is used as the lock name .lock(path, Session.SessionLockScope.UNLOCK_ON_CONNECTION_LOSS) .thenAccept(lock -> onLockAcquired(session, path, lock)); } private void onLockAcquired(Session session, String path, Session.SessionLock lock) { final TopicUpdate topicUpdate = session.feature(TopicUpdate.class); // If this session still owns the lock, update a String topic, setting // its value to the session ID of this session. The topic will be // created if it doesn't already exist. final UpdateConstraint locked = updateConstraints().locked(lock); topicUpdate.addAndSet( path + "/last_updater", newTopicSpecification(TopicType.STRING), String.class, session.getSessionId().toString(), locked); // Create a factory for update streams final TopicSpecification specification = newTopicSpecification(TopicType.INT64); final Supplier> updateStreamFactory = () -> topicUpdate.newUpdateStreamBuilder() .specification(specification) .constraint(locked) .build(path, Long.class); final UpdateStream updateStream = updateStreamFactory.get(); updateStream .validate() .whenComplete((result, ex) -> { if (ex != null) { LOG.warn("Unable to initialise first value stream. Unable to begin updating path.", ex); // The lock will be released with the session stop(); } else { // Begin updating with the stream final UpdateTask updateTask = new UpdateTask(updateStreamFactory, updateStream, random::nextLong); updateTask.scheduleUpdate(); } }); } private final class UpdateTask { private final Supplier> updateStreamFactory; private final Supplier valueSupplier; private volatile UpdateStream updateStream; private UpdateTask( Supplier> updateStreamFactory, UpdateStream updateStream, Supplier valueSupplier) { this.updateStreamFactory = updateStreamFactory; this.updateStream = updateStream; this.valueSupplier = valueSupplier; } private void scheduleUpdate() { executor.schedule((Runnable) this::performUpdate, PERIOD, UNIT); } private void performUpdate() { // Generate the next value and send update performUpdate(valueSupplier.get()); } private void performUpdate(long value) { // Send update updateStream .set(value) .whenComplete((result, ex) -> { if (ex != null) { handleUpdateFailure(value, ex); } else { scheduleUpdate(); } }); } private void handleUpdateFailure(long value, Throwable ex) { final Throwable cause = ex.getCause(); if (cause instanceof ClusterRoutingException) { // Replace stream and retry the update updateStream = updateStreamFactory.get(); performUpdate(value); } else if (cause instanceof UnsatisfiedConstraintException) { // Another session gained the session lock and is now // responsible for the the topic. LOG.warn("Another session has gained the responsibility for updating the topic"); // The lock will be released with the session stop(); } else if (cause instanceof NoSuchTopicException) { // The topic has been removed since the stream was created, // this could be recoverable but it implies that something // else has taken responsibility for the path. Attempting // to recover could cause this and something else to compete // over the topic. LOG.warn("The topic has been deleted"); // The lock will be released with the session stop(); } else if (cause instanceof InvalidUpdateStreamException) { // Something other than the stream changed the topic, this // could be recoverable but it implies that something else has // taken responsibility for the topic. Attempting to recover // could cause this and something else to compete over the // topic. For example this session could have lost connection // and another has taken over its role. LOG.warn("The update stream is no longer valid"); // The lock will be released with the session stop(); } else if (cause instanceof IncompatibleTopicException) { // Following an attempt to replace the stream, it was found the // topic has been removed and replaced with an incompatible // topic. This implies that something else has taken // responsibility for the path. LOG.warn("The topic is not compatible"); // The lock will be released with the session stop(); } else if (cause instanceof PermissionsException) { // The session does't have permission update update the path. // This is not recoverable. LOG.warn("The session doesn't have permission to update the path"); // The lock will be released with the session stop(); } else if (cause instanceof SessionClosedException) { // The session has closed. This is not recoverable. LOG.warn("The session has closed"); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy