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

io.gatling.http.client.pool.ChannelPool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2011-2024 GatlingCorp (https://gatling.io)
 *
 * 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 io.gatling.http.client.pool;

import static io.gatling.http.client.util.Assertions.assertNotNull;

import io.gatling.http.client.impl.DefaultHttpClient;
import io.netty.channel.Channel;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class ChannelPool {

  private static final Logger LOGGER = LoggerFactory.getLogger(ChannelPool.class);

  private static final AttributeKey CHANNEL_POOL_KEY =
      AttributeKey.valueOf("poolKey");
  private static final AttributeKey CHANNEL_TOUCH_TIMESTAMP =
      AttributeKey.valueOf("idleTimestamp");
  private static final AttributeKey CHANNEL_HTTP2_CONNEXION =
      AttributeKey.valueOf("http2Connection");
  private static final AttributeKey HTTP2_POOLED = AttributeKey.valueOf("http2Pooled");
  private static final AttributeKey CHANNEL_GOAWAY = AttributeKey.valueOf("goAway");

  static final int INITIAL_CLIENT_MAP_SIZE = 1000;
  static final int INITIAL_KEY_PER_CLIENT_MAP_SIZE = 2;
  static final int INITIAL_CHANNEL_QUEUE_SIZE = 2;

  private final Map>> channels =
      new HashMap<>(INITIAL_CLIENT_MAP_SIZE);
  private final CoalescingChannelPool coalescingChannelPool = new CoalescingChannelPool();

  private Queue remoteChannels(ChannelPoolKey key) {
    return channels
        .computeIfAbsent(key.clientId, k -> new HashMap<>(INITIAL_KEY_PER_CLIENT_MAP_SIZE))
        .computeIfAbsent(key.remoteKey, k -> new ArrayDeque<>(INITIAL_CHANNEL_QUEUE_SIZE));
  }

  private static boolean isHttp1(Channel channel) {
    return channel.pipeline().get(DefaultHttpClient.APP_HTTP_HANDLER) != null;
  }

  public static boolean isHttp2(Channel channel) {
    return !isHttp1(channel);
  }

  ////////////////////////////// CHANNEL_POOL_KEY
  public static void registerPoolKey(Channel channel, ChannelPoolKey key) {
    channel.attr(CHANNEL_POOL_KEY).set(key);
  }

  ////////////////////////////// CHANNEL_IDLE_TIMESTAMP
  private static void touch(Channel channel) {
    channel.attr(CHANNEL_TOUCH_TIMESTAMP).set(System.nanoTime());
  }

  private static boolean isLastTouchTooOld(Channel channel, long now, long idleTimeoutNanos) {
    return now - channel.attr(CHANNEL_TOUCH_TIMESTAMP).get() > idleTimeoutNanos;
  }

  ////////////////////////////// CHANNEL_HTTP2_CONNEXION
  public static void registerHttp2Connection(Channel channel, Http2Connection http2Connection) {
    channel.attr(CHANNEL_HTTP2_CONNEXION).set(http2Connection);
  }

  private static Http2Connection getHttp2Connection(Channel channel) {
    return channel.attr(CHANNEL_HTTP2_CONNEXION).get();
  }

  private static boolean canOpenStream(Channel channel) {
    return getHttp2Connection(channel).local().canOpenStream();
  }

  ////////////////////////////// CHANNEL_GOAWAY
  public static void markAsGoAway(Channel channel) {
    channel.attr(CHANNEL_GOAWAY).set(Boolean.TRUE);
  }

  private static boolean isNotGoAway(Channel channel) {
    return !channel.hasAttr(CHANNEL_GOAWAY);
  }

  public Channel poll(ChannelPoolKey key) {
    Queue channels = remoteChannels(key);

    Iterator it = channels.iterator();

    while (it.hasNext()) {
      Channel channel = it.next();

      if (!channel.isActive()) {
        it.remove();
        break;
      } else if (isHttp1(channel)) {
        it.remove();
        LOGGER.debug("Retrieved HTTP/1 channel from pool for key {}", key);
        return channel;
      } else if (isNotGoAway(channel) && canOpenStream(channel)) {
        LOGGER.debug("Retrieved HTTP/2 channel from pool for key {}", key);
        touch(channel);
        return channel;
      }
    }

    LOGGER.debug("No channel in the pool for key {}", key);
    return null;
  }

  public Channel pollCoalescedChannel(
      long clientId, String domain, List addresses) {
    Channel channel =
        coalescingChannelPool.getCoalescedChannel(
            clientId,
            domain,
            addresses,
            chan -> ChannelPool.isNotGoAway(chan) && ChannelPool.canOpenStream(chan));
    if (channel != null) {
      LOGGER.debug("Retrieved channel from coalescing pool for domain {}", domain);
    }
    return channel;
  }

  public void offerCoalescedChannel(
      Set subjectAlternativeNames,
      InetSocketAddress address,
      Channel channel,
      ChannelPoolKey key) {
    IpAndPort ipAndPort = new IpAndPort(address.getAddress().getAddress(), address.getPort());
    LOGGER.debug(
        "Offering channel entry {} with subjectAlternativeNames {} to coalescing pool",
        ipAndPort,
        subjectAlternativeNames);
    coalescingChannelPool.addEntry(key.clientId, ipAndPort, subjectAlternativeNames, channel);
  }

  public void offer(Channel channel) {
    ChannelPoolKey key = channel.attr(CHANNEL_POOL_KEY).get();
    assertNotNull(key, "Channel doesn't have a key");
    touch(channel);

    LOGGER.debug("Offering channel entry {} to pool", key);

    if (isHttp1(channel)) {
      remoteChannels(key).offer(channel);
    } else if (!channel.hasAttr(HTTP2_POOLED)) {
      channel.attr(HTTP2_POOLED).set(Boolean.TRUE);
      // we never remove from the queue, so we only offer the first time
      remoteChannels(key).offer(channel);
    }
  }

  public void closeIdleChannels(long idleTimeoutNanos) {
    long now = System.nanoTime();
    for (Map.Entry>> clientEntry : channels.entrySet()) {
      for (Map.Entry> entry : clientEntry.getValue().entrySet()) {
        Queue deque = entry.getValue();
        for (Channel channel : deque) {
          boolean http2 = isHttp2(channel);
          if (isLastTouchTooOld(channel, now, idleTimeoutNanos)
              && (!http2 || getHttp2Connection(channel).numActiveStreams() == 0)) {
            channel.close();
            deque.remove(channel);
            if (http2) {
              coalescingChannelPool.deleteIdleEntry(clientEntry.getKey(), channel);
            }
          }
        }
      }
    }
  }

  public void flushClientIdChannelPoolPartitions(long clientId) {
    Map> clientChannel = channels.get(clientId);
    if (clientChannel != null) {
      clientChannel.entrySet().stream().flatMap(e -> e.getValue().stream()).forEach(Channel::close);
      channels.remove(clientId);
      coalescingChannelPool.deleteClientEntries(clientId);
    }
  }

  @Override
  public String toString() {
    return "ChannelPool{"
        + "channels="
        + channels
        + ", coalescingChannelPool="
        + coalescingChannelPool
        + '}';
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy