/* * This software is subject to the terms of the Eclipse Public License v1.0 * Agreement, available at the following URL: * http://www.eclipse.org/legal/epl-v10.html. * You must accept the terms of that agreement to use this software. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.server.Execution; import mondrian.util.Pair; import org.eigenbase.util.property.IntegerProperty; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.*; /** * A utility class for {@link RolapConnection}. It specializes in * shepherding the creation of RolapResult by running the actual execution * on a separate thread from the user thread so we can: * <ul> * <li>Monitor all executions for timeouts and resource limits as they run * in the background</li> * <li>Bubble exceptions to the user thread as fast as they happen.</li> * <li>Gracefully cancel all SQL statements and cleanup in the background.</li> * </ul> * * @author LBoudreau */ public class RolapResultShepherd { /** * An executor service used for both the shepherd thread and the * Execution objects. */ private final ExecutorService executor; /** * List of tasks that should be monitored by the shepherd thread. */ private final List<Pair<FutureTask<Result>, Execution>> tasks = new CopyOnWriteArrayList<Pair<FutureTask<Result>,Execution>>(); private final Timer timer = Util.newTimer("mondrian.rolap.RolapResultShepherd#timer", true); public RolapResultShepherd() { final IntegerProperty property = MondrianProperties.instance().RolapConnectionShepherdNbThreads; final int maximumPoolSize = property.get(); executor = Util.getExecutorService( // We use the same value for coreSize and maxSize // because that's the behavior we want. All extra // tasks will be put on an unbounded queue. maximumPoolSize, maximumPoolSize, 1, "mondrian.rolap.RolapResultShepherd$executor", new RejectedExecutionHandler() { public void rejectedExecution( Runnable r, ThreadPoolExecutor executor) { throw MondrianResource.instance().QueryLimitReached.ex( maximumPoolSize, property.getPath()); } }); final Pair<Long, TimeUnit> interval = Util.parseInterval( String.valueOf( MondrianProperties.instance() .RolapConnectionShepherdThreadPollingInterval.get()), TimeUnit.MILLISECONDS); long period = interval.right.toMillis(interval.left); timer.schedule( new TimerTask() { public void run() { for (final Pair<FutureTask<Result>, Execution> task : tasks) { if (task.left.isDone()) { tasks.remove(task); continue; } if (task.right.isCancelOrTimeout()) { // Remove it from the list so that we know // it was cleaned once. tasks.remove(task); // Cancel the FutureTask for which // the user thread awaits. The user // thread will call // Execution.checkCancelOrTimeout // later and take care of sending // an exception on the user thread. task.left.cancel(false); } } } }, period, period); } /** * Executes and shepherds the execution of an Execution instance. * The shepherd will wrap the Execution instance into a Future object * which can be monitored for exceptions. If any are encountered, * two things will happen. First, the user thread will be returned and * the resulting exception will bubble up. Second, the execution thread * will attempt to do a graceful stop of all running SQL statements and * release all other resources gracefully in the background. * @param execution An Execution instance. * @param callable A callable to monitor returning a Result instance. * @throws ResourceLimitExceededException if some resource limit specified * in the property file was exceeded * @throws QueryCanceledException if query was canceled during execution * @throws QueryTimeoutException if query exceeded timeout specified in * the property file * @return A Result object, as supplied by the Callable passed as a * parameter. */ public Result shepherdExecution( Execution execution, Callable<Result> callable) { // We must wrap this execution into a task that so that we are able // to monitor, cancel and detach from it. FutureTask<Result> task = new FutureTask<Result>(callable); // Register this task with the shepherd thread final Pair<FutureTask<Result>, Execution> pair = new Pair<FutureTask<Result>, Execution>( task, execution); tasks.add(pair); try { // Now run it. executor.execute(task); return task.get(); } catch (Throwable e) { // Make sure to clean up pending SQL queries. execution.cancelSqlStatements(); // Make sure to propagate the interruption flag. if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } // Unwrap any java.concurrent wrappers. Throwable node = e; if (e instanceof ExecutionException) { ExecutionException executionException = (ExecutionException) e; node = executionException.getCause(); } // Let the Execution throw whatever it wants to, this way the // API contract is respected. The program should in most cases // stop here as most exceptions will originate from the Execution // instance. execution.checkCancelOrTimeout(); // We must also check for ResourceLimitExceededExceptions, // which might be wrapped by an ExecutionException. In order to // respect the API contract, we must throw the cause, not the // wrapper. final ResourceLimitExceededException t = Util.getMatchingCause( node, ResourceLimitExceededException.class); if (t != null) { throw t; } // Check for Mondrian exceptions in the exception chain. // we can throw these back as-is. final MondrianException m = Util.getMatchingCause( node, MondrianException.class); if (m != null) { // Throw that. throw m; } // Since we got here, this means that the exception was // something else. Just wrap/throw. if (node instanceof RuntimeException) { throw (RuntimeException) node; } else if (node instanceof Error) { throw (Error) node; } else { throw new MondrianException(node); } } } public void shutdown() { this.timer.cancel(); this.executor.shutdown(); this.tasks.clear(); } } // End RolapResultShepherd.java