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

org.glowroot.agent.shaded.grpc.internal.ManagedChannelImpl Maven / Gradle / Ivy

There is a newer version: 0.9.24
Show newest version
/*
 * Copyright 2014, Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.glowroot.agent.shaded.grpc.internal;

import static org.glowroot.agent.shaded.grpc.internal.GrpcUtil.TIMER_SERVICE;

import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.Preconditions;
import org.glowroot.agent.shaded.google.common.util.concurrent.Futures;
import org.glowroot.agent.shaded.google.common.util.concurrent.ListenableFuture;

import org.glowroot.agent.shaded.grpc.Attributes;
import org.glowroot.agent.shaded.grpc.CallOptions;
import org.glowroot.agent.shaded.grpc.Channel;
import org.glowroot.agent.shaded.grpc.ClientCall;
import org.glowroot.agent.shaded.grpc.ClientInterceptor;
import org.glowroot.agent.shaded.grpc.ClientInterceptors;
import org.glowroot.agent.shaded.grpc.CompressorRegistry;
import org.glowroot.agent.shaded.grpc.DecompressorRegistry;
import org.glowroot.agent.shaded.grpc.EquivalentAddressGroup;
import org.glowroot.agent.shaded.grpc.LoadBalancer;
import org.glowroot.agent.shaded.grpc.ManagedChannel;
import org.glowroot.agent.shaded.grpc.MethodDescriptor;
import org.glowroot.agent.shaded.grpc.NameResolver;
import org.glowroot.agent.shaded.grpc.ResolvedServerInfo;
import org.glowroot.agent.shaded.grpc.Status;
import org.glowroot.agent.shaded.grpc.TransportManager;
import org.glowroot.agent.shaded.grpc.internal.ClientCallImpl.ClientTransportProvider;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.glowroot.agent.jul.Logger;
import java.util.regex.Pattern;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

/** A communication channel for making outgoing RPCs. */
@ThreadSafe
public final class ManagedChannelImpl extends ManagedChannel {
  private static final Logger log = Logger.getLogger(ManagedChannelImpl.class.getName());

  private static final ListenableFuture NULL_VALUE_TRANSPORT_FUTURE =
        Futures.immediateFuture(null);

