com.eucalyptus.util.async.AsyncRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.util.async.AsyncRequestHandler.java

Source

/*************************************************************************
 * Copyright 2009-2012 Eucalyptus Systems, Inc.
 *
 * 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/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 *
 * This file may incorporate work covered under the following copyright
 * and permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software 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.
 *
 *   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. USERS OF THIS SOFTWARE ACKNOWLEDGE
 *   THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
 *   COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
 *   AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
 *   SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
 *   WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
 *   REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
 *   IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
 *   NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
 ************************************************************************/

package com.eucalyptus.util.async;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.ServiceUris;
import com.eucalyptus.component.Topology;
import com.eucalyptus.records.EventClass;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.WildcardNameMatcher;
import com.eucalyptus.util.async.AsyncRequestChannelPoolMap.ChannelPoolKey;
import com.eucalyptus.ws.EucalyptusRemoteFault;
import com.eucalyptus.ws.IoMessage;
import com.eucalyptus.ws.StackConfiguration;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.pool.ChannelPool;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

/**
 * @author decker
 * @param <Q>
 * @param <R>
 */
public class AsyncRequestHandler<Q extends BaseMessage, R extends BaseMessage> extends ChannelInboundHandlerAdapter
        implements RequestHandler<Q, R> {
    private static final Logger LOG = Logger.getLogger(AsyncRequestHandler.class);
    private static final Logger MESSAGE_LOG = Logger.getLogger("com.eucalyptus.client.MessageLogger");
    private static final WildcardNameMatcher MATCHER = new WildcardNameMatcher();
    private static final AsyncRequestChannelPoolMap POOL_MAP = new AsyncRequestChannelPoolMap();

    private final AsyncRequest<Q, R> parent;
    private final AtomicBoolean writeComplete = new AtomicBoolean(false);
    private final AtomicBoolean channelReleased = new AtomicBoolean(false);
    private final AtomicReference<Q> request = new AtomicReference<>(null);
    private final CheckedListenableFuture<R> response;

    private volatile ChannelPool channelPool;
    private volatile Future<Channel> acquireFuture;

    AsyncRequestHandler(final AsyncRequest<Q, R> parent, final CheckedListenableFuture<R> response) {
        super();
        this.parent = parent;
        this.response = response;
    }

    /**
     *
     */
    @Override
    public boolean fire(final ServiceConfiguration config, final Q request) {
        if (!this.request.compareAndSet(null, request)) {
            LOG.warn("Duplicate write attempt for request: " + this.request.get().getClass().getSimpleName());
            return false;
        } else {
            try {
                final InetSocketAddress serviceSocketAddress = config.getSocketAddress();
                final Bootstrap clientBootstrap = config.getComponentId().getClientBootstrap();
                final ChannelInitializer<?> initializer = config.getComponentId().getClientChannelInitializer();
                final int poolSizeLimit = initializer instanceof AsyncRequestPoolable
                        ? ((AsyncRequestPoolable) initializer).fixedSize()
                        : -1;
                final IoMessage<FullHttpRequest> ioMessage = IoMessage.httpRequest(ServiceUris.internal(config),
                        this.request.get());
                final ChannelPoolKey poolKey = new ChannelPoolKey(clientBootstrap, initializer,
                        serviceSocketAddress, poolSizeLimit);
                final long before = System.currentTimeMillis();
                this.channelPool = POOL_MAP.get(poolKey);
                this.acquireFuture = channelPool.acquire();
                this.acquireFuture.addListener(new GenericFutureListener<Future<Channel>>() {
                    @Override
                    public void operationComplete(final Future<Channel> future) throws Exception {
                        try {
                            if (future.isSuccess()) {
                                final Channel channel = future.get();
                                logAcquired(channel, before);
                                channel.pipeline().addLast("request-handler", AsyncRequestHandler.this);

                                if (!initializer.getClass().getSimpleName().startsWith("GatherLog")) {
                                    Topology.populateServices(config, AsyncRequestHandler.this.request.get());
                                }

                                logMessage(ioMessage);

                                channel.writeAndFlush(ioMessage).addListener(new ChannelFutureListener() {
                                    @Override
                                    public void operationComplete(final ChannelFuture future) throws Exception {
                                        AsyncRequestHandler.this.writeComplete.set(true);

                                        Logs.extreme()
                                                .debug(EventRecord.here(request.getClass(),
                                                        EventClass.SYSTEM_REQUEST, EventType.CHANNEL_WRITE,
                                                        request.getClass().getSimpleName(),
                                                        request.getCorrelationId(), serviceSocketAddress.toString(),
                                                        "" + future.channel().localAddress(),
                                                        "" + future.channel().remoteAddress()));
                                    }
                                });
                            } else {
                                AsyncRequestHandler.this.teardown(future.cause());
                            }
                        } catch (final Exception ex) {
                            LOG.error(ex, ex);
                            AsyncRequestHandler.this.teardown(ex);
                        }
                    }
                });
                return true;
            } catch (final Exception t) {
                LOG.error(t, t);
                this.teardown(t);
                return false;
            }
        }
    }

    private void logAcquired(final Channel channel, final long before) {
        final long acquireTime = System.currentTimeMillis() - before;
        final Level level;
        if (acquireTime > 45_000L) {
            level = Level.WARN;
        } else if (acquireTime > 30_000L) {
            level = Level.INFO;
        } else if (acquireTime > 10_000L) {
            level = Level.DEBUG;
        } else {
            level = Level.TRACE;
        }
        if (LOG.isEnabledFor(level)) {
            LOG.log(level, "Acquire took " + acquireTime + "ms for " + channel.remoteAddress());
        }
        Logs.extreme().debug("Acquired as: " + channel.localAddress());
    }

    private void logMessage(final IoMessage ioMessage) {
        Logs.extreme().debug(ioMessage);
        final Object payload = ioMessage.getMessage();
        final String patternList = Objects.toString(StackConfiguration.CLIENT_MESSAGE_LOG_WHITELIST, "");
        if (payload != null && (MATCHER.matches(patternList, payload.getClass().getSimpleName())
                || MATCHER.matches(patternList, payload.getClass().getName()))) {
            MESSAGE_LOG.info(payload);
        }
    }

    private void teardown(Throwable t) {
        if (t == null) {
            t = new NullPointerException("teardown() called with null argument.");
        }
        this.logRequestFailure(t);
        this.response.setException(t);
        if (this.acquireFuture != null) {
            this.maybeCloseChannel();
        }
    }

    private void maybeCloseChannel() {
        if (!this.acquireFuture.isDone() && !this.acquireFuture.cancel(true)) {
            LOG.error("Failed to cancel in-flight connection request: " + this.acquireFuture.getNow().toString());
        }
        final Channel channel = this.acquireFuture.getNow();
        if (channel != null) {
            closeAndReleaseChannel(channel);
        }
    }

    private void closeAndReleaseChannel(@Nonnull final Channel channel) {
        if (channel.isOpen()) {
            channel.close().addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(final ChannelFuture future) throws Exception {
                    EventRecord.here(AsyncRequestHandler.this.request.get().getClass(), EventClass.SYSTEM_REQUEST,
                            EventType.CHANNEL_CLOSED).trace();
                    releaseChannel(channel);
                }
            });
        } else {
            EventRecord.here(AsyncRequestHandler.this.request.get().getClass(), EventClass.SYSTEM_REQUEST,
                    EventType.CHANNEL_CLOSED, "ALREADY_CLOSED").trace();
            releaseChannel(channel);
        }
    }

    private void releaseChannel(@Nonnull final Channel channel) {
        if (this.channelPool != null && channelReleased.compareAndSet(false, true)) {
            this.channelPool.release(channel);
        }
    }

    private void logRequestFailure(Throwable t) {
        try {
            Logs.extreme().debug("RESULT:" + t.getMessage() + ":REQUEST:"
                    + ((this.request.get() != null) ? this.request.get().getClass() : "REQUEST IS NULL"));
            if (Exceptions.isCausedBy(t, RetryableConnectionException.class)
                    || Exceptions.isCausedBy(t, ConnectionException.class)
                    || Exceptions.isCausedBy(t, IOException.class)) {
                Logs.extreme().debug(
                        "Failed request: " + this.request.get().toSimpleString() + " because of: " + t.getMessage(),
                        t);
            }
        } catch (Exception ex) {
            Logs.extreme().error(ex, ex);
        }
    }

    @Override
    public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
        this.checkFinished(ctx, true);
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
        this.messageReceived(ctx, msg);
    }

    @Override
    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
        Logs.extreme().error(cause, cause);
        if (cause instanceof EucalyptusRemoteFault) {//GRZE: treat this like a normal response, set the response and close the channel.
            this.response.setException(cause);
            if (this.acquireFuture != null) {
                this.maybeCloseChannel();
            }
        } else {
            this.teardown(cause);
        }
    }

    private void messageReceived(final ChannelHandlerContext ctx, final Object message) {
        try {
            if (message instanceof IoMessage) {
                final IoMessage response = (IoMessage) message;
                try {
                    final R msg = (R) response.getMessage();
                    if (!msg.get_return(true)) {
                        this.teardown(new FailedRequestException("Cluster response includes _return=false", msg));
                    } else {
                        logMessage(response);
                        this.response.set(msg);
                        if (HttpHeaders.isKeepAlive(((IoMessage) message).getHttpMessage())) {
                            releaseChannel(ctx.channel());
                        } else {
                            closeAndReleaseChannel(ctx.channel());
                        }
                    }
                } catch (final Exception e1) {
                    LOG.error(e1, e1);
                    this.teardown(e1);
                }
            } else if (message == null) {
                final NoResponseException ex = new NoResponseException("Channel received a null response.",
                        this.request.get());
                LOG.error(ex, ex);
                this.teardown(ex);
            } else {
                final UnknownMessageTypeException ex = new UnknownMessageTypeException(
                        "Channel received a unknown response type: " + message.getClass().getCanonicalName(),
                        this.request.get(), message);
                LOG.error(ex, ex);
                this.teardown(ex);
            }
        } catch (final Exception t) {
            LOG.error(t, t);
            this.teardown(t);
        } finally {
            if (message instanceof IoMessage) {
                ((IoMessage) message).getHttpMessage().release();
            }
        }
    }

    private void checkFinished(final ChannelHandlerContext ctx, final boolean inactive) {
        if ((this.acquireFuture != null) && !this.acquireFuture.isSuccess()
                && (this.acquireFuture.cause() instanceof IOException)) {
            final Throwable ioError = this.acquireFuture.cause();
            if (!this.writeComplete.get()) {
                this.teardown(new RetryableConnectionException(
                        "Channel was closed before the write operation could be completed: " + ioError.getMessage(),
                        ioError, this.request.get()));
            } else {
                this.teardown(new ConnectionException(
                        "Channel was closed before the response was received: " + ioError.getMessage(), ioError,
                        this.request.get()));
            }
        } else {
            if (!this.writeComplete.get()) {
                this.teardown(new RetryableConnectionException(
                        "Channel was closed before the write operation could be completed", this.request.get()));
            } else if (!this.response.isDone()) {
                this.teardown(new ConnectionException("Channel was closed before the response was received.",
                        this.request.get()));
            } else {
                //GRZE:WOO:HA: guess we either failed to connect asynchronously or did the write but didn't actually read anything. So....
                this.teardown(new ChannelException("Channel was closed before connecting."));
            }
        }
    }

    public AtomicReference<Q> getRequest() {
        return request;
    }

    public CheckedListenableFuture<R> getResponse() {
        return response;
    }

}