Java tutorial
/************************************************************************* * 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; } }