/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.hibernate.notification; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.Validate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author Óscar González Fernández * */ class NotBlockingAutoUpdatedSnapshot<T> implements IAutoUpdatedSnapshot<T> { private static final Log LOG = LogFactory .getLog(NotBlockingAutoUpdatedSnapshot.class); private final Callable<T> callable; private final AtomicReference<State> currentState; private final String name; private final ExecutionsReport executionsReport; private abstract class State { abstract T getValue(); void cancel() { } State nextState(Future<T> future) { return new PreviousValueAndOngoingCalculation(this, future); } boolean hasBeenInitialized() { return true; } } private class NotLaunchState extends State { @Override T getValue() { throw new UnsupportedOperationException(); } @Override State nextState(Future<T> future) { return new FirstCalculation(future); } @Override boolean hasBeenInitialized() { return false; } } private class NoOngoingCalculation extends State { private final T value; NoOngoingCalculation(T value) { this.value = value; } @Override T getValue() { return value; } } private class PreviousValueAndOngoingCalculation extends State { private final State previousValue; private final Future<T> ongoingCalculation; private PreviousValueAndOngoingCalculation(State value, Future<T> ongoingCalculation) { Validate.notNull(value); Validate.notNull(ongoingCalculation); this.previousValue = value; this.ongoingCalculation = ongoingCalculation; } @Override T getValue() { if (!ongoingCalculation.isCancelled() && ongoingCalculation.isDone()) { T newValue = getValueFromFuture(); currentState.compareAndSet(this, new NoOngoingCalculation( newValue)); return newValue; } LOG.debug(name + " the ongoing calculation has not been completed. " + "Returning previous value"); return previousValue.getValue(); } private T getValueFromFuture() { try { return ongoingCalculation.get(); } catch (Exception e) { LOG.error("error creating new value for " + name + ", keeping old value", e); return previousValue.getValue(); } } @Override void cancel() { if (ongoingCalculation.isDone() || ongoingCalculation.isCancelled()) { return; } LOG.debug(name + " cancelling ongoing future"); try { ongoingCalculation.cancel(true); } catch (Exception e) { LOG.error("error cancelling future for " + name, e); } } } private class FirstCalculation extends State { private final Future<T> ongoingCalculation; private FirstCalculation(Future<T> ongoingCalculation) { this.ongoingCalculation = ongoingCalculation; } @Override T getValue() { try { return ongoingCalculation.get(); } catch (Exception e) { throw new RuntimeException(e); } } @Override void cancel() { ongoingCalculation.cancel(true); } } public NotBlockingAutoUpdatedSnapshot(String name, Callable<T> callable) { Validate.notNull(callable); Validate.notNull(name); this.name = "*" + name + "*"; this.callable = callable; this.currentState = new AtomicReference<State>(new NotLaunchState()); this.executionsReport = new ExecutionsReport(); } @Override public T getValue() { return currentState.get().getValue(); } public void reloadNeeded(ExecutorService executorService) { Future<T> future = executorService .submit(callableDecoratedWithStatistics()); State previousState; State newState = null; do { if (newState != null) { newState.cancel(); } previousState = currentState.get(); newState = previousState.nextState(future); } while (!currentState.compareAndSet(previousState, newState)); previousState.cancel(); } public void ensureFirstLoad(ExecutorService executorService) { if (hasBeenInitialized()) { return; } Future<T> future = executorService .submit(callableDecoratedWithStatistics()); State previous = currentState.get(); State newState = previous.nextState(future); boolean compareAndSet = currentState.compareAndSet(previous, newState); if (!compareAndSet) { newState.cancel(); } } @Override public String toString() { return name; } private boolean hasBeenInitialized() { return currentState.get().hasBeenInitialized(); } private Callable<T> callableDecoratedWithStatistics() { final long requestTime = System.currentTimeMillis(); return new Callable<T>() { @Override public T call() throws Exception { long start = System.currentTimeMillis(); long timeWaiting = start - requestTime; Exception error = null; try { return callable.call(); } catch (Exception e) { error = e; LOG.error("error executing snapshot " + name); throw e; } finally { long timeExecuting = System.currentTimeMillis() - start; executionsReport.newData(timeWaiting, timeExecuting, error); } } }; } private static class Data { final int executionTimes; final int errors; long totalMsWaiting; long totalMsExecuting; private Data(int executionTimes, int errors, long totalMsWaiting, long totalMsExecuting) { this.executionTimes = executionTimes; this.totalMsWaiting = totalMsWaiting; this.totalMsExecuting = totalMsExecuting; this.errors = errors; } public Data newData(long timeWaiting, long timeExcuting, Exception e) { return new Data(executionTimes + 1, errors + (e != null ? 1 : 0), totalMsWaiting + timeWaiting, totalMsExecuting + timeExcuting); } } private class ExecutionsReport { private AtomicReference<Data> data = new AtomicReference<Data>( new Data(0, 0, 0, 0)); public void newData(long timeWaiting, long timeExecuting, Exception possibleError) { Data previousData; Data newData; do { previousData = data.get(); newData = previousData.newData(timeWaiting, timeExecuting, possibleError); } while (!data.compareAndSet(previousData, newData)); report(timeWaiting, timeExecuting, newData, possibleError); } private void report(long timeWaiting, long timeExecuting, Data data, Exception possibleError) { LOG.debug(name + " took " + timeExecuting + " ms executing"); if (possibleError != null) { LOG.error("error loading " + name, possibleError); } LOG.debug(name + " waited for " + timeWaiting + " ms until executing"); LOG.debug(name + " mean time waiting for execution: " + data.totalMsWaiting / data.executionTimes + " ms"); LOG.debug(name + " mean time executing: " + data.totalMsExecuting / data.executionTimes + " ms"); LOG.debug(name + " has been executed " + data.executionTimes + " times"); LOG.debug(name + " has produced errors " + data.errors + " times"); } } }