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

momento.sdk.SubscriptionWrapper Maven / Gradle / Ivy

package momento.sdk;

import com.google.protobuf.ByteString;
import grpc.cache_client.pubsub._SubscriptionItem;
import grpc.cache_client.pubsub._SubscriptionRequest;
import grpc.cache_client.pubsub._TopicItem;
import grpc.cache_client.pubsub._TopicValue;
import io.grpc.Status;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import momento.sdk.exceptions.CacheServiceExceptionMapper;
import momento.sdk.exceptions.InternalServerException;
import momento.sdk.responses.topic.TopicMessage;
import momento.sdk.responses.topic.TopicSubscribeResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class SubscriptionWrapper implements AutoCloseable {
  private final Logger logger = LoggerFactory.getLogger(SubscriptionWrapper.class);
  private final IScsTopicConnection connection;
  private final SendSubscribeOptions options;
  private boolean firstMessage = true;
  private boolean isConnectionLost = false;

  private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

  private CancelableClientCallStreamObserver<_SubscriptionItem> subscription;

  SubscriptionWrapper(IScsTopicConnection connection, SendSubscribeOptions options) {
    this.connection = connection;
    this.options = options;
  }

  /**
   * Public method for testing purposes only. Do not call this method in production code or any
   * context other than testing the topic client.
   *
   * 

This method returns a CompletableFuture that represents the asynchronous execution of the * internal subscription logic with retry mechanism. * * @return A CompletableFuture representing the asynchronous execution of the internal * subscription logic with retry mechanism. */ public CompletableFuture subscribeWithRetry() { CompletableFuture future = new CompletableFuture<>(); subscribeWithRetryInternal(future); return future; } private void subscribeWithRetryInternal(CompletableFuture future) { subscription = new CancelableClientCallStreamObserver<_SubscriptionItem>() { @Override public void onNext(_SubscriptionItem item) { if (firstMessage) { if (item.getKindCase() != _SubscriptionItem.KindCase.HEARTBEAT) { future.completeExceptionally( new InternalServerException( "Expected heartbeat message for topic " + options.getTopicName() + " on cache " + options.getCacheName() + ". Got: " + item.getKindCase())); } firstMessage = false; future.complete(null); } if (isConnectionLost) { isConnectionLost = false; options.onConnectionRestored(); } handleSubscriptionItem(item); } @Override public void onError(Throwable t) { if (firstMessage) { firstMessage = false; future.completeExceptionally(t); } else { logger.debug("Subscription failed, retrying..."); if (!isConnectionLost) { isConnectionLost = true; options.onConnectionLost(); } if (t instanceof io.grpc.StatusRuntimeException) { logger.debug( "Throwable is an instance of StatusRuntimeException, checking status code..."); io.grpc.StatusRuntimeException statusRuntimeException = (io.grpc.StatusRuntimeException) t; if (statusRuntimeException.getStatus().getCode() == Status.Code.UNAVAILABLE) { logger.info("Status code is UNAVAILABLE, retrying subscription after a delay..."); scheduleRetry(() -> subscribeWithRetryInternal(future)); } else { logger.debug("Status code is not UNAVAILABLE, not retrying subscription."); } } else { logger.debug( "Throwable is not an instance of StatusRuntimeException, not retrying subscription."); options.onError(t); } } } @Override public void onCompleted() { handleSubscriptionCompleted(); } }; _SubscriptionRequest subscriptionRequest = _SubscriptionRequest.newBuilder() .setCacheName(options.getCacheName()) .setTopic(options.getTopicName()) .setResumeAtTopicSequenceNumber( options.subscriptionState.getResumeAtTopicSequenceNumber()) .build(); try { connection.subscribe(subscriptionRequest, subscription); options.subscriptionState.setSubscribed(); } catch (Exception e) { future.completeExceptionally( new TopicSubscribeResponse.Error(CacheServiceExceptionMapper.convert(e))); } } private void scheduleRetry(Runnable retryAction) { scheduler.schedule(retryAction, 5, TimeUnit.SECONDS); } private void handleSubscriptionCompleted() { this.options.onCompleted(); } private void handleSubscriptionItem(_SubscriptionItem item) { switch (item.getKindCase()) { case ITEM: handleSubscriptionItemMessage(item); break; case DISCONTINUITY: handleSubscriptionDiscontinuity(item); break; case HEARTBEAT: handleSubscriptionHeartbeat(); break; default: handleSubscriptionUnknown(); break; } } private void handleSubscriptionDiscontinuity(_SubscriptionItem discontinuityItem) { logger.debug( "{}, {}, {}, {}", options.getCacheName(), options.getTopicName(), discontinuityItem.getDiscontinuity().getLastTopicSequence(), discontinuityItem.getDiscontinuity().getNewTopicSequence()); } private void handleSubscriptionHeartbeat() { logger.debug("heartbeat {} {}", options.getCacheName(), options.getTopicName()); } private void handleSubscriptionUnknown() { logger.warn("unknown {} {}", options.getCacheName(), options.getTopicName()); } private void handleSubscriptionItemMessage(_SubscriptionItem item) { _TopicItem topicItem = item.getItem(); _TopicValue topicValue = topicItem.getValue(); options.subscriptionState.setResumeAtTopicSequenceNumber( (int) topicItem.getTopicSequenceNumber()); TopicMessage message; switch (topicValue.getKindCase()) { case TEXT: message = handleSubscriptionTextMessage(topicValue.getText(), item.getItem().getPublisherId()); this.options.onItem(message); break; case BINARY: message = handleSubscriptionBinaryMessage( topicValue.getBinary().toByteArray(), item.getItem().getPublisherId()); this.options.onItem(message); break; default: handleSubscriptionUnknown(); break; } } private TopicMessage.Text handleSubscriptionTextMessage(String text, String publisherId) { _TopicValue topicValue = _TopicValue.newBuilder().setText(text).build(); return new TopicMessage.Text(topicValue, publisherId.isEmpty() ? null : publisherId); } private TopicMessage.Binary handleSubscriptionBinaryMessage(byte[] binary, String publisherId) { _TopicValue topicValue = _TopicValue.newBuilder().setBinary(ByteString.copyFrom(binary)).build(); return new TopicMessage.Binary(topicValue, publisherId.isEmpty() ? null : publisherId); } public void unsubscribe() { subscription.cancel( "Unsubscribing from topic: " + options.getTopicName() + " in cache: " + options.getCacheName(), null); } @Override public void close() { if (subscription != null) { subscription.onCompleted(); } scheduler.shutdown(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy