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

com.rabbitmq.stream.impl.StreamEnvironment Maven / Gradle / Ivy

Go to download

The RabbitMQ Stream Java client library allows Java applications to interface with RabbitMQ Stream.

The newest version!
// Copyright (c) 2020-2023 Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// This software, the RabbitMQ Stream Java client library, is dual-licensed under the
// Mozilla Public License 2.0 ("MPL"), and the Apache License version 2 ("ASL").
// For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.stream.impl;

import static com.rabbitmq.stream.impl.AsyncRetry.asyncRetry;
import static com.rabbitmq.stream.impl.Utils.*;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.rabbitmq.stream.*;
import com.rabbitmq.stream.MessageHandler.Context;
import com.rabbitmq.stream.compression.CompressionCodecFactory;
import com.rabbitmq.stream.impl.Client.ClientParameters;
import com.rabbitmq.stream.impl.Client.ShutdownListener;
import com.rabbitmq.stream.impl.Client.StreamStatsResponse;
import com.rabbitmq.stream.impl.OffsetTrackingCoordinator.Registration;
import com.rabbitmq.stream.impl.StreamConsumerBuilder.TrackingConfiguration;
import com.rabbitmq.stream.impl.StreamEnvironmentBuilder.DefaultTlsConfiguration;
import com.rabbitmq.stream.impl.Utils.ClientConnectionType;
import com.rabbitmq.stream.sasl.CredentialsProvider;
import com.rabbitmq.stream.sasl.UsernamePasswordCredentialsProvider;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.URLDecoder;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.SSLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class StreamEnvironment implements Environment {

  private static final Logger LOGGER = LoggerFactory.getLogger(StreamEnvironment.class);

  private final EventLoopGroup eventLoopGroup;
  private final ScheduledExecutorService scheduledExecutorService;
  private final boolean privateScheduleExecutorService;
  private final Client.ClientParameters clientParametersPrototype;
  private final List
addresses; private final List producers = new CopyOnWriteArrayList<>(); private final List consumers = new CopyOnWriteArrayList<>(); private final Codec codec; private final BackOffDelayPolicy recoveryBackOffDelayPolicy; private final BackOffDelayPolicy topologyUpdateBackOffDelayPolicy; private final ConsumersCoordinator consumersCoordinator; private final ProducersCoordinator producersCoordinator; private final OffsetTrackingCoordinator offsetTrackingCoordinator; private final AtomicBoolean closed = new AtomicBoolean(false); private final AddressResolver addressResolver; private final Clock clock = new Clock(); private final ScheduledFuture clockRefreshFuture; private final ByteBufAllocator byteBufAllocator; private final AtomicBoolean locatorsInitialized = new AtomicBoolean(false); private final Runnable locatorInitializationSequence; private final List locators = new CopyOnWriteArrayList<>(); private final ExecutorServiceFactory executorServiceFactory; private final ObservationCollector observationCollector; @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") StreamEnvironment( ScheduledExecutorService scheduledExecutorService, Client.ClientParameters clientParametersPrototype, List uris, BackOffDelayPolicy recoveryBackOffDelayPolicy, BackOffDelayPolicy topologyBackOffDelayPolicy, AddressResolver addressResolver, int maxProducersByConnection, int maxTrackingConsumersByConnection, int maxConsumersByConnection, DefaultTlsConfiguration tlsConfiguration, ByteBufAllocator byteBufAllocator, boolean lazyInit, Function connectionNamingStrategy, Function clientFactory, ObservationCollector observationCollector, boolean forceReplicaForConsumers) { this.recoveryBackOffDelayPolicy = recoveryBackOffDelayPolicy; this.topologyUpdateBackOffDelayPolicy = topologyBackOffDelayPolicy; this.byteBufAllocator = byteBufAllocator; clientParametersPrototype = clientParametersPrototype.byteBufAllocator(byteBufAllocator); clientParametersPrototype = maybeSetUpClientParametersFromUris(uris, clientParametersPrototype); this.observationCollector = observationCollector; boolean tls; if (tlsConfiguration != null && tlsConfiguration.enabled()) { tls = true; try { SslContext sslContext = tlsConfiguration.sslContext() == null ? SslContextBuilder.forClient().build() : tlsConfiguration.sslContext(); clientParametersPrototype.sslContext(sslContext); clientParametersPrototype.tlsHostnameVerification( tlsConfiguration.hostnameVerificationEnabled()); } catch (SSLException e) { throw new StreamException("Error while creating Netty SSL context", e); } } else { tls = false; } if (uris.isEmpty()) { this.addresses = Collections.singletonList( new Address(clientParametersPrototype.host(), clientParametersPrototype.port())); } else { int defaultPort = tls ? Client.DEFAULT_TLS_PORT : Client.DEFAULT_PORT; this.addresses = uris.stream() .map( uriItem -> new Address( uriItem.getHost() == null ? "localhost" : uriItem.getHost(), uriItem.getPort() == -1 ? defaultPort : uriItem.getPort())) .collect(Collectors.toList()); } AddressResolver addressResolverToUse = addressResolver; if (this.addresses.size() == 1 && "localhost".equals(this.addresses.get(0).host()) && addressResolver == DEFAULT_ADDRESS_RESOLVER) { CredentialsProvider credentialsProvider = clientParametersPrototype.credentialsProvider(); if (credentialsProvider instanceof UsernamePasswordCredentialsProvider) { String username = ((UsernamePasswordCredentialsProvider) credentialsProvider).getUsername(); if (DEFAULT_USERNAME.equals(username)) { Address address = new Address("localhost", clientParametersPrototype.port()); Set
passedInAddresses = ConcurrentHashMap.newKeySet(); addressResolverToUse = addr -> { passedInAddresses.add(addr); if (passedInAddresses.size() > 1) { LOGGER.warn("Assumed development environment but it seems incorrect."); passedInAddresses.clear(); } return address; }; LOGGER.info( "Connecting to localhost with {} user, assuming development environment", DEFAULT_USERNAME); LOGGER.info("Using address resolver to always connect to localhost"); } } } this.addressResolver = addressResolverToUse; this.addresses.forEach(address -> this.locators.add(new Locator(address))); this.executorServiceFactory = new DefaultExecutorServiceFactory( this.addresses.size(), 1, "rabbitmq-stream-locator-connection-"); if (clientParametersPrototype.eventLoopGroup == null) { this.eventLoopGroup = new NioEventLoopGroup(); this.clientParametersPrototype = clientParametersPrototype.duplicate().eventLoopGroup(this.eventLoopGroup); } else { this.eventLoopGroup = null; this.clientParametersPrototype = clientParametersPrototype .duplicate() .eventLoopGroup(clientParametersPrototype.eventLoopGroup); } ScheduledExecutorService executorService; if (scheduledExecutorService == null) { int threads = Runtime.getRuntime().availableProcessors(); LOGGER.debug("Creating scheduled executor service with {} thread(s)", threads); ThreadFactory threadFactory = new Utils.NamedThreadFactory("rabbitmq-stream-environment-scheduler-"); executorService = Executors.newScheduledThreadPool(threads, threadFactory); this.privateScheduleExecutorService = true; } else { executorService = scheduledExecutorService; this.privateScheduleExecutorService = false; } this.scheduledExecutorService = executorService; this.producersCoordinator = new ProducersCoordinator( this, maxProducersByConnection, maxTrackingConsumersByConnection, connectionNamingStrategy, Utils.coordinatorClientFactory(this)); this.consumersCoordinator = new ConsumersCoordinator( this, maxConsumersByConnection, connectionNamingStrategy, Utils.coordinatorClientFactory(this), forceReplicaForConsumers); this.offsetTrackingCoordinator = new OffsetTrackingCoordinator(this); ClientParameters clientParametersForInit = locatorParametersCopy(); Runnable locatorInitSequence = () -> { RuntimeException lastException = null; for (int i = 0; i < addresses.size(); i++) { Address address = addresses.get(i); Locator locator = locator(i); address = addressResolver.resolve(address); String connectionName = connectionNamingStrategy.apply(ClientConnectionType.LOCATOR); Client.ClientParameters locatorParameters = clientParametersForInit .duplicate() .host(address.host()) .port(address.port()) .clientProperty("connection_name", connectionName) .shutdownListener( shutdownListener(locator, connectionNamingStrategy, clientFactory)); try { Client client = clientFactory.apply(locatorParameters); locator.client(client); LOGGER.debug("Created locator connection '{}'", connectionName); LOGGER.debug("Locator connected to {}", address); } catch (RuntimeException e) { LOGGER.debug("Error while try to connect to {}: {}", address, e.getMessage()); lastException = e; } } if (this.locators.stream().allMatch(Locator::isNotSet)) { throw lastException == null ? new StreamException("Not locator available") : lastException; } else { this.locators.forEach( l -> { if (l.isNotSet()) { scheduleLocatorConnection(l, connectionNamingStrategy, clientFactory); } }); } }; if (lazyInit) { this.locatorInitializationSequence = locatorInitSequence; } else { locatorInitSequence.run(); locatorsInitialized.set(true); this.locatorInitializationSequence = () -> {}; } this.codec = clientParametersPrototype.codec() == null ? Codecs.DEFAULT : clientParametersPrototype.codec(); this.clockRefreshFuture = this.scheduledExecutorService.scheduleAtFixedRate( namedRunnable(this.clock::refresh, "Background clock refresh"), 1, 1, SECONDS); } private ShutdownListener shutdownListener( Locator locator, Function connectionNamingStrategy, Function clientFactory) { AtomicReference shutdownListenerReference = new AtomicReference<>(); Client.ShutdownListener shutdownListener = shutdownContext -> { if (shutdownContext.isShutdownUnexpected()) { locator.client(null); LOGGER.debug( "Unexpected locator disconnection for locator on '{}', trying to reconnect", locator.label()); try { Client.ClientParameters newLocatorParameters = this.locatorParametersCopy().shutdownListener(shutdownListenerReference.get()); asyncRetry( () -> { LOGGER.debug("Locator reconnection..."); Address resolvedAddress = addressResolver.resolve(locator.address()); String connectionName = connectionNamingStrategy.apply(ClientConnectionType.LOCATOR); LOGGER.debug( "Trying to reconnect locator on {}, with client connection name '{}'", resolvedAddress, connectionName); Client newLocator = clientFactory.apply( newLocatorParameters .host(resolvedAddress.host()) .port(resolvedAddress.port()) .clientProperty("connection_name", connectionName)); LOGGER.debug("Created locator connection '{}'", connectionName); LOGGER.debug("Locator connected on {}", resolvedAddress); return newLocator; }) .description("Locator recovery") .scheduler(this.scheduledExecutorService) .delayPolicy(recoveryBackOffDelayPolicy) .build() .thenAccept(locator::client) .exceptionally( ex -> { LOGGER.debug("Locator recovery failed", ex); return null; }); } catch (Exception e) { LOGGER.debug("Error while scheduling locator reconnection", e); } } else { LOGGER.debug("Locator connection '{}' closing normally", locator.label()); } }; shutdownListenerReference.set(shutdownListener); return shutdownListener; } private void scheduleLocatorConnection( Locator locator, Function connectionNamingStrategy, Function clientFactory) { ShutdownListener shutdownListener = shutdownListener(locator, connectionNamingStrategy, clientFactory); try { Client.ClientParameters newLocatorParameters = this.locatorParametersCopy().shutdownListener(shutdownListener); asyncRetry( () -> { LOGGER.debug("Locator reconnection..."); Address resolvedAddress = addressResolver.resolve(locator.address()); String connectionName = connectionNamingStrategy.apply(ClientConnectionType.LOCATOR); LOGGER.debug( "Trying to reconnect locator on {}, with client connection name '{}'", resolvedAddress, connectionName); Client newLocator = clientFactory.apply( newLocatorParameters .host(resolvedAddress.host()) .port(resolvedAddress.port()) .clientProperty("connection_name", connectionName)); LOGGER.debug("Created locator connection '{}'", connectionName); LOGGER.debug("Locator connected on {}", resolvedAddress); return newLocator; }) .description("Locator recovery") .scheduler(this.scheduledExecutorService) .delayPolicy(recoveryBackOffDelayPolicy) .build() .thenAccept(locator::client) .exceptionally( ex -> { LOGGER.debug("Locator recovery failed", ex); return null; }); } catch (Exception e) { LOGGER.debug("Error while scheduling locator reconnection", e); } } private Locator locator(int i) { return this.locators.get(i); } private static String uriDecode(String s) { try { // URLDecode decodes '+' to a space, as for // form encoding. So protect plus signs. return URLDecoder.decode(s.replace("+", "%2B"), "US-ASCII"); } catch (IOException e) { throw new IllegalArgumentException(e); } } Client.ClientParameters maybeSetUpClientParametersFromUris( List uris, Client.ClientParameters clientParametersPrototype) { if (uris.isEmpty()) { return clientParametersPrototype; } else { URI uri = uris.get(0); clientParametersPrototype = clientParametersPrototype.duplicate(); String host = uri.getHost(); if (host != null) { clientParametersPrototype.host(host); } int port = uri.getPort(); if (port != -1) { clientParametersPrototype.port(port); } String userInfo = uri.getRawUserInfo(); if (userInfo != null) { String[] userPassword = userInfo.split(":"); if (userPassword.length > 2) { throw new IllegalArgumentException("Bad user info in URI " + userInfo); } clientParametersPrototype.username(uriDecode(userPassword[0])); if (userPassword.length == 2) { clientParametersPrototype.password(uriDecode(userPassword[1])); } } String path = uri.getRawPath(); if (path != null && path.length() > 0) { if (path.indexOf('/', 1) != -1) { throw new IllegalArgumentException("Multiple segments in path of URI: " + path); } clientParametersPrototype.virtualHost(uriDecode(uri.getPath().substring(1))); } return clientParametersPrototype; } } public ByteBufAllocator byteBufAllocator() { return byteBufAllocator; } void maybeInitializeLocator() { if (this.locatorsInitialized.compareAndSet(false, true)) { try { this.locatorInitializationSequence.run(); } catch (RuntimeException e) { this.locatorsInitialized.set(false); throw e; } } } @Override public StreamCreator streamCreator() { checkNotClosed(); return new StreamStreamCreator(this); } @Override public void deleteStream(String stream) { checkNotClosed(); this.maybeInitializeLocator(); Client.Response response = this.locator().delete(stream); if (!response.isOk()) { throw new StreamException( "Error while deleting stream " + stream + " (" + formatConstant(response.getResponseCode()) + ")", response.getResponseCode()); } } @Override public void deleteSuperStream(String superStream) { checkNotClosed(); this.maybeInitializeLocator(); Client.Response response = this.locator().deleteSuperStream(superStream); if (!response.isOk()) { throw new StreamException( "Error while deleting super stream " + superStream + " (" + formatConstant(response.getResponseCode()) + ")", response.getResponseCode()); } } @Override public StreamStats queryStreamStats(String stream) { checkNotClosed(); this.maybeInitializeLocator(); StreamStatsResponse response = locatorOperation( Utils.namedFunction( client -> client.streamStats(stream), "Query stream stats on stream '%s'", stream)); if (response.isOk()) { Map info = response.getInfo(); BiFunction offsetSupplierLogic = (key, message) -> { if (!info.containsKey(key) || info.get(key) == -1) { return () -> { throw new NoOffsetException(message); }; } else { try { long offset = info.get(key); return () -> offset; } catch (NumberFormatException e) { return () -> { throw new NoOffsetException(message); }; } } }; LongSupplier firstOffsetSupplier = offsetSupplierLogic.apply("first_chunk_id", "No first offset for stream " + stream); LongSupplier committedOffsetSupplier = offsetSupplierLogic.apply( "committed_chunk_id", "No committed chunk ID for stream " + stream); return new DefaultStreamStats(firstOffsetSupplier, committedOffsetSupplier); } else { throw convertCodeToException( response.getResponseCode(), stream, () -> "Error while querying stream stats: " + formatConstant(response.getResponseCode()) + "."); } } @Override public boolean streamExists(String stream) { checkNotClosed(); this.maybeInitializeLocator(); short responseCode = locatorOperation( Utils.namedFunction( client -> { try { return client.streamStats(stream).getResponseCode(); } catch (UnsupportedOperationException e) { Map metadata = client.metadata(stream); return metadata.get(stream).getResponseCode(); } }, "Stream exists for stream '%s'", stream)); if (responseCode == Constants.RESPONSE_CODE_OK) { return true; } else if (responseCode == Constants.RESPONSE_CODE_STREAM_DOES_NOT_EXIST) { return false; } else { throw convertCodeToException( responseCode, stream, () -> format( "Unexpected result when checking if stream '%s' exists: %s.", stream, formatConstant(responseCode))); } } private static class DefaultStreamStats implements StreamStats { private final LongSupplier firstOffsetSupplier, committedOffsetSupplier; private DefaultStreamStats( LongSupplier firstOffsetSupplier, LongSupplier committedOffsetSupplier) { this.firstOffsetSupplier = firstOffsetSupplier; this.committedOffsetSupplier = committedOffsetSupplier; } @Override public long firstOffset() { return firstOffsetSupplier.getAsLong(); } @Override public long committedChunkId() { return committedOffsetSupplier.getAsLong(); } } @Override public ProducerBuilder producerBuilder() { checkNotClosed(); return new StreamProducerBuilder(this); } void addProducer(StreamProducer producer) { this.producers.add(producer); } void removeProducer(StreamProducer producer) { this.producers.remove(producer); } void addConsumer(StreamConsumer consumer) { this.consumers.add(consumer); } void removeConsumer(StreamConsumer consumer) { this.consumers.remove(consumer); } @Override public ConsumerBuilder consumerBuilder() { checkNotClosed(); return new StreamConsumerBuilder(this); } @Override public void close() { if (closed.compareAndSet(false, true)) { for (StreamProducer producer : producers) { try { producer.closeFromEnvironment(); } catch (Exception e) { LOGGER.warn("Error while closing producer, moving on to the next one", e); } } for (StreamConsumer consumer : consumers) { try { consumer.closeFromEnvironment(); } catch (Exception e) { LOGGER.warn("Error while closing consumer, moving on to the next one", e); } } this.producersCoordinator.close(); this.consumersCoordinator.close(); this.offsetTrackingCoordinator.close(); for (Locator locator : this.locators) { try { if (locator.isSet()) { locator.client().close(); locator.client(null); } } catch (Exception e) { LOGGER.warn("Error while closing locator client", e); } } try { this.executorServiceFactory.close(); } catch (Exception e) { LOGGER.info("Error while closing executor service factory: {}", e.getMessage()); } this.clockRefreshFuture.cancel(false); if (privateScheduleExecutorService) { this.scheduledExecutorService.shutdownNow(); } try { if (this.eventLoopGroup != null && (!this.eventLoopGroup.isShuttingDown() || !this.eventLoopGroup.isShutdown())) { LOGGER.debug("Closing Netty event loop group"); this.eventLoopGroup.shutdownGracefully(1, 10, SECONDS).get(10, SECONDS); } } catch (InterruptedException e) { LOGGER.info("Event loop group closing has been interrupted"); Thread.currentThread().interrupt(); } catch (ExecutionException e) { LOGGER.info("Event loop group closing failed", e); } catch (TimeoutException e) { LOGGER.info("Could not close event loop group in 10 seconds"); } } } ScheduledExecutorService scheduledExecutorService() { return this.scheduledExecutorService; } void execute(Runnable task, String description, Object... args) { this.scheduledExecutorService().execute(namedRunnable(task, description, args)); } BackOffDelayPolicy recoveryBackOffDelayPolicy() { return this.recoveryBackOffDelayPolicy; } BackOffDelayPolicy topologyUpdateBackOffDelayPolicy() { return this.topologyUpdateBackOffDelayPolicy; } CompressionCodecFactory compressionCodecFactory() { return this.clientParametersPrototype.compressionCodecFactory; } ObservationCollector observationCollector() { return this.observationCollector; } Runnable registerConsumer( StreamConsumer consumer, String stream, OffsetSpecification offsetSpecification, String trackingReference, SubscriptionListener subscriptionListener, Runnable trackingClosingCallback, MessageHandler messageHandler, Map subscriptionProperties, ConsumerFlowStrategy flowStrategy) { return this.consumersCoordinator.subscribe( consumer, stream, offsetSpecification, trackingReference, subscriptionListener, trackingClosingCallback, messageHandler, subscriptionProperties, flowStrategy); } Runnable registerProducer(StreamProducer producer, String reference, String stream) { return producersCoordinator.registerProducer(producer, reference, stream); } Client locator() { return this.locators.stream() .filter(Locator::isSet) .findAny() .orElseThrow(LocatorNotAvailableException::new) .client(); } T locatorOperation(Function operation) { return locatorOperation(operation, this::locator, this.recoveryBackOffDelayPolicy); } static T locatorOperation( Function operation, Supplier clientSupplier, BackOffDelayPolicy backOffDelayPolicy) { int maxAttempt = 3; int attempt = 0; boolean executed = false; Exception lastException = null; T result = null; LOGGER.debug("Starting locator operation '{}'", operation); long start = System.nanoTime(); while (attempt < maxAttempt) { try { Client client = clientSupplier.get(); LOGGER.debug( "Using locator on {}:{} to run operation '{}'", client.getHost(), client.getPort(), operation); result = operation.apply(client); LOGGER.debug( "Locator operation '{}' succeeded in {}", operation, Duration.ofNanos(System.nanoTime() - start)); executed = true; break; } catch (LocatorNotAvailableException e) { Duration waitTime = backOffDelayPolicy.delay(attempt); LOGGER.debug( "No locator available for operation '{}', waiting for {} before retrying", operation, waitTime); attempt++; try { Thread.sleep(waitTime.toMillis()); } catch (InterruptedException ex) { lastException = ex; Thread.currentThread().interrupt(); break; } } catch (Exception e) { LOGGER.debug("Exception during locator operation '{}': {}", operation, exceptionMessage(e)); lastException = e; break; } } if (!executed) { if (lastException == null) { throw new LocatorNotAvailableException(); } else { throw new StreamException( "Could not execute operation after " + maxAttempt + " attempts", lastException); } } return result; } boolean filteringSupported() { return this.locatorOperation(Client::filteringSupported); } Clock clock() { return this.clock; } AddressResolver addressResolver() { return this.addressResolver; } Codec codec() { return this.codec; } Client.ClientParameters clientParametersCopy() { return this.clientParametersPrototype.duplicate(); } private Client.ClientParameters locatorParametersCopy() { return this.clientParametersPrototype .duplicate() .executorServiceFactory(this.executorServiceFactory) .dispatchingExecutorServiceFactory(Utils.NO_OP_EXECUTOR_SERVICE_FACTORY); } TrackingConsumerRegistration registerTrackingConsumer( StreamConsumer streamConsumer, TrackingConfiguration configuration) { Runnable closingCallable = this.producersCoordinator.registerTrackingConsumer(streamConsumer); Registration offsetTrackingRegistration; if (this.offsetTrackingCoordinator.needTrackingRegistration(configuration)) { offsetTrackingRegistration = this.offsetTrackingCoordinator.registerTrackingConsumer(streamConsumer, configuration); } else { offsetTrackingRegistration = null; } Runnable closingSequence; if (offsetTrackingRegistration == null) { closingSequence = closingCallable; } else { closingSequence = () -> { try { LOGGER.debug("Executing offset tracking registration closing sequence "); offsetTrackingRegistration.closingCallback().run(); LOGGER.debug("Offset tracking registration closing sequence executed"); } catch (Exception e) { LOGGER.warn( "Error while executing offset tracking registration closing sequence: {}", e.getMessage()); } closingCallable.run(); }; } return new TrackingConsumerRegistration( closingSequence, offsetTrackingRegistration == null ? null : offsetTrackingRegistration.postMessageProcessingCallback(), offsetTrackingRegistration == null ? Utils.NO_OP_LONG_CONSUMER : offsetTrackingRegistration.trackingCallback(), offsetTrackingRegistration == null ? Utils.NO_OP_LONG_SUPPLIER : offsetTrackingRegistration::flush); } @Override public String toString() { return "{ \"locators\" : [" + this.locators.stream() .map( l -> { Client c = l.nullableClient(); return c == null ? "null" : ("\"" + c.connectionName() + "\""); }) .collect(Collectors.joining(",")) + "], " + Utils.jsonField("producer_client_count", this.producersCoordinator.clientCount()) + "," + Utils.jsonField("consumer_client_count", this.consumersCoordinator.managerCount()) + "," + "\"producers\" : " + this.producersCoordinator + ", \"consumers\" : " + this.consumersCoordinator + ", \"offset_tracking\" : " + this.offsetTrackingCoordinator + "}"; } static class TrackingConsumerRegistration { private final Runnable closingCallback; private final Consumer postMessageProcessingCallback; private final LongConsumer trackingCallback; private final LongSupplier flushOperation; TrackingConsumerRegistration( Runnable closingCallback, Consumer postMessageProcessingCallback, LongConsumer trackingCallback, LongSupplier flushOperation) { this.closingCallback = closingCallback; this.postMessageProcessingCallback = postMessageProcessingCallback; this.trackingCallback = trackingCallback; this.flushOperation = flushOperation; } Runnable closingCallback() { return closingCallback; } LongConsumer trackingCallback() { return trackingCallback; } Consumer postMessageProcessingCallback() { return postMessageProcessingCallback; } long flush() { return this.flushOperation.getAsLong(); } } static class LocatorNotAvailableException extends StreamException { public LocatorNotAvailableException() { super("Locator not available"); } } private void checkNotClosed() { if (this.closed.get()) { throw new IllegalStateException("This environment instance has been closed"); } } private static class Locator { private final Address address; private volatile Optional client; private volatile LocalDateTime lastChanged; private Locator(Address address) { this.address = address; this.client = Optional.empty(); lastChanged = LocalDateTime.now(); LOGGER.debug( "Locator wrapper '{}' created with no connection at {}", this.label(), lastChanged); } Locator client(Client client) { Client previous = this.nullableClient(); this.client = Optional.ofNullable(client); LocalDateTime now = LocalDateTime.now(); LOGGER.debug( "Locator wrapper '{}' updated from {} to {}, last changed {}, {} ago", this.label(), previous, client, this.lastChanged, Duration.between(this.lastChanged, now)); lastChanged = now; return this; } private boolean isNotSet() { return !this.isSet(); } private boolean isSet() { return this.client.isPresent(); } private Client client() { return this.client.orElseThrow(LocatorNotAvailableException::new); } private Client nullableClient() { return this.client.orElse(null); } private Address address() { return this.address; } private String label() { Client c = this.nullableClient(); if (c == null) { return address.host() + ":" + address.port(); } else { return c.getHost() + ":" + c.getPort(); } } @Override public String toString() { return "Locator{" + "address=" + address + ", client=" + client + '}'; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy