/** * Copyright 2016 Yahoo Inc. * * 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.yahoo.pulsar.client.impl; import static com.google.common.base.Preconditions.checkArgument; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.yahoo.pulsar.client.api.Message; import com.yahoo.pulsar.client.api.MessageId; import com.yahoo.pulsar.client.api.MessageRouter; import com.yahoo.pulsar.client.api.Producer; import com.yahoo.pulsar.client.api.ProducerConfiguration; import com.yahoo.pulsar.client.api.PulsarClientException; import com.yahoo.pulsar.client.util.FutureUtil; import com.yahoo.pulsar.common.naming.DestinationName; public class PartitionedProducerImpl extends ProducerBase { private List<ProducerImpl> producers; private int numPartitions; private MessageRouter routerPolicy; private final ProducerStats stats; public PartitionedProducerImpl(PulsarClientImpl client, String topic, ProducerConfiguration conf, int numPartitions, CompletableFuture<Producer> producerCreatedFuture) { super(client, topic, conf, producerCreatedFuture); this.producers = Lists.newArrayListWithCapacity(numPartitions); this.numPartitions = numPartitions; this.routerPolicy = conf.getMessageRouter(numPartitions); stats = client.getConfiguration().getStatsIntervalSeconds() > 0 ? new ProducerStats() : null; start(); } private void start() { AtomicReference<Throwable> createFail = new AtomicReference<Throwable>(); AtomicInteger completed = new AtomicInteger(); for (int partitionIndex = 0; partitionIndex < numPartitions; partitionIndex++) { String partitionName = DestinationName.get(topic).getPartition(partitionIndex).toString(); ProducerImpl producer = new ProducerImpl(client, partitionName, null, conf, new CompletableFuture<Producer>(), partitionIndex); producers.add(producer); producer.producerCreatedFuture().handle((prod, createException) -> { if (createException != null) { setState(State.Failed); createFail.compareAndSet(null, createException); } // we mark success if all the partitions are created // successfully, else we throw an exception // due to any // failure in one of the partitions and close the successfully // created partitions if (completed.incrementAndGet() == numPartitions) { if (createFail.get() == null) { setState(State.Ready); producerCreatedFuture().complete(PartitionedProducerImpl.this); log.info("[{}] Created partitioned producer", topic); } else { closeAsync().handle((ok, closeException) -> { producerCreatedFuture().completeExceptionally(createFail.get()); client.cleanupProducer(this); return null; }); log.error("[{}] Could not create partitioned producer.", topic, createFail.get().getCause()); } } return null; }); } } @Override public CompletableFuture<MessageId> sendAsync(Message message) { switch (getState()) { case Ready: case Connecting: break; // Ok case Closing: case Closed: return FutureUtil.failedFuture(new PulsarClientException.AlreadyClosedException("Producer already closed")); case Failed: case Uninitialized: return FutureUtil.failedFuture(new PulsarClientException.NotConnectedException()); } int partition = routerPolicy.choosePartition(message); checkArgument(partition >= 0 && partition < numPartitions, "Illegal partition index chosen by the message routing policy"); return producers.get(partition).sendAsync(message); } @Override public boolean isConnected() { for (ProducerImpl producer : producers) { // returns false if any of the partition is not connected if (!producer.isConnected()) { return false; } } return true; } @Override public CompletableFuture<Void> closeAsync() { if (getState() == State.Closing || getState() == State.Closed) { return CompletableFuture.completedFuture(null); } setState(State.Closing); AtomicReference<Throwable> closeFail = new AtomicReference<Throwable>(); AtomicInteger completed = new AtomicInteger(numPartitions); CompletableFuture<Void> closeFuture = new CompletableFuture<>(); for (Producer producer : producers) { if (producer != null) { producer.closeAsync().handle((closed, ex) -> { if (ex != null) { closeFail.compareAndSet(null, ex); } if (completed.decrementAndGet() == 0) { if (closeFail.get() == null) { setState(State.Closed); closeFuture.complete(null); log.info("[{}] Closed Partitioned Producer", topic); client.cleanupProducer(this); } else { setState(State.Failed); closeFuture.completeExceptionally(closeFail.get()); log.error("[{}] Could not close Partitioned Producer", topic, closeFail.get().getCause()); } } return null; }); } } return closeFuture; } @Override public synchronized ProducerStats getStats() { if (stats == null) { return null; } stats.reset(); for (int i = 0; i < numPartitions; i++) { stats.updateCumulativeStats(producers.get(i).getStats()); } return stats; } private static final Logger log = LoggerFactory.getLogger(PartitionedProducerImpl.class); @Override void connectionFailed(PulsarClientException exception) { // noop } @Override void connectionOpened(ClientCnx cnx) { // noop } @Override String getHandlerName() { return "partition-producer"; } }