com.linecorp.armeria.client.pool.DefaultKeyedChannelPool.java Source code

Java tutorial

Introduction

Here is the source code for com.linecorp.armeria.client.pool.DefaultKeyedChannelPool.java

Source

/*
 * Copyright 2015 LINE Corporation
 *
 * LINE Corporation 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 com.linecorp.armeria.client.pool;

import static java.util.Objects.requireNonNull;

import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Function;

import com.linecorp.armeria.common.util.Exceptions;

import io.netty.channel.Channel;
import io.netty.channel.EventLoop;
import io.netty.channel.pool.ChannelHealthChecker;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;

/**
 * Default {@link KeyedChannelPool} implementation.
 */
public class DefaultKeyedChannelPool<K> implements KeyedChannelPool<K> {

    private static final IllegalStateException FULL_EXCEPTION = Exceptions
            .clearTrace(new IllegalStateException("ChannelPool full"));

    private static final IllegalStateException UNHEALTHY_NON_OFFERED_TO_POOL = Exceptions
            .clearTrace(new IllegalStateException("Channel is unhealthy; not offering it back to pool"));

    private final EventLoop eventLoop;
    private final Function<K, Future<Channel>> channelFactory;
    private final ChannelHealthChecker healthCheck;
    private final KeyedChannelPoolHandler<K> channelPoolHandler;
    private final boolean releaseHealthCheck;

    private final Map<K, Deque<Channel>> pool;

    /**
     * Creates a new instance.
     */
    public DefaultKeyedChannelPool(EventLoop eventLoop, Function<K, Future<Channel>> channelFactory,
            KeyedChannelPoolHandler<K> channelPoolHandler) {
        this(eventLoop, channelFactory, ChannelHealthChecker.ACTIVE, channelPoolHandler, true);
    }

    /**
     * Creates a new instance.
     */
    public DefaultKeyedChannelPool(EventLoop eventLoop, Function<K, Future<Channel>> channelFactory,
            ChannelHealthChecker healthCheck, KeyedChannelPoolHandler<K> channelPoolHandler) {
        this(eventLoop, channelFactory, healthCheck, channelPoolHandler, true);
    }

    /**
     * Creates a new instance.
     */
    public DefaultKeyedChannelPool(EventLoop eventLoop, Function<K, Future<Channel>> channelFactory,
            ChannelHealthChecker healthCheck, KeyedChannelPoolHandler<K> channelPoolHandler,
            boolean releaseHealthCheck) {
        this.eventLoop = requireNonNull(eventLoop, "eventLoop");
        this.channelFactory = requireNonNull(channelFactory, "channelFactory");
        this.healthCheck = requireNonNull(healthCheck, "healthCheck");
        this.channelPoolHandler = new SafeKeyedChannelPoolHandler<>(
                requireNonNull(channelPoolHandler, "channelPoolHandler"));
        this.releaseHealthCheck = releaseHealthCheck;

        pool = new ConcurrentHashMap<>();
    }

    @Override
    public Future<Channel> acquire(K key) {
        return acquire(key, eventLoop.newPromise());
    }

    @Override
    public Future<Channel> acquire(final K key, final Promise<Channel> promise) {
        requireNonNull(key, "key");
        requireNonNull(promise, "promise");

        if (eventLoop.inEventLoop()) {
            acquireHealthyFromPoolOrNew(key, promise);
        } else {
            eventLoop.execute(() -> acquireHealthyFromPoolOrNew(key, promise));
        }

        return promise;
    }

    private Future<Channel> acquireHealthyFromPoolOrNew(final K key, final Promise<Channel> promise) {
        final Deque<Channel> queue = pool.get(key);
        final Channel ch = queue == null ? null : queue.poll();

        if (ch == null) {
            Future<Channel> f = channelFactory.apply(key);
            if (f.isDone()) {
                notifyConnect(key, f, promise);
            } else {
                f.addListener((Future<Channel> future) -> notifyConnect(key, future, promise));
            }
            return promise;
        }

        EventLoop loop = ch.eventLoop();
        if (loop.inEventLoop()) {
            doHealthCheck(key, ch, promise);
        } else {
            loop.execute(() -> doHealthCheck(key, ch, promise));
        }

        return promise;
    }

    private void notifyConnect(K key, Future<Channel> future, Promise<Channel> promise) {
        assert future.isDone();

        try {
            if (future.isSuccess()) {
                Channel channel = future.getNow();
                channel.attr(KeyedChannelPoolUtil.POOL).set(this);
                channelPoolHandler.channelCreated(key, channel);
                channel.closeFuture().addListener(f -> channelPoolHandler.channelClosed(key, channel));
                promise.setSuccess(channel);
            } else {
                promise.setFailure(future.cause());
            }
        } catch (Exception e) {
            promise.setFailure(e);
        }
    }

