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

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

// Copyright (c) 2020-2021 VMware, Inc. or its affiliates.  All rights reserved.
//
// 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.Utils.formatConstant;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.rabbitmq.stream.Address;
import com.rabbitmq.stream.AddressResolver;
import com.rabbitmq.stream.BackOffDelayPolicy;
import com.rabbitmq.stream.Codec;
import com.rabbitmq.stream.ConsumerBuilder;
import com.rabbitmq.stream.Environment;
import com.rabbitmq.stream.MessageHandler;
import com.rabbitmq.stream.MessageHandler.Context;
import com.rabbitmq.stream.OffsetSpecification;
import com.rabbitmq.stream.ProducerBuilder;
import com.rabbitmq.stream.StreamCreator;
import com.rabbitmq.stream.StreamException;
import com.rabbitmq.stream.compression.CompressionCodecFactory;
import com.rabbitmq.stream.impl.OffsetTrackingCoordinator.Registration;
import com.rabbitmq.stream.impl.StreamConsumerBuilder.TrackingConfiguration;
import com.rabbitmq.stream.impl.StreamEnvironmentBuilder.DefaultTlsConfiguration;
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.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
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 Random random = new Random();

  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 volatile Client locator; StreamEnvironment( ScheduledExecutorService scheduledExecutorService, Client.ClientParameters clientParametersPrototype, List uris, BackOffDelayPolicy recoveryBackOffDelayPolicy, BackOffDelayPolicy topologyBackOffDelayPolicy, AddressResolver addressResolver, int maxProducersByConnection, int maxTrackingConsumersByConnection, int maxConsumersByConnection, DefaultTlsConfiguration tlsConfiguration, ByteBufAllocator byteBufAllocator) { this( scheduledExecutorService, clientParametersPrototype, uris, recoveryBackOffDelayPolicy, topologyBackOffDelayPolicy, addressResolver, maxProducersByConnection, maxTrackingConsumersByConnection, maxConsumersByConnection, tlsConfiguration, byteBufAllocator, cp -> new Client(cp)); } StreamEnvironment( ScheduledExecutorService scheduledExecutorService, Client.ClientParameters clientParametersPrototype, List uris, BackOffDelayPolicy recoveryBackOffDelayPolicy, BackOffDelayPolicy topologyBackOffDelayPolicy, AddressResolver addressResolver, int maxProducersByConnection, int maxTrackingConsumersByConnection, int maxConsumersByConnection, DefaultTlsConfiguration tlsConfiguration, ByteBufAllocator byteBufAllocator, Function clientFactory) { this.recoveryBackOffDelayPolicy = recoveryBackOffDelayPolicy; this.topologyUpdateBackOffDelayPolicy = topologyBackOffDelayPolicy; this.byteBufAllocator = byteBufAllocator; clientParametersPrototype.byteBufAllocator(byteBufAllocator); clientParametersPrototype = maybeSetUpClientParametersFromUris(uris, clientParametersPrototype); this.addressResolver = addressResolver; 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()); } 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); } if (scheduledExecutorService == null) { this.scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()); this.privateScheduleExecutorService = true; } else { this.scheduledExecutorService = scheduledExecutorService; this.privateScheduleExecutorService = false; } this.producersCoordinator = new ProducersCoordinator( this, maxProducersByConnection, maxTrackingConsumersByConnection, Utils.coordinatorClientFactory(this)); this.consumersCoordinator = new ConsumersCoordinator( this, maxConsumersByConnection, Utils.coordinatorClientFactory(this)); this.offsetTrackingCoordinator = new OffsetTrackingCoordinator(this); AtomicReference shutdownListenerReference = new AtomicReference<>(); Client.ShutdownListener shutdownListener = shutdownContext -> { if (shutdownContext.isShutdownUnexpected()) { this.locator = null; LOGGER.debug("Unexpected locator disconnection, trying to reconnect"); Client.ClientParameters newLocatorParameters = this.clientParametersPrototype .duplicate() .shutdownListener(shutdownListenerReference.get()); AsyncRetry.asyncRetry( () -> { Address address = addresses.size() == 1 ? addresses.get(0) : addresses.get(random.nextInt(addresses.size())); address = addressResolver.resolve(address); LOGGER.debug("Trying to reconnect locator on {}", address); Client newLocator = clientFactory.apply( newLocatorParameters .host(address.host()) .port(address.port()) .clientProperty("connection_name", "rabbitmq-stream-locator")); LOGGER.debug("Locator connected on {}", address); return newLocator; }) .description("Locator recovery") .scheduler(this.scheduledExecutorService) .delayPolicy(recoveryBackOffDelayPolicy) .build() .thenAccept(newLocator -> this.locator = newLocator); } }; shutdownListenerReference.set(shutdownListener); RuntimeException lastException = null; for (Address address : addresses) { address = addressResolver.resolve(address); Client.ClientParameters locatorParameters = clientParametersPrototype .duplicate() .host(address.host()) .port(address.port()) .clientProperty("connection_name", "rabbitmq-stream-locator") .shutdownListener(shutdownListenerReference.get()); try { this.locator = clientFactory.apply(locatorParameters); LOGGER.debug("Locator connected to {}", address); break; } catch (RuntimeException e) { LOGGER.debug("Error while try to connect to {}: {}", address, e.getMessage()); lastException = e; } } if (this.locator == null) { throw lastException; } this.codec = locator.codec(); this.clockRefreshFuture = this.scheduledExecutorService.scheduleAtFixedRate( () -> this.clock.refresh(), 1, 1, SECONDS); } 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; } @Override public StreamCreator streamCreator() { return new StreamStreamCreator(this); } @Override public void deleteStream(String stream) { 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 ProducerBuilder producerBuilder() { 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() { 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(); try { if (this.locator != null && this.locator.isOpen()) { this.locator.close(); this.locator = null; } } catch (Exception e) { LOGGER.warn("Error while closing locator client", e); } 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; } BackOffDelayPolicy recoveryBackOffDelayPolicy() { return this.recoveryBackOffDelayPolicy; } BackOffDelayPolicy topologyUpdateBackOffDelayPolicy() { return this.topologyUpdateBackOffDelayPolicy; } CompressionCodecFactory compressionCodecFactory() { return this.clientParametersPrototype.compressionCodecFactory; } Runnable registerConsumer( StreamConsumer consumer, String stream, OffsetSpecification offsetSpecification, String trackingReference, MessageHandler messageHandler) { Runnable closingCallback = this.consumersCoordinator.subscribe( consumer, stream, offsetSpecification, trackingReference, messageHandler); return closingCallback; } Runnable registerProducer(StreamProducer producer, String reference, String stream) { return producersCoordinator.registerProducer(producer, reference, stream); } // FIXME make the locator available as a completable future (with retry) // this would make client code more robust Client locator() { if (this.locator == null) { throw new StreamException("No connection available"); } return this.locator; } Clock clock() { return this.clock; } AddressResolver addressResolver() { return this.addressResolver; } Codec codec() { return this.codec; } Client.ClientParameters clientParametersCopy() { return this.clientParametersPrototype.duplicate(); } TrackingConsumerRegistration registerTrackingConsumer( StreamConsumer streamConsumer, TrackingConfiguration configuration) { Runnable closingCallable = this.producersCoordinator.registerTrackingConsumer(streamConsumer); Registration offsetTrackingRegistration = null; if (this.offsetTrackingCoordinator.needTrackingRegistration(configuration)) { offsetTrackingRegistration = this.offsetTrackingCoordinator.registerTrackingConsumer(streamConsumer, configuration); } return new TrackingConsumerRegistration( closingCallable, offsetTrackingRegistration == null ? null : offsetTrackingRegistration.postMessageProcessingCallback(), offsetTrackingRegistration == null ? Utils.NO_OP_LONG_CONSUMER : offsetTrackingRegistration.trackingCallback()); } @Override public String toString() { Client locator = this.locator; return "{ locator : " + (locator == null ? "null" : ("'" + locator.getHost() + ":" + locator.getPort() + "'")) + ", " + "'producers' : " + this.producersCoordinator + ", 'consumers' : " + this.consumersCoordinator + "}"; } static class TrackingConsumerRegistration { private final Runnable closingCallback; private final Consumer postMessageProcessingCallback; private final LongConsumer trackingCallback; TrackingConsumerRegistration( Runnable closingCallback, Consumer postMessageProcessingCallback, LongConsumer trackingCallback) { this.closingCallback = closingCallback; this.postMessageProcessingCallback = postMessageProcessingCallback; this.trackingCallback = trackingCallback; } public Runnable closingCallback() { return closingCallback; } public LongConsumer trackingCallback() { return trackingCallback; } public Consumer postMessageProcessingCallback() { return postMessageProcessingCallback; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy