/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.function; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.Lifecycle; import org.threeten.bp.Instant; import com.google.common.collect.Maps; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.engine.function.config.FunctionConfigurationSource; import com.opengamma.engine.function.config.FunctionRepositoryFactory; import com.opengamma.id.ObjectId; import com.opengamma.id.VersionCorrection; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.PoolExecutor; import com.opengamma.util.PoolExecutor.CompletionListener; import com.opengamma.util.monitor.OperationTimer; /** * Combines a function repository and compiler to give access to compiled functions. */ public class CompiledFunctionService implements Lifecycle { private static final Logger s_logger = LoggerFactory.getLogger(CompiledFunctionService.class); private final FunctionRepositoryFactory _functionRepositoryFactory; private FunctionRepository _rawFunctionRepository; private FunctionRepository _initializedFunctionRepository; private final FunctionRepositoryCompiler _functionRepositoryCompiler; private final FunctionCompilationContext _functionCompilationContext; private final Set<FunctionDefinition> _reinitializingFunctionDefinitions = new HashSet<FunctionDefinition>(); private final Set<ObjectId> _reinitializingFunctionRequirements = new HashSet<ObjectId>(); /** * A pool executor for general use by the engine. This should be used for tasks that should saturate the available processors. * <p> * This is not null unless the service has been shutdown. */ private volatile PoolExecutor _executorService; private final FunctionReinitializer _reinitializer = new FunctionReinitializer() { @Override public synchronized void reinitializeFunction(final FunctionDefinition function, final ObjectId identifier) { s_logger.debug("Re-initialize function {} on change to {}", function, identifier); ArgumentChecker.notNull(function, "function"); _reinitializingFunctionDefinitions.add(function); _reinitializingFunctionRequirements.add(identifier); } @Override public synchronized void reinitializeFunction(final FunctionDefinition function, final Collection<ObjectId> identifiers) { s_logger.debug("Re-initialize function {} on changes to {}", function, identifiers); ArgumentChecker.notNull(function, "function"); _reinitializingFunctionDefinitions.add(function); _reinitializingFunctionRequirements.addAll(identifiers); } }; private static PoolExecutor createExecutorService() { return new PoolExecutor(Math.max(Runtime.getRuntime().availableProcessors(), 1), "CFS"); } /** * Creates a new instance. * <p> * This method will use a static function repository - it will reinitialize functions, but cannot respond to addition or removal of functions. Use a constructor that takes * {@link FunctionConfigurationSource} instead. * * @param functionRepository the static function repository, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null * @deprecated Use {@link #CompiledFunctionService(FunctionConfigurationSource,FunctionRepositoryCompiler,FunctionCompilationContext)} instead */ @Deprecated public CompiledFunctionService(final FunctionRepository functionRepository, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext) { this(functionRepository, functionRepositoryCompiler, functionCompilationContext, createExecutorService()); } /** * Creates a new instance. * <p> * This method will use a static function repository - it will reinitialize functions, but cannot respond to addition or removal of functions. Use a constructor that takes * {@link FunctionConfigurationSource} instead. * * @param functionRepository the static function repository, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null * @param executorService the executor service for general processor heavy operations, not null * @deprecated Use {@link #CompiledFunctionService(FunctionConfigurationSource,FunctionRepositoryCompiler,FunctionCompilationContext,PoolExecutor)} instead */ @Deprecated public CompiledFunctionService(final FunctionRepository functionRepository, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext, PoolExecutor executorService) { this(FunctionRepositoryFactory.constructRepositoryFactory(functionRepository), functionRepositoryCompiler, functionCompilationContext, executorService); } /** * Creates a new instance. * * @param functions the source of function configuration, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null */ public CompiledFunctionService(final FunctionConfigurationSource functions, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext) { this(functions, functionRepositoryCompiler, functionCompilationContext, createExecutorService()); } /** * Creates a new instance. * * @param functions the source of function configuration, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null * @param executorService the executor service for general processor heavy operations, not null */ public CompiledFunctionService(final FunctionConfigurationSource functions, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext, PoolExecutor executorService) { this(FunctionRepositoryFactory.constructRepositoryFactory(functions), functionRepositoryCompiler, functionCompilationContext, executorService); } /** * Creates a new instance. * * @param functions the source of function repository to use, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null */ public CompiledFunctionService(final FunctionRepositoryFactory functions, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext) { this(functions, functionRepositoryCompiler, functionCompilationContext, createExecutorService()); } /** * Creates a new instance. * * @param functions the source of function repository to use, not null * @param functionRepositoryCompiler the repository compiler service, not null * @param functionCompilationContext the function context, not null * @param executorService the executor service for general processor heavy operations, not null */ public CompiledFunctionService(final FunctionRepositoryFactory functions, final FunctionRepositoryCompiler functionRepositoryCompiler, final FunctionCompilationContext functionCompilationContext, PoolExecutor executorService) { ArgumentChecker.notNull(functions, "functions"); ArgumentChecker.notNull(functionRepositoryCompiler, "functionRepositoryCompiler"); ArgumentChecker.notNull(functionCompilationContext, "functionCompilationContext"); ArgumentChecker.notNull(executorService, "executorService"); _functionRepositoryFactory = functions; _functionRepositoryCompiler = functionRepositoryCompiler; _functionCompilationContext = functionCompilationContext; _executorService = executorService; } private static final class StaticFunctionRepository implements FunctionRepository { private final Map<String, FunctionDefinition> _functions; private StaticFunctionRepository(final FunctionRepository functionRepo) { if (functionRepo != null) { final Collection<FunctionDefinition> functions = functionRepo.getAllFunctions(); _functions = Maps.newHashMapWithExpectedSize(functions.size()); for (FunctionDefinition function : functions) { _functions.put(function.getUniqueId(), function); } } else { _functions = new HashMap<String, FunctionDefinition>(); } } private void remove(final FunctionDefinition function) { _functions.remove(function.getUniqueId()); } private void add(final FunctionDefinition function) { _functions.put(function.getUniqueId(), function); } @Override public Collection<FunctionDefinition> getAllFunctions() { return _functions.values(); } @Override public FunctionDefinition getFunction(final String uniqueId) { return _functions.get(uniqueId); } } protected void initializeImpl(final long initId, final Collection<FunctionDefinition> functions) { final OperationTimer timer = new OperationTimer(s_logger, "Initializing {} function definitions", functions.size()); _reinitializingFunctionDefinitions.clear(); _reinitializingFunctionRequirements.clear(); final StaticFunctionRepository initialized = new StaticFunctionRepository(_initializedFunctionRepository); final PoolExecutor.Service<FunctionDefinition> jobs = getExecutorService().createService(new CompletionListener<FunctionDefinition>() { @Override public void success(final FunctionDefinition function) { if (function != null) { synchronized (initialized) { initialized.add(function); } } } @Override public void failure(final Throwable error) { s_logger.warn("Couldn't initialize function", error); // Don't take any further action - the error has been logged and the function is not in the "initialized" set } }); getFunctionCompilationContext().setFunctionReinitializer(_reinitializer); getFunctionCompilationContext().setFunctionInitId(initId); synchronized (initialized) { for (final FunctionDefinition definition : functions) { initialized.remove(definition); } } for (final FunctionDefinition definition : functions) { jobs.execute(new Callable<FunctionDefinition>() { @Override public FunctionDefinition call() { try { definition.init(getFunctionCompilationContext()); return definition; } catch (final UnsupportedOperationException e) { s_logger.warn("Function {}, is not supported in this configuration - {}", definition.getUniqueId(), e.getMessage()); s_logger.info("Caught exception", e); return null; } catch (final Exception e) { s_logger.error("Couldn't initialize function {}", definition.getUniqueId()); throw new OpenGammaRuntimeException("Couldn't initialize " + definition.getShortName(), e); } } }); } try { jobs.join(); } catch (final InterruptedException e) { Thread.interrupted(); s_logger.warn("Interrupted while initializing function definitions."); throw new OpenGammaRuntimeException("Interrupted while initializing function definitions. ViewProcessor not safe to use."); } _initializedFunctionRepository = initialized; getFunctionCompilationContext().setFunctionReinitializer(null); timer.finished(); } /** * Initializes all functions. * * @return the set of object identifiers that should trigger re-initialization */ public Set<ObjectId> initialize() { return initialize(System.currentTimeMillis()); } /** * Initializes all functions. * * @param initId the initialization identifier * @return the set of object identifiers that should trigger re-initialization */ public synchronized Set<ObjectId> initialize(final long initId) { s_logger.info("Initializing all function definitions to {}", initId); _rawFunctionRepository = getFunctionRepositoryFactory().constructRepository(Instant.ofEpochMilli(initId)); _initializedFunctionRepository = null; initializeImpl(initId, _rawFunctionRepository.getAllFunctions()); return _reinitializingFunctionRequirements; } private void reinitializeImpl(final long initId) { final FunctionRepository newFunctionRepository = getFunctionRepositoryFactory().constructRepository(Instant.ofEpochMilli(initId)); if (newFunctionRepository == _rawFunctionRepository) { // Same repository; just reinitialization of some functions final Set<FunctionDefinition> reinitialize = _reinitializingFunctionDefinitions; if (reinitialize.isEmpty()) { s_logger.warn("No functions registered for re-initialization"); getFunctionCompilationContext().setFunctionInitId(initId); } else { initializeImpl(initId, new ArrayList<FunctionDefinition>(reinitialize)); } } else { // Different repository; full initialization _rawFunctionRepository = newFunctionRepository; _initializedFunctionRepository = null; initializeImpl(initId, newFunctionRepository.getAllFunctions()); } } public synchronized void reinitializeIfNeeded(final long initId) { if (getFunctionCompilationContext().getFunctionInitId() != initId) { s_logger.info("Re-initializing function definitions - was {} required {}", getFunctionCompilationContext().getFunctionInitId(), initId); reinitializeImpl(initId); } } /** * Re-initializes functions that requested re-initialization during their previous initialization. * * @return the set of unique identifiers requested by any initialized functions that should trigger re-initialization */ public synchronized Set<ObjectId> reinitialize() { long initId = System.currentTimeMillis(); while (getFunctionCompilationContext().getFunctionInitId() == initId) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new OpenGammaRuntimeException("Interrupted", e); } initId = System.currentTimeMillis(); } s_logger.info("Re-initializing all function definitions to {}", initId); reinitializeImpl(initId); return _reinitializingFunctionRequirements; } /** * Returns the source of the underlying (raw) function repository. The repository sourced from here will be used during initialization operations to populate the {@link #getFunctionRepository} and * {@link #getInitializedFunctionRepository} values. * * @return the source of the underlying function repository, not null */ public FunctionRepositoryFactory getFunctionRepositoryFactory() { return _functionRepositoryFactory; } /** * Returns the underlying (raw) function repository. Definitions in the repository may or may not be properly initialized. If functions are needed that can be reliably used, use * {@link #getInitializedFunctionRepository} instead. * * @return the function repository, not null */ public synchronized FunctionRepository getFunctionRepository() { return _rawFunctionRepository; } /** * Returns a repository of initialized functions. This may be a subset of the underlying (raw) repository if one or more threw exceptions during their {@link FunctionDefinition#init} calls. * * @return the function repository, not null */ public synchronized FunctionRepository getInitializedFunctionRepository() { return _initializedFunctionRepository; } public FunctionRepositoryCompiler getFunctionRepositoryCompiler() { return _functionRepositoryCompiler; } public FunctionCompilationContext getFunctionCompilationContext() { return _functionCompilationContext; } public CompiledFunctionRepository compileFunctionRepository(final long timestamp) { final FunctionCompilationContext context = getFunctionCompilationContext(); // TODO: [PLAT-2761] Temporary hack until the correct version/correction is passed in context.setComputationTargetResolver(context.getRawComputationTargetResolver().atVersionCorrection(VersionCorrection.LATEST)); return getFunctionRepositoryCompiler().compile(getInitializedFunctionRepository(), context, getExecutorService(), Instant.ofEpochMilli(timestamp)); } public CompiledFunctionRepository compileFunctionRepository(final Instant timestamp) { final FunctionCompilationContext context = getFunctionCompilationContext(); // TODO: [PLAT-2761] Temporary hack until the correct version/correction is passed in context.setComputationTargetResolver(context.getRawComputationTargetResolver().atVersionCorrection(VersionCorrection.LATEST)); return getFunctionRepositoryCompiler().compile(getInitializedFunctionRepository(), context, getExecutorService(), timestamp); } public PoolExecutor getExecutorService() { return _executorService; } @Override public CompiledFunctionService clone() { return new CompiledFunctionService(getFunctionRepositoryFactory(), getFunctionRepositoryCompiler(), getFunctionCompilationContext().clone()); } // Lifecycle @Override public synchronized void start() { if (_executorService == null) { _executorService = createExecutorService(); } } @Override public void stop() { final PoolExecutor executor; synchronized (this) { executor = _executorService; _executorService = null; } if (executor != null) { executor.stop(); } } @Override public boolean isRunning() { return _executorService != null; } }