    private void doHealthCheck(final K key, final Channel ch, final Promise<Channel> promise) {
        assert ch.eventLoop().inEventLoop();

        Future<Boolean> f = healthCheck.isHealthy(ch);
        if (f.isDone()) {
            notifyHealthCheck(key, f, ch, promise);
        } else {
            f.addListener((FutureListener<Boolean>) future -> notifyHealthCheck(key, future, ch, promise));
        }
    }

    private void notifyHealthCheck(final K key, Future<Boolean> future, Channel ch, Promise<Channel> promise) {
        assert ch.eventLoop().inEventLoop();

        if (future.isSuccess()) {
            if (future.getNow() == Boolean.TRUE) {
                try {
                    ch.attr(KeyedChannelPoolUtil.POOL).set(this);
                    channelPoolHandler.channelAcquired(key, ch);
                    promise.setSuccess(ch);
                } catch (Throwable cause) {
                    closeAndFail(ch, cause, promise);
                }
            } else {
                closeChannel(ch);
                acquireHealthyFromPoolOrNew(key, promise);
            }
        } else {
            closeChannel(ch);
            acquireHealthyFromPoolOrNew(key, promise);
        }
    }

    private static void closeChannel(Channel channel) {
        channel.attr(KeyedChannelPoolUtil.POOL).set(null);
        channel.close();
    }

    private static void closeAndFail(Channel channel, Throwable cause, Promise<?> promise) {
        closeChannel(channel);
        promise.setFailure(cause);
    }

    @Override
    public Future<Void> release(K key, Channel channel) {
        return release(key, channel, eventLoop.newPromise());
    }

    @Override
    public Future<Void> release(final K key, final Channel channel, final Promise<Void> promise) {
        requireNonNull(key, "key");
        requireNonNull(channel, "channel");
        requireNonNull(promise, "promise");

        try {
            EventLoop loop = channel.eventLoop();
            if (loop.inEventLoop()) {
                doReleaseChannel(key, channel, promise);
            } else {
                loop.execute(() -> doReleaseChannel(key, channel, promise));
            }
        } catch (Throwable cause) {
            closeAndFail(channel, cause, promise);
        }
        return promise;
    }

    private void doReleaseChannel(K key, Channel channel, Promise<Void> promise) {
        assert channel.eventLoop().inEventLoop();
        if (channel.attr(KeyedChannelPoolUtil.POOL).getAndSet(null) != this) {
            // Better include a stracktrace here as this is an user error.
            closeAndFail(channel,
                    new IllegalArgumentException("Channel " + channel + " was not acquired from this ChannelPool"),
                    promise);
        } else {
            try {
                if (releaseHealthCheck) {
                    doHealthCheckOnRelease(key, channel, promise);
                } else {
                    releaseAndOffer(key, channel, promise);
                }
            } catch (Throwable cause) {
                closeAndFail(channel, cause, promise);
            }
        }
    }

    private void doHealthCheckOnRelease(K key, final Channel channel, final Promise<Void> promise)
            throws Exception {
        final Future<Boolean> f = healthCheck.isHealthy(channel);
        if (f.isDone()) {
            releaseAndOfferIfHealthy(key, channel, promise, f);
        } else {
            f.addListener(future -> releaseAndOfferIfHealthy(key, channel, promise, f));
        }
    }

    private void releaseAndOfferIfHealthy(K key, Channel channel, Promise<Void> promise, Future<Boolean> future)
            throws Exception {
        if (future.getNow()) { //channel turns out to be healthy, offering and releasing it.
            releaseAndOffer(key, channel, promise);
        } else { //channel ont healthy, just releasing it.
            channelPoolHandler.channelReleased(key, channel);
            closeAndFail(channel, UNHEALTHY_NON_OFFERED_TO_POOL, promise);
        }
    }

    private void releaseAndOffer(K key, Channel channel, Promise<Void> promise) throws Exception {
        if (offerChannel(key, channel)) {
            channelPoolHandler.channelReleased(key, channel);
            promise.setSuccess(null);
        } else {
            closeAndFail(channel, FULL_EXCEPTION, promise);
        }
    }

    protected Channel pollChannel(K key) {
        final Deque<Channel> queue = pool.get(key);
        final Channel ch;
        if (queue == null) {
            ch = null;
        } else {
            ch = queue.poll();
            if (queue.isEmpty()) {
                pool.remove(key);
            }
        }
        return ch;
    }

    protected boolean offerChannel(K key, Channel channel) {
        return pool.computeIfAbsent(key, k -> new ConcurrentLinkedDeque<>()).offer(channel);
    }

    @Override
    public void close() {
        pool.forEach((k, v) -> {
            for (;;) {
                Channel channel = pollChannel(k);
                if (channel == null) {
                    break;
                }

                if (channel.isOpen()) {
                    channel.close();
                }
            }
        });
    }
}