  // Matching this pattern means the target string is a URI target or at least intended to be one.
  // A URI target must be an absolute hierarchical URI.
  // From RFC 2396: scheme = alpha *( alpha | digit | "+" | "-" | "." )
  private static final Pattern URI_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-.]*:/.*");

  private final ClientTransportFactory transportFactory;
  private final Executor executor;
  private final boolean usingSharedExecutor;
  private final String userAgent;
  private final Object lock = new Object();

  private final DecompressorRegistry decompressorRegistry;
  private final CompressorRegistry compressorRegistry;

  /**
   * Executor that runs deadline timers for requests.
   */
  private ScheduledExecutorService scheduledExecutor;

  private final BackoffPolicy.Provider backoffPolicyProvider;

  /**
   * We delegate to this channel, so that we can have interceptors as necessary. If there aren't
   * any interceptors this will just be {@link RealChannel}.
   */
  private final Channel interceptorChannel;

  private final NameResolver nameResolver;
  private final LoadBalancer loadBalancer;

  /**
   * Maps EquivalentAddressGroups to transports for that server.
   */
  @GuardedBy("lock")
  private final Map transports =
      new HashMap();

  @GuardedBy("lock")
  private boolean shutdown;
  @GuardedBy("lock")
  private boolean terminated;

  private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
    @Override
    public ListenableFuture get(CallOptions callOptions) {
      synchronized (lock) {
        if (shutdown) {
          return NULL_VALUE_TRANSPORT_FUTURE;
        }
      }
      return loadBalancer.pickTransport(callOptions.getRequestKey());
    }
  };

  ManagedChannelImpl(String target, BackoffPolicy.Provider backoffPolicyProvider,
      NameResolver.Factory nameResolverFactory, Attributes nameResolverParams,
      LoadBalancer.Factory loadBalancerFactory, ClientTransportFactory transportFactory,
      DecompressorRegistry decompressorRegistry, CompressorRegistry compressorRegistry,
      @Nullable Executor executor, @Nullable String userAgent,
      List interceptors) {
    if (executor == null) {
      usingSharedExecutor = true;
      this.executor = SharedResourceHolder.get(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
    } else {
      usingSharedExecutor = false;
      this.executor = executor;
    }
    this.backoffPolicyProvider = backoffPolicyProvider;
    this.nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
    this.loadBalancer = loadBalancerFactory.newLoadBalancer(nameResolver.getServiceAuthority(), tm);
    this.transportFactory = transportFactory;
    this.userAgent = userAgent;
    this.interceptorChannel = ClientInterceptors.intercept(new RealChannel(), interceptors);
    scheduledExecutor = SharedResourceHolder.get(TIMER_SERVICE);
    this.decompressorRegistry = decompressorRegistry;
    this.compressorRegistry = compressorRegistry;

    this.nameResolver.start(new NameResolver.Listener() {
      @Override
      public void onUpdate(List servers, Attributes config) {
        loadBalancer.handleResolvedAddresses(servers, config);
      }

      @Override
      public void onError(Status error) {
        Preconditions.checkArgument(!error.isOk(), "the error status must not be OK");
        loadBalancer.handleNameResolutionError(error);
      }
    });
  }

  @VisibleForTesting
  static NameResolver getNameResolver(String target, NameResolver.Factory nameResolverFactory,
      Attributes nameResolverParams) {
    // Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
    // "dns:///".
    URI targetUri = null;
    StringBuilder uriSyntaxErrors = new StringBuilder();
    try {
      targetUri = new URI(target);
      // For "localhost:8080" this would likely cause newNameResolver to return null, because
      // "localhost" is parsed as the scheme. Will fall into the next branch and try
      // "dns:///localhost:8080".
    } catch (URISyntaxException e) {
      // Can happen with ip addresses like "[::1]:1234" or 127.0.0.1:1234.
      uriSyntaxErrors.append(e.getMessage());
    }
    if (targetUri != null) {
      NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverParams);
      if (resolver != null) {
        return resolver;
      }
      // "foo.googleapis.com:8080" cause resolver to be null, because "foo.googleapis.com" is an
      // unmapped scheme. Just fall through and will try "dns:///foo.googleapis.com:8080"
    }

    // If we reached here, the targetUri couldn't be used.
    if (!URI_PATTERN.matcher(target).matches()) {
      // It doesn't look like a URI target. Maybe it's an authority string. Try with the default
      // scheme from the factory.
      try {
        targetUri = new URI(nameResolverFactory.getDefaultScheme(), null, "/" + target, null);
      } catch (URISyntaxException e) {
        // Should not be possible.
        throw new IllegalArgumentException(e);
      }
      if (targetUri != null) {
        NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverParams);
        if (resolver != null) {
          return resolver;
        }
      }
    }
    throw new IllegalArgumentException(String.format(
        "cannot find a NameResolver for %s%s",
        target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : ""));
  }

  /**
   * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
   * cancelled.
   */
  @Override
  public ManagedChannelImpl shutdown() {
    ArrayList transportsCopy = new ArrayList();
    synchronized (lock) {
      if (shutdown) {
        return this;
      }
      shutdown = true;
      // After shutdown there are no new calls, so no new cancellation tasks are needed
      scheduledExecutor = SharedResourceHolder.release(TIMER_SERVICE, scheduledExecutor);
      if (transports.isEmpty()) {
        terminated = true;
        lock.notifyAll();
        onChannelTerminated();
      } else {
        transportsCopy.addAll(transports.values());
      }
    }
    loadBalancer.shutdown();
    nameResolver.shutdown();
    for (TransportSet ts : transportsCopy) {
      ts.shutdown();
    }
    return this;
  }

  /**
   * Initiates a forceful shutdown in which preexisting and new calls are cancelled. Although
   * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely
   * return {@code false} immediately after this method returns.
   *
   * 

NOT YET IMPLEMENTED. This method currently behaves identically to shutdown(). */ // TODO(ejona86): cancel preexisting calls. @Override public ManagedChannelImpl shutdownNow() { shutdown(); return this; } @Override public boolean isShutdown() { synchronized (lock) { return shutdown; } } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { synchronized (lock) { long timeoutNanos = unit.toNanos(timeout); long endTimeNanos = System.nanoTime() + timeoutNanos; while (!terminated && (timeoutNanos = endTimeNanos - System.nanoTime()) > 0) { TimeUnit.NANOSECONDS.timedWait(lock, timeoutNanos); } return terminated; } } @Override public boolean isTerminated() { synchronized (lock) { return terminated; } } /* * Creates a new outgoing call on the channel. */ @Override public ClientCall newCall(MethodDescriptor method, CallOptions callOptions) { return interceptorChannel.newCall(method, callOptions); } @Override public String authority() { return interceptorChannel.authority(); } private class RealChannel extends Channel { @Override public ClientCall newCall(MethodDescriptor method, CallOptions callOptions) { Executor executor = callOptions.getExecutor(); if (executor == null) { executor = ManagedChannelImpl.this.executor; } return new ClientCallImpl( method, executor, callOptions, transportProvider, scheduledExecutor) .setUserAgent(userAgent) .setDecompressorRegistry(decompressorRegistry) .setCompressorRegistry(compressorRegistry); } @Override public String authority() { String authority = nameResolver.getServiceAuthority(); return Preconditions.checkNotNull(authority, "authority"); } } /** * If we're using the shared executor, returns its reference. */ private void onChannelTerminated() { if (usingSharedExecutor) { SharedResourceHolder.release(GrpcUtil.SHARED_CHANNEL_EXECUTOR, (ExecutorService) executor); } // Release the transport factory so that it can deallocate any resources. transportFactory.release(); } private final TransportManager tm = new TransportManager() { @Override public void updateRetainedTransports(Collection addrs) { // TODO(zhangkun83): warm-up new servers and discard removed servers. } @Override public ListenableFuture getTransport( final EquivalentAddressGroup addressGroup) { Preconditions.checkNotNull(addressGroup, "addressGroup"); TransportSet ts; synchronized (lock) { if (shutdown) { return NULL_VALUE_TRANSPORT_FUTURE; } ts = transports.get(addressGroup); if (ts == null) { ts = new TransportSet(addressGroup, authority(), loadBalancer, backoffPolicyProvider, transportFactory, scheduledExecutor, new TransportSet.Callback() { @Override public void onTerminated() { synchronized (lock) { transports.remove(addressGroup); if (shutdown && transports.isEmpty()) { if (terminated) { log.warning("transportTerminated called after already terminated"); } terminated = true; lock.notifyAll(); onChannelTerminated(); } } } }); transports.put(addressGroup, ts); } } return ts.obtainActiveTransport(); } @Override public Channel makeChannel(ClientTransport transport) { return new SingleTransportChannel( transport, executor, scheduledExecutor, authority()); } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy