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

org.apache.druid.messages.server.OutboxImpl Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.druid.messages.server;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import org.apache.druid.common.guava.FutureBox;
import org.apache.druid.common.guava.FutureUtils;
import org.apache.druid.java.util.common.Pair;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.messages.MessageBatch;
import org.apache.druid.messages.client.MessageRelay;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Production implementation of {@link Outbox}. Each outbox is represented by an {@link OutboxQueue}.
 */
public class OutboxImpl implements Outbox
{
  private static final int MAX_BATCH_SIZE = 8;

  // clientHost -> outgoing message queue
  private final ConcurrentHashMap> queues;
  private volatile boolean stopped;

  public OutboxImpl()
  {
    this.queues = new ConcurrentHashMap<>();
  }

  @LifecycleStop
  public void stop()
  {
    stopped = true;

    final Iterator> it = queues.values().iterator();
    while (it.hasNext()) {
      it.next().stop();
      it.remove();
    }
  }

  @Override
  public ListenableFuture sendMessage(String clientHost, MessageType message)
  {
    if (stopped) {
      return Futures.immediateCancelledFuture();
    }

    return queues.computeIfAbsent(clientHost, id -> new OutboxQueue<>())
                 .sendMessage(message);
  }

  @Override
  public ListenableFuture> getMessages(String clientHost, long epoch, long startWatermark)
  {
    if (stopped) {
      return Futures.immediateCancelledFuture();
    }

    final OutboxQueue queue = queues.computeIfAbsent(clientHost, id -> new OutboxQueue<>());
    if (epoch != queue.epoch && epoch != MessageRelay.INIT) {
      return Futures.immediateFuture(new MessageBatch<>(Collections.emptyList(), queue.epoch, 0));
    }

    return queue.getMessages(startWatermark);
  }

  @Override
  public void resetOutbox(final String clientHost)
  {
    final OutboxQueue queue = queues.remove(clientHost);
    if (queue != null) {
      queue.stop();
    }
  }

  @VisibleForTesting
  long getOutboxEpoch(final String clientHost)
  {
    final OutboxQueue queue = queues.get(clientHost);
    return queue != null ? queue.epoch : MessageRelay.INIT;
  }

  /**
   * Outgoing queue for a specific client.
   */
  public static class OutboxQueue
  {
    /**
     * Epoch, set when the outbox is created. Attached to returned batches through {@link MessageBatch#getEpoch()}.
     */
    private final long epoch;

    /**
     * Currently-outstanding futures.
     */
    private final FutureBox pendingFutures = new FutureBox();

    @GuardedBy("this")
    private long startWatermark = 0;

    @GuardedBy("this")
    private final Deque, T>> queue = new ArrayDeque<>();

    @GuardedBy("this")
    private SettableFuture messageAvailableFuture = SettableFuture.create();

    public OutboxQueue()
    {
      // Random positive number, to differentiate this outbox from a previous version that may have lived
      // on the same host. (When the upstream relay connects, it needs to know if this is the "same" outbox
      // it was previously listening to.)
      this.epoch = ThreadLocalRandom.current().nextLong() & Long.MAX_VALUE;
    }

    ListenableFuture sendMessage(final T message)
    {
      final SettableFuture future = SettableFuture.create();

      synchronized (this) {
        queue.add(Pair.of(future, message));
        if (!messageAvailableFuture.isDone()) {
          messageAvailableFuture.set(null);
        }
      }

      return pendingFutures.register(future);
    }

    ListenableFuture> getMessages(final long newStartWatermark)
    {
      synchronized (this) {
        // Ack and drain all messages up to startWatermark.
        while (!queue.isEmpty() && startWatermark < newStartWatermark) {
          final Pair, T> message = queue.poll();
          startWatermark++;
          message.lhs.set(null);
        }

        if (queue.isEmpty()) {
          // Send next batch when a message is available.
          if (messageAvailableFuture.isDone()) {
            messageAvailableFuture = SettableFuture.create();
          }

          return pendingFutures.register(
              FutureUtils.transform(
                  Futures.nonCancellationPropagating(messageAvailableFuture),
                  ignored -> {
                    synchronized (this) {
                      return nextBatch();
                    }
                  }
              )
          );
        } else {
          return pendingFutures.register(Futures.immediateFuture(nextBatch()));
        }
      }
    }

    void stop()
    {
      pendingFutures.close();
    }

    @GuardedBy("this")
    private MessageBatch nextBatch()
    {
      final List batch = new ArrayList<>();
      final Iterator, T>> it = queue.iterator();

      while (it.hasNext() && batch.size() < MAX_BATCH_SIZE) {
        batch.add(it.next().rhs);
      }

      return new MessageBatch<>(batch, epoch, startWatermark);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy