/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.view; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.Lifecycle; import com.opengamma.core.change.ChangeEvent; import com.opengamma.core.change.ChangeListener; import com.opengamma.core.change.ChangeProvider; import com.opengamma.engine.function.CompiledFunctionService; import com.opengamma.engine.function.config.FunctionConfigurationSource; import com.opengamma.engine.view.impl.ViewProcessorInternal; import com.opengamma.financial.timeseries.HistoricalTimeSeriesSourceChangeProvider; import com.opengamma.id.ObjectId; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.NamedThreadPoolFactory; /** * Manages a set of view processors that share a function repository. When function configuration changes (or the objects the functions are based on change) all active view processes are paused so * that the reinitialization can occur without giving inconsistent results for a running view cycle. * <p> * Note that previous behavior was capable of "latching" the sources so that the "get-latest" methods could always be used by functions. This has been removed as relying on such methods is problematic * for other system traits and a valid version-correction timestamp is available at all times. */ public class ViewProcessorManager implements Lifecycle { private static final Logger s_logger = LoggerFactory.getLogger(ViewProcessorManager.class); private final Set<ViewProcessorInternal> _viewProcessors = new HashSet<ViewProcessorInternal>(); private final Set<CompiledFunctionService> _functions = new HashSet<CompiledFunctionService>(); private final Collection<ChangeProvider> _masters = new ArrayList<ChangeProvider>(); private final ReentrantLock _lifecycleLock = new ReentrantLock(); private final ReentrantLock _changeLock = new ReentrantLock(); private final ExecutorService _executor = NamedThreadPoolFactory.newCachedThreadPool("ViewProcessorManager", true); private final Set<ObjectId> _watchSet = new HashSet<ObjectId>(); private final Set<WatchSetProvider> _watchSetProviders = new HashSet<WatchSetProvider>(); private final ChangeListener _masterListener = new ChangeListener() { @Override public void entityChanged(ChangeEvent event) { if (_watchSet.contains(event.getObjectId())) { ViewProcessorManager.this.onMasterChanged(event.getObjectId()); } } }; private Set<ObjectId> _pendingChanges = new HashSet<ObjectId>(); private boolean _isRunning; public ViewProcessorManager() { } private void assertNotRunning() { if (isRunning()) { throw new IllegalStateException("Already running"); } } public void setViewProcessor(final ViewProcessorInternal viewProcessor) { ArgumentChecker.notNull(viewProcessor, "viewProcessor"); assertNotRunning(); _viewProcessors.clear(); _viewProcessors.add(viewProcessor); } public void setViewProcessors(final Collection<ViewProcessorInternal> viewProcessors) { ArgumentChecker.notNull(viewProcessors, "viewProcessors"); assertNotRunning(); _viewProcessors.clear(); _viewProcessors.addAll(viewProcessors); } public Set<ViewProcessorInternal> getViewProcessors() { return Collections.unmodifiableSet(_viewProcessors); } /** * @param master the master, not null * @param source for Spring compatibility only - ignored * @deprecated Use {@link #setMaster} instead */ @Deprecated public void setMasterAndSource(final ChangeProvider master, final Object source) { setMaster(master); } public void setMaster(final ChangeProvider master) { ArgumentChecker.notNull(master, "master"); assertNotRunning(); _masters.clear(); _masters.add(master); } /** * @param masterToSource the masters, the values of the map are ignored * @deprecated Use {@link #setMasters} instead */ @Deprecated public void setMastersAndSources(final Map<ChangeProvider, ?> masterToSource) { ArgumentChecker.notNull(masterToSource, "masterToSource"); setMasters(masterToSource.keySet()); } public void setMasters(final Collection<? extends ChangeProvider> masters) { ArgumentChecker.notNull(masters, "masters"); assertNotRunning(); _masters.clear(); _masters.addAll(masters); } public void setWatchSetProviders(final Set<WatchSetProvider> watchSetProviders) { ArgumentChecker.notNull(watchSetProviders, "watchSetProviders"); assertNotRunning(); _watchSetProviders.clear(); _watchSetProviders.addAll(watchSetProviders); } @Override public boolean isRunning() { _lifecycleLock.lock(); try { return _isRunning; } finally { _lifecycleLock.unlock(); } } @Override public void start() { _lifecycleLock.lock(); try { if (!_isRunning) { _changeLock.lock(); try { for (ChangeProvider master : _masters) { master.changeManager().addChangeListener(_masterListener); } for (ViewProcessorInternal viewProcessor : _viewProcessors) { viewProcessor.getFunctionCompilationService().getFunctionRepositoryFactory().changeManager().addChangeListener(_masterListener); } } finally { _changeLock.unlock(); } _functions.clear(); for (ViewProcessorInternal viewProcessor : _viewProcessors) { _functions.add(viewProcessor.getFunctionCompilationService()); } s_logger.info("Initializing functions"); for (CompiledFunctionService function : _functions) { final Set<ObjectId> watch = function.initialize(); _watchSet.addAll(watch); addAlternateWatchSet(watch); } reinitializeWatchSet(); s_logger.debug("WatchSet = {}", _watchSet); s_logger.info("Starting view processors"); for (ViewProcessorInternal viewProcessor : _viewProcessors) { viewProcessor.start(); } _isRunning = true; } } finally { _lifecycleLock.unlock(); } } @Override public void stop() { _lifecycleLock.lock(); try { if (_isRunning) { for (ViewProcessorInternal viewProcessor : _viewProcessors) { viewProcessor.stop(); viewProcessor.getFunctionCompilationService().getFunctionRepositoryFactory().changeManager().removeChangeListener(_masterListener); } for (ChangeProvider master : _masters) { master.changeManager().removeChangeListener(_masterListener); } _masters.clear(); _isRunning = false; } } finally { _lifecycleLock.unlock(); } } private void onMasterChanged(final ObjectId objectIdentifier) { s_logger.debug("Change from {}", objectIdentifier); _changeLock.lock(); try { if (_pendingChanges.isEmpty()) { s_logger.debug("Starting reinitializer job"); // Kick off a reinitialization; this may take some time if the view processors must wait for their views to finish calculating first _executor.submit(new Runnable() { @Override public void run() { try { reinitializeFunctions(); } catch (Throwable t) { s_logger.error("Error reinitializing", t); } } }); } else { s_logger.debug("Reinitializing job already active for {} other changes", _pendingChanges.size()); } _pendingChanges.add(objectIdentifier); } finally { _changeLock.unlock(); } } private void reinitializeFunctions() { _lifecycleLock.lock(); s_logger.info("Begin configuration change"); try { final List<Runnable> resumes = new ArrayList<Runnable>(_viewProcessors.size()); final List<Future<Runnable>> suspends = new ArrayList<Future<Runnable>>(_viewProcessors.size()); s_logger.debug("Suspending view processors"); for (ViewProcessorInternal viewProcessor : _viewProcessors) { suspends.add(viewProcessor.suspend(_executor)); } while (!suspends.isEmpty()) { final Future<Runnable> future = suspends.remove(suspends.size() - 1); try { resumes.add(future.get(3000, TimeUnit.MILLISECONDS)); } catch (TimeoutException e) { s_logger.warn("Timeout waiting for view to suspend"); suspends.add(future); } catch (Throwable t) { s_logger.warn("Couldn't suspend view", t); } } Set<ObjectId> pendingChanges; _changeLock.lock(); try { pendingChanges = _pendingChanges; _pendingChanges = new HashSet<ObjectId>(); } finally { _changeLock.unlock(); } s_logger.trace("Pending changed = {}", pendingChanges); s_logger.debug("Re-initializing functions"); _watchSet.clear(); for (CompiledFunctionService functions : _functions) { try { final Set<ObjectId> watch = functions.reinitialize(); _watchSet.addAll(watch); addAlternateWatchSet(watch); } catch (Throwable t) { s_logger.error("Error reinitializing functions", t); } } reinitializeWatchSet(); s_logger.trace("WatchSet = {}", _watchSet); s_logger.debug("Resuming view processors"); for (Runnable resume : resumes) { resume.run(); } s_logger.info("Configuration change complete"); } finally { _lifecycleLock.unlock(); } } private void reinitializeWatchSet() { _watchSet.add(FunctionConfigurationSource.OBJECT_ID); _watchSet.add(HistoricalTimeSeriesSourceChangeProvider.ALL_HISTORICAL_TIME_SERIES); } private void addAlternateWatchSet(final Set<ObjectId> watchSet) { for (WatchSetProvider provider : _watchSetProviders) { final Set<ObjectId> additional = provider.getAdditionalWatchSet(watchSet); if (additional != null) { _watchSet.addAll(additional); } } } }