/************************************************************************* * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. ************************************************************************/ package com.eucalyptus.util.async; import java.net.InetSocketAddress; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import com.eucalyptus.ws.StackConfiguration; import com.google.common.base.MoreObjects; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.pool.AbstractChannelPoolHandler; import io.netty.channel.pool.AbstractChannelPoolMap; import io.netty.channel.pool.ChannelHealthChecker; import io.netty.channel.pool.ChannelPoolHandler; import io.netty.channel.pool.FixedChannelPool; import io.netty.channel.pool.SimpleChannelPool; import io.netty.util.AttributeKey; import io.netty.util.concurrent.Future; /** * */ class AsyncRequestChannelPoolMap extends AbstractChannelPoolMap<AsyncRequestChannelPoolMap.ChannelPoolKey, SimpleChannelPool> { private static final AtomicLong CHANNEL_CREATES = new AtomicLong( 0L ); private static final AtomicLong CHANNEL_RELEASES = new AtomicLong( 0L ); private static final AtomicLong CHANNEL_ACQUIRES = new AtomicLong( 0L ); private static final AttributeKey<Long> POOL_RELEASED = AttributeKey.newInstance("asyncReleasedTime"); private static final AttributeKey<Integer> POOL_REQUESTS = AttributeKey.newInstance("asyncRequestCount"); private static final String CHANNEL_REUSE_MAX_IDLE_PROP = "com.eucalyptus.util.async.channelReuseMaxIdle"; private static final String CHANNEL_REUSE_MAX_REQUESTS_PROP = "com.eucalyptus.util.async.channelReuseMaxRequests"; private static long CHANNEL_REUSE_MAX_IDLE = MoreObjects.firstNonNull( Longs.tryParse( System.getProperty( CHANNEL_REUSE_MAX_IDLE_PROP, "" ) ), 45_000L ); private static int CHANNEL_REUSE_MAX_REQUESTS = MoreObjects.firstNonNull( Ints.tryParse( System.getProperty( CHANNEL_REUSE_MAX_REQUESTS_PROP, "" ) ), 75 ); @Override protected SimpleChannelPool newPool( final ChannelPoolKey key ) { final Bootstrap bootstrap = key.bootstrap.remoteAddress( key.address ); final ChannelPoolHandler handler = new AsyncRequestsChannelPoolHandler( key.initializer ); final ChannelHealthChecker checker = new AsyncRequestsChannelHealthChecker( ); if ( key.size <= 0 ) { return new SimpleChannelPool( bootstrap, handler, checker ); } else { return new FixedChannelPool( bootstrap, handler, checker, FixedChannelPool.AcquireTimeoutAction.FAIL, MoreObjects.firstNonNull( StackConfiguration.CLIENT_HTTP_POOL_ACQUIRE_TIMEOUT, 60_000L ), key.size, Integer.MAX_VALUE ); } } @Override public String toString( ) { return MoreObjects.toStringHelper( this ) .add( "size", size( ) ) .add( "channelsCreated", CHANNEL_CREATES.get( ) ) .add( "channelsAcquired", CHANNEL_ACQUIRES.get( ) ) .add( "channelsReleased", CHANNEL_RELEASES.get( ) ) .toString( ); } static final class ChannelPoolKey { private final Bootstrap bootstrap; private final ChannelInitializer<?> initializer; private final InetSocketAddress address; private final int size; ChannelPoolKey( final Bootstrap bootstrap, final ChannelInitializer<?> initializer, final InetSocketAddress address, final int size ) { this.bootstrap = bootstrap; this.initializer = initializer; this.address = address; this.size = size; } @Override public boolean equals( final Object o ) { if ( this == o ) return true; if ( o == null || getClass( ) != o.getClass( ) ) return false; final ChannelPoolKey that = (ChannelPoolKey) o; return size == that.size && Objects.equals( initializer, that.initializer ) && Objects.equals( address, that.address ); } @Override public int hashCode() { return Objects.hash( initializer, address, size ); } @Override public String toString( ) { return MoreObjects.toStringHelper( this ) .add( "address", address ) .add( "initializer", initializer.getClass( ).getSimpleName( ) ) .add( "size", size ) .toString( ); } } private static final class AsyncRequestsChannelPoolHandler extends AbstractChannelPoolHandler { private final ChannelInitializer<?> initializer; AsyncRequestsChannelPoolHandler( final ChannelInitializer<?> initializer ) { this.initializer = initializer; } @Override public void channelCreated( final Channel ch ) { CHANNEL_CREATES.incrementAndGet( ); ch.attr( POOL_RELEASED ).set( System.currentTimeMillis( ) ); ch.attr( POOL_REQUESTS ).set( 0 ); ch.pipeline( ).addLast( initializer ); } @Override public void channelReleased( final Channel ch ) { CHANNEL_RELEASES.incrementAndGet( ); ch.attr( POOL_RELEASED ).set( System.currentTimeMillis( ) ); ch.pipeline( ).remove( AsyncRequestHandler.class ); } @Override public void channelAcquired( final Channel ch ) { CHANNEL_ACQUIRES.incrementAndGet( ); ch.attr( POOL_REQUESTS ).set( ch.attr( POOL_REQUESTS ).get( ) + 1 ); } } private static final class AsyncRequestsChannelHealthChecker implements ChannelHealthChecker { @Override public Future<Boolean> isHealthy( final Channel channel ) { // do not reuse channel if idle too long or has handled too many requests final long timeSinceUsed = System.currentTimeMillis( ) - channel.attr( POOL_RELEASED ).get( ); final int requestCount = channel.attr( POOL_REQUESTS ).get( ); if ( timeSinceUsed > CHANNEL_REUSE_MAX_IDLE || requestCount > CHANNEL_REUSE_MAX_REQUESTS ) { return channel.eventLoop( ).newSucceededFuture( Boolean.FALSE ); } // reuse if active return ChannelHealthChecker.ACTIVE.isHealthy( channel ); } } }