/* * Copyright (2005-2012) Schibsted ASA * This file is part of Possom. * * Possom is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Possom 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Possom. If not, see <http://www.gnu.org/licenses/>. * */ package no.sesat.search.mode.executor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import no.sesat.search.mode.command.SearchCommand; import no.sesat.search.mode.config.SearchConfiguration; import no.sesat.search.result.ResultItem; import no.sesat.search.result.ResultList; /** * An extension to the ParallelSearchCommandExecutor that supports individual thread pools for each skin's different * commands. * Since each command's SearchConfiguration is a singleton (against the given the skin, mode, and id). * * Any SearchCommand that misbehaves has it's corresponding pool frozen to the active number of threads. * * * @version <tt>$Id$</tt> */ final class ThrottledSearchCommandExecutor extends ParallelSearchCommandExecutor { private static final Map<SearchConfiguration,Throttle> THROTTLES = new HashMap<SearchConfiguration,Throttle>(); private static final ReadWriteLock THROTTLES_LOCK = new ReentrantReadWriteLock(); private static volatile long lastThrottleLog = System.currentTimeMillis() / 60000; public ThrottledSearchCommandExecutor(){} @Override public Map<Future<ResultList<ResultItem>>,SearchCommand> invokeAll( Collection<SearchCommand> callables) throws InterruptedException { final Collection<SearchCommand> allowedCallables = new ArrayList<SearchCommand>(callables); for(SearchCommand command : callables){ final Throttle throttle = getThrottle(command.getSearchConfiguration()); if(throttle.isThrottled()){ LOG.error(command.getSearchConfiguration() + " is throttled and will not be executed"); allowedCallables.remove(command); command.handleCancellation(); }else{ throttle.incrementPedal(); } } if (LOG.isDebugEnabled() && System.currentTimeMillis() / 60000 != lastThrottleLog) { logThrottles(); lastThrottleLog = System.currentTimeMillis() / 60000; } return super.invokeAll(allowedCallables); } @Override public Map<Future<ResultList<ResultItem>>, SearchCommand> waitForAll( final Map<Future<ResultList<ResultItem>>,SearchCommand> results, final int timeoutInMillis) throws InterruptedException, TimeoutException, ExecutionException { try{ return super.waitForAll(results, timeoutInMillis); }finally{ for(SearchCommand command : results.values()){ final Throttle throttle = getThrottle(command.getSearchConfiguration()); if(command.isCancelled()){ LOG.warn("FREEZING (at " + Math.max(1, throttle.getPedal()) + ") THREAD POOL EXECUTOR " + command.getSearchConfiguration() + '\n'); // we freeze thread pool at current size (excluding the just failed callable) throttle.throttle(); }else if(throttle.isThrottled()){ LOG.warn("Restoring ThreadPoolExecutor " + command.getSearchConfiguration() + '\n'); // command was successful unfreeze thread pool throttle.unthrottle(); } throttle.decrementPedal(); } } } protected Throttle getThrottle(final SearchConfiguration config) { Throttle throttle; try{ THROTTLES_LOCK.readLock().lock(); throttle = THROTTLES.get(config); }finally{ THROTTLES_LOCK.readLock().unlock(); } if(null == throttle){ try{ THROTTLES_LOCK.writeLock().lock(); throttle = new Throttle(); THROTTLES.put(config, throttle); }finally{ THROTTLES_LOCK.writeLock().unlock(); } } return throttle; } private static void logThrottles(){ final StringBuilder sb = new StringBuilder(); THROTTLES_LOCK.readLock().lock(); for(Map.Entry<SearchConfiguration,Throttle> entry : THROTTLES.entrySet()){ sb.append('\n' + entry.getKey().toString() + " executing " + entry.getValue().getPedal()); } THROTTLES_LOCK.readLock().unlock(); LOG.debug(sb.toString()); } private static final class Throttle{ private int limit = Integer.MAX_VALUE; private volatile int pedal = 0; boolean isThrottled(){ return pedal >= limit; } int getLimit(){ return limit; } void throttle(){ this.limit = Math.max(1, pedal); } void unthrottle(){ limit = Integer.MAX_VALUE; } void incrementPedal(){ ++pedal; } void decrementPedal(){ --pedal; } int getPedal(){ return pedal; } } }