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

com.clickzetta.platform.connection.ChannelManagerImpl Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
package com.clickzetta.platform.connection;

import com.clickzetta.platform.client.RequestStreamObserver;
import com.clickzetta.platform.util.Pair;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.ConnectivityState;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.Tuple2;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class ChannelManagerImpl extends AbstractReconnectSupport implements ChannelManager {

  private static final Logger LOG = LoggerFactory.getLogger(ChannelManagerImpl.class);

  private final Object lock;

  private final List> streamObservers;
  private final List> hostPorts;

  private final AtomicInteger index;

  private final Queue cleanerQueue;
  private ScheduledExecutorService cleanerService;

  protected ChannelManagerImpl() {
    this.lock = new Object();
    this.streamObservers = new ArrayList<>();
    this.hostPorts = new ArrayList<>();
    this.cleanerQueue = new LinkedBlockingQueue<>();
    this.index = new AtomicInteger(0);
  }

  @Override
  public ChannelData getChannel() throws IOException {
    // wait until no reconnect.
    waitOnNoInReconnect();
    synchronized (lock) {
      int rpcIndex = index.getAndUpdate(operand -> operand == streamObservers.size() - 1 ? 0 : operand + 1);
      Pair hostPort = hostPorts.get(rpcIndex);
      RequestStreamObserver observer = null;
      if (rpcIndex < streamObservers.size()) {
        observer = streamObservers.get(rpcIndex);
        observer.retain();
      }
      return ChannelData.of(rpcIndex, hostPort, observer);
    }
  }

  @Override
  public boolean buildChannelInternal(boolean multiEnable, String host, Integer port, InitCallback callback) {
    synchronized (lock) {
      if (multiEnable || !this.hostPorts.contains(new Pair<>(host, port))) {
        ManagedChannel channel = null;
        if (port != null && port != -1) {
          channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext()
              .enableRetry()
              .keepAliveTime(600, TimeUnit.SECONDS)
              .keepAliveTimeout(180, TimeUnit.SECONDS)
              .keepAliveWithoutCalls(true)
              .build();
        } else {
          channel = ManagedChannelBuilder.forTarget(host).usePlaintext()
              .enableRetry()
              .keepAliveTime(600, TimeUnit.SECONDS)
              .keepAliveTimeout(180, TimeUnit.SECONDS)
              .keepAliveWithoutCalls(true)
              .build();
        }
        this.hostPorts.add(new Pair<>(host, port));
        if (callback != null) {
          RequestStreamObserver observer = callback.call(channel);
          if (observer != null) {
            this.streamObservers.add(observer);
          }
        }
        LOG.info("start to buildChannelInternal with {}:{}", host, port);
        return true;
      }
    }
    return false;
  }

  @Override
  public void rebuildChannels(boolean multiEnable, List> hostPorts, InitCallback callback) {
    Preconditions.checkArgument(hostPorts != null && hostPorts.size() > 0,
        "rebuildChannels need hostPorts is not empty.");
    synchronized (lock) {
      if (cleanerService == null) {
        cleanerService = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder()
            .setNameFormat("cleanerService-%d")
            .setDaemon(true).build());
        cleanerService.scheduleAtFixedRate(() -> {
          if (!cleanerQueue.isEmpty()) {
            List> remain = new ArrayList<>();
            while (!cleanerQueue.isEmpty()) {
              ExpireCleaner cleaner = cleanerQueue.poll();
              remain.addAll(cleaner.call());
            }
            if (!remain.isEmpty()) {
              cleanerQueue.offer(new ExpireCleaner(remain));
            }
          }
        }, 30 * 1000, 60 * 1000, TimeUnit.MILLISECONDS);
      }
      this.cleanerQueue.offer(new ExpireCleaner(this.streamObservers));
      this.streamObservers.clear();
      this.hostPorts.clear();
      this.index.set(0);
      for (Tuple2 hostPort : hostPorts) {
        String host = hostPort._1;
        Integer port = hostPort._2;
        if (multiEnable || !this.hostPorts.contains(new Pair<>(host, port))) {
          ManagedChannel channel = null;
          if (port != null && port != -1) {
            channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext()
                .enableRetry()
                .keepAliveTime(600, TimeUnit.SECONDS)
                .keepAliveTimeout(180, TimeUnit.SECONDS)
                .keepAliveWithoutCalls(true)
                .build();
          } else {
            channel = ManagedChannelBuilder.forTarget(host).usePlaintext()
                .enableRetry()
                .keepAliveTime(600, TimeUnit.SECONDS)
                .keepAliveTimeout(180, TimeUnit.SECONDS)
                .keepAliveWithoutCalls(true)
                .build();
          }
          this.hostPorts.add(new Pair<>(host, port));
          if (callback != null) {
            RequestStreamObserver observer = callback.call(channel);
            if (observer != null) {
              this.streamObservers.add(observer);
            }
          }
          LOG.info("start to buildChannelInternal with {}:{}", host, port);
        }
      }
    }
  }

  private final Set activeStateSet = new HashSet() {{
    add(ConnectivityState.CONNECTING);
    add(ConnectivityState.READY);
  }};

  @Override
  public ChannelData validChannelActiveWithMaxRetry(ChannelData channelData, int maxRetry,
                                                       InitCallback callback) throws IOException {
    // touch channel if active.
    AtomicReference throwable = new AtomicReference<>();
    ManagedChannel channel = channelData.streamObserver.getChannel();
    ConnectivityState connectState = channel.getState(false);
    while (!activeStateSet.contains(connectState) && maxRetry > 0) {
      LOG.warn("get uncaught channel connectivity state {}.", connectState);
      synchronized (lock) {
        if (!activeStateSet.contains(connectState = channel.getState(true))) {
          channel.notifyWhenStateChanged(connectState, () -> {
            // lock for async callback.
            synchronized (lock) {
              try {
                RequestStreamObserver observer = callback.call(channel);
                if (observer != null && channelData.rpcIndex < this.streamObservers.size()) {
                  this.streamObservers.get(channelData.rpcIndex).replaceWithNew(observer);
                }
              } catch (Throwable t) {
                throwable.set(t);
              } finally {
                lock.notify();
              }
            }
          });
          try {
            lock.wait(5 * 1000);
          } catch (InterruptedException e) {
            throw new IOException(e);
          }
          if (throwable.get() != null) {
            throw new IOException(throwable.get());
          }
          maxRetry--;
          connectState = channel.getState(false);
        }
      }
    }
    if (!activeStateSet.contains(connectState)) {
      throw new IOException(String.format("valid channel max retry times with connectivity state %s.", connectState));
    }
    if (throwable.get() != null) {
      throw new IOException(throwable.get());
    }
    return channelData;
  }

  private class ExpireCleaner {
    private List> streamObservers = new ArrayList<>();

    public ExpireCleaner(List> streamObservers) {
      this.streamObservers.addAll(streamObservers);
    }

    public List> call() {
      List> remain = new ArrayList<>();
      streamObservers.forEach(observer -> {
        if (observer.refCnt() <= 0) {
          observer.onCancel();
          LOG.info("RequestStreamObserver expire clean success with {}", observer);
        } else {
          remain.add(observer);
        }
      });
      streamObservers.clear();
      streamObservers = null;
      return remain;
    }

    @Override
    public String toString() {
      return "ExpireCleaner{" +
          "streamObservers=" + streamObservers +
          '}';
    }
  }

  @Override
  public void close(long wait_time_ms) {
    LOG.info("ChannelManager close called...");
    super.close(wait_time_ms);
    if (cleanerService != null) {
      cleanerService.shutdown();
      try {
        boolean closed = cleanerService.awaitTermination(wait_time_ms, TimeUnit.MILLISECONDS);
        if (!closed) {
          cleanerService.shutdownNow();
        }
      } catch (Throwable t) {
        // ignore
      }
    }
    for (RequestStreamObserver streamObserver : streamObservers) {
      streamObserver.onCompleted(wait_time_ms);
    }
    for (ExpireCleaner cleaner : cleanerQueue) {
      cleaner.streamObservers.forEach(sob -> sob.onCancel(wait_time_ms));
    }
    this.index.set(0);
    this.streamObservers.clear();
    this.cleanerQueue.clear();
    this.hostPorts.clear();
    LOG.info("ChannelManager close success.");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy