/************************************************************************* * Copyright 2009-2016 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. * * This file may incorporate work covered under the following copyright * and permission notice: * * Copyright (C) 2009 Google Inc. * * 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 com.eucalyptus.util.concurrent; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.configurable.ConfigurableFieldType; import com.eucalyptus.records.Logs; import com.google.common.base.Predicate; import org.apache.log4j.Logger; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.system.Threads; import com.eucalyptus.util.async.CheckedListenableFuture; import com.eucalyptus.util.async.Futures; import com.google.common.util.concurrent.ExecutionList; import javax.annotation.Nullable; import static com.eucalyptus.util.Parameters.checkParam; import static org.hamcrest.Matchers.notNullValue; /** * <p> * An abstract base implementation of the listener support provided by {@link ListenableFuture}. * This class uses an {@link ExecutionList} to guarantee that all registered listeners will be * executed. Listener/Executor pairs are stored in the execution list and executed in the order in * which they were added, but because of thread scheduling issues there is no guarantee that the JVM * will execute them in order. In addition, listeners added after the task is complete will be * executed immediately, even if some previously added listeners have not yet been executed. * * <p> * This class uses the {@link AbstractFuture} class to implement the {@code ListenableFuture} * interface and simply delegates the {@link #addListener(Runnable, ExecutorService)} and * {@link #done()} methods to it. * * @author Sven Mawson * @author chris grzegorczyk <grze@eucalyptus.com> Adopted and repurposed to support callable * chaining. */ @ConfigurableClass( root = "bootstrap.async", description = "Parameters controlling the asynchronous futures and executors." ) public abstract class AbstractListenableFuture<V> extends AbstractFuture<V> implements ListenableFuture<V> { private static final Logger LOG = Logger.getLogger( AbstractListenableFuture.class ); private static final Runnable DONE = () -> { }; private static final ExecutorService executor = Executors.newCachedThreadPool( Threads.threadFactory( "listenable-future-pool-%d" ) ); private static final Predicate<StackTraceElement> filter = Threads.filterStackByQualifiedName( "com.eucalyptus.*" ); @ConfigurableField( description = "Number of seconds a future listener can execute before a debug message is logged.", type = ConfigurableFieldType.PRIVATE, initial = "30" ) public static Long FUTURE_LISTENER_DEBUG_LIMIT_SECS = 30L; @ConfigurableField( description = "Number of seconds a future listener can execute before an info message is logged.", type = ConfigurableFieldType.PRIVATE, initial = "60" ) public static Long FUTURE_LISTENER_INFO_LIMIT_SECS = 60L; @ConfigurableField( description = "Number of seconds a future listener can execute before an error message is logged.", type = ConfigurableFieldType.PRIVATE, initial = "120" ) public static Long FUTURE_LISTENER_ERROR_LIMIT_SECS = 120L; @ConfigurableField( description = "Number of seconds a future listener's executor waits to get() per call.", type = ConfigurableFieldType.PRIVATE, initial = "30" ) public static Long FUTURE_LISTENER_GET_TIMEOUT = 30L; @ConfigurableField( description = "Total number of seconds a future listener's executor waits to get().", type = ConfigurableFieldType.PRIVATE, initial = "8" ) public static Integer FUTURE_LISTENER_GET_RETRIES = 8; private final ConcurrentLinkedQueue<Runnable> listeners = new ConcurrentLinkedQueue<Runnable>( ); private final AtomicBoolean finished = new AtomicBoolean( false ); private final String startingStack; protected AbstractListenableFuture() { this.startingStack = Threads.currentStackRange( 0, 32 ); } protected <T> void add( final ExecPair<T> pair ) { this.listeners.add( pair ); if ( this.finished.get( ) ) { EventRecord.here( pair.getClass( ), EventType.FUTURE, "run(" + pair.toString( ) + ")" ).exhaust( ); this.listeners.remove( pair ); pair.run( ); } else { EventRecord.here( pair.getClass( ), EventType.FUTURE, "add(" + pair.toString( ) + ")" ).exhaust( ); } } @Override public void addListener( final Runnable listener, final ExecutorService exec ) { final ExecPair<Object> pair = new ExecPair<Object>( new Callable() { @Override public Object call() throws Exception { listener.run(); return null; } @Override public String toString() { return "ListenableFuture.ExecPair.listener " + listener + " [" + Thread.currentThread().getStackTrace()[2] + "]"; } }, exec ); this.add( pair ); } @Override public void addListener( final Runnable listener ) { this.addListener( listener, executor ); } /** * @see com.eucalyptus.util.concurrent.ListenableFuture#addListener(java.util.concurrent.Callable, * ExecutorService) */ @Override public <T> CheckedListenableFuture<T> addListener( final Callable<T> listener, final ExecutorService executor ) { final ExecPair<T> pair = new ExecPair<T>( listener, executor ); this.add( pair ); return pair.getFuture( ); } /** * @see com.eucalyptus.util.concurrent.ListenableFuture#addListener(java.util.concurrent.Callable) */ @Override public <T> CheckedListenableFuture<T> addListener( final Callable<T> listener ) { return this.addListener( listener, executor ); } @Override protected void done( ) { this.listeners.add( DONE ); if ( this.finished.compareAndSet( false, true ) ) { while ( this.listeners.peek( ) != DONE ) { this.listeners.poll( ).run( ); } } } @Override public boolean set( final V value ) { return super.set( value ); } @Override public boolean setException( final Throwable throwable ) { return super.setException( throwable ); } class ExecPair<C> implements Runnable { private Callable<C> callable; private Runnable runnable; private final CheckedListenableFuture<C> future = Futures.newGenericeFuture( ); private final ExecutorService executor; ExecPair( final Callable callable, final ExecutorService executor ) { checkParam( "BUG: callable is null.", callable, notNullValue() ); checkParam( "BUG: executor is null.", executor, notNullValue() ); this.callable = callable; this.executor = executor; } ExecPair( final Callable<C> callable ) { this( callable, AbstractListenableFuture.executor ); } private static final String message = "Listener failed to execute within the time limit (%d): %s using executor %s"; @Override public void run( ) { try { final long startTime = System.currentTimeMillis(); Predicate<Callable<C>> timeoutLogger = new Predicate<Callable<C>>() { @Override public boolean apply( @Nullable Callable<C> input ) { try { long elapsed = System.currentTimeMillis() - startTime; long seconds = TimeUnit.MILLISECONDS.toSeconds( elapsed ); Supplier<String> details = () -> ExecPair.this.callable.toString() + " [" + Threads.filteredStack( filter ).iterator().next() + "]"; if ( seconds > FUTURE_LISTENER_DEBUG_LIMIT_SECS ) { if ( LOG.isDebugEnabled( ) ) { LOG.debug( String.format( message, FUTURE_LISTENER_DEBUG_LIMIT_SECS, details.get(), executor.toString() ) ); } return true; } else if ( seconds > FUTURE_LISTENER_INFO_LIMIT_SECS ) { if ( LOG.isInfoEnabled( ) ) { LOG.info( String.format( message, FUTURE_LISTENER_INFO_LIMIT_SECS, details.get( ), executor.toString( ) ) ); } return true; } else if ( seconds > FUTURE_LISTENER_ERROR_LIMIT_SECS ) { LOG.error( String.format( message, FUTURE_LISTENER_ERROR_LIMIT_SECS, details.get(), executor.toString() ) ); return true; } if ( LOG.isTraceEnabled( ) ) { LOG.trace( String.format( "Listener still within time limit (%d): %s using executor %s", FUTURE_LISTENER_ERROR_LIMIT_SECS, details.get(), executor.toString() ) ); } } catch ( Exception e ) { LOG.error( e ); } return false; } }; Future<C> execFuture = this.executor.submit( this.callable ); for ( int iterations = 0; ( !Bootstrap.isOperational( ) && !Bootstrap.isShuttingDown( ) ) || ( iterations < FUTURE_LISTENER_GET_RETRIES ); iterations++ ) { try { C outcome = execFuture.get( FUTURE_LISTENER_GET_TIMEOUT, TimeUnit.SECONDS ); this.future.set( outcome ); break; } catch ( TimeoutException e ) { continue; } finally { if (timeoutLogger.apply(this.callable) && Logs.exhaust( ).isDebugEnabled( )) { Logs.exhaust().debug( "Intial Stack: \n" + AbstractListenableFuture.this.startingStack ); Logs.exhaust().debug( "Current Stack: \n" + Threads.currentStackString() ); } } } if ( !this.future.isDone() ) { String message = "Failed to invoke listener for " + AbstractListenableFuture.this + " of type: " + (this.runnable != null ? this.runnable : this.callable); LOG.error( message ); LOG.error( startingStack ); throw new TimeoutException( message ); } } catch ( final InterruptedException ex ) { LOG.error( ex ); Thread.currentThread( ).interrupt( ); this.future.setException( ex ); } catch ( final ExecutionException ex ) { LOG.error( ex, ex ); this.future.setException( ex.getCause( ) ); } catch ( final Exception ex ) { LOG.error( ex, ex ); this.future.setException( ex.getCause( ) ); } } CheckedListenableFuture<C> getFuture( ) { return this.future; } protected ExecutorService getExecutor( ) { return this.executor; } @Override public String toString( ) { return String.format( "ExecPair:callable=%s:runnable=%s", this.callable, this.runnable ); } } }