/**
* 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.ArrayDeque;
import java.util.Comparator;
import java.util.Map;
import java.util.Queue;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.util.PoolExecutor;
import com.opengamma.util.PoolExecutor.CompletionListener;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Implements a {@link FunctionRepositoryCompiler} that caches the results of previous compilations so that a minimal compilation is performed each time.
*/
public class CachingFunctionRepositoryCompiler implements FunctionRepositoryCompiler {
private static final Logger s_logger = LoggerFactory.getLogger(CachingFunctionRepositoryCompiler.class);
private static final Comparator<Pair<FunctionRepository, Instant>> s_comparator = new Comparator<Pair<FunctionRepository, Instant>>() {
@Override
public int compare(final Pair<FunctionRepository, Instant> o1, final Pair<FunctionRepository, Instant> o2) {
if (o1 == o2) {
return 0;
}
if (o1.getFirst() == o2.getFirst()) {
// Same repository, order by timestamp
return o1.getSecond().compareTo(o2.getSecond());
}
// If the repositories aren't equal, just need a deterministic ordering
final int hc1 = o1.getFirst().hashCode();
final int hc2 = o2.getFirst().hashCode();
if (hc1 < hc2) {
return -1;
}
if (hc1 > hc2) {
return 1;
}
return o1.getSecond().compareTo(o2.getSecond());
}
};
private final TreeMap<Pair<FunctionRepository, Instant>, InMemoryCompiledFunctionRepository> _compilationCache = new TreeMap<Pair<FunctionRepository, Instant>, InMemoryCompiledFunctionRepository>(
s_comparator);
private final Queue<Pair<FunctionRepository, Instant>> _activeEntries = new ArrayDeque<Pair<FunctionRepository, Instant>>();
private int _cacheSize = 16;
private long _functionInitId;
private final ConcurrentMap<Pair<FunctionRepository, Instant>, Callable<CompiledFunctionRepository>> _activeCompilations =
new ConcurrentHashMap<Pair<FunctionRepository, Instant>, Callable<CompiledFunctionRepository>>();
public synchronized void setCacheSize(final int cacheSize) {
_cacheSize = cacheSize;
while (_activeEntries.size() > cacheSize) {
_compilationCache.remove(_activeEntries.remove());
}
}
public int getCacheSize() {
return _cacheSize;
}
protected TreeMap<Pair<FunctionRepository, Instant>, InMemoryCompiledFunctionRepository> getCompilationCache() {
return _compilationCache;
}
protected Queue<Pair<FunctionRepository, Instant>> getActiveCacheEntries() {
return _activeEntries;
}
protected boolean addFunctionFromCachedRepository(final InMemoryCompiledFunctionRepository before, final InMemoryCompiledFunctionRepository after, final InMemoryCompiledFunctionRepository compiled,
final FunctionDefinition function, final Instant atInstant) {
if (before != null) {
final CompiledFunctionDefinition compiledFunction = before.findDefinition(function.getUniqueId());
if (compiledFunction != null) {
if (compiledFunction.getLatestInvocationTime() == null) {
// previous one always valid
compiled.addFunction(compiledFunction);
return true;
} else {
final Instant validUntil = compiledFunction.getLatestInvocationTime();
if (!validUntil.isBefore(atInstant)) {
// previous one still valid
compiled.addFunction(compiledFunction);
return true;
}
}
}
}
if (after != null) {
final CompiledFunctionDefinition compiledFunction = after.findDefinition(function.getUniqueId());
if (compiledFunction != null) {
if (compiledFunction.getEarliestInvocationTime() == null) {
// next one always valid
compiled.addFunction(compiledFunction);
return true;
} else {
final Instant validFrom = compiledFunction.getEarliestInvocationTime();
if (!validFrom.isAfter(atInstant)) {
// next one already valid
compiled.addFunction(compiledFunction);
return true;
}
}
}
}
return false;
}
protected InMemoryCompiledFunctionRepository compile(final FunctionCompilationContext context, final FunctionRepository functions, final Instant atInstant,
final InMemoryCompiledFunctionRepository before, final InMemoryCompiledFunctionRepository after, final PoolExecutor poolExecutor) {
final InMemoryCompiledFunctionRepository compiled = new InMemoryCompiledFunctionRepository(context);
final AtomicInteger failures = new AtomicInteger();
final PoolExecutor.Service<CompiledFunctionDefinition> jobs = poolExecutor.createService(new CompletionListener<CompiledFunctionDefinition>() {
@Override
public void success(final CompiledFunctionDefinition result) {
compiled.addFunction(result);
}
@Override
public void failure(final Throwable error) {
// Don't propagate the error outwards; it just won't be in the compiled repository
s_logger.debug("Error compiling function definition", error);
failures.incrementAndGet();
}
});
for (final FunctionDefinition function : functions.getAllFunctions()) {
if (addFunctionFromCachedRepository(before, after, compiled, function, atInstant)) {
continue;
}
jobs.execute(new Callable<CompiledFunctionDefinition>() {
@Override
public CompiledFunctionDefinition call() throws Exception {
try {
s_logger.debug("Compiling {}", function);
return function.compile(context, atInstant);
} catch (final Exception e) {
s_logger.warn("Compiling {} threw {}", function.getShortName(), e);
throw e;
}
}
});
}
try {
jobs.join();
} catch (InterruptedException e) {
Thread.interrupted();
throw new OpenGammaRuntimeException("Interrupted while compiling function definitions.");
}
if (failures.get() != 0) {
s_logger.error("Encountered {} errors while compiling repository", failures);
}
return compiled;
}
protected synchronized InMemoryCompiledFunctionRepository getCachedCompilation(final Pair<FunctionRepository, Instant> key) {
return getCompilationCache().get(key);
}
protected synchronized InMemoryCompiledFunctionRepository getPreviousCompilation(final Pair<FunctionRepository, Instant> key) {
final Map.Entry<Pair<FunctionRepository, Instant>, InMemoryCompiledFunctionRepository> entry = getCompilationCache().lowerEntry(key);
if ((entry != null) && (entry.getKey().getFirst() == key.getFirst())) {
return entry.getValue();
}
return null;
}
protected synchronized InMemoryCompiledFunctionRepository getNextCompilation(final Pair<FunctionRepository, Instant> key) {
final Map.Entry<Pair<FunctionRepository, Instant>, InMemoryCompiledFunctionRepository> entry = getCompilationCache().higherEntry(key);
if ((entry != null) && (entry.getKey().getFirst() == key.getFirst())) {
return entry.getValue();
}
return null;
}
protected synchronized void cacheCompilation(final Pair<FunctionRepository, Instant> key, final InMemoryCompiledFunctionRepository compiled) {
final Queue<Pair<FunctionRepository, Instant>> active = getActiveCacheEntries();
if (active.size() >= getCacheSize()) {
getCompilationCache().remove(active.remove());
}
getCompilationCache().put(key, compiled);
getActiveCacheEntries().add(key);
}
@Override
public CompiledFunctionRepository compile(final FunctionRepository repository, final FunctionCompilationContext context, final PoolExecutor executor, final Instant atInstant) {
clearInvalidCache(context.getFunctionInitId());
final Pair<FunctionRepository, Instant> key = Pairs.of(repository, atInstant);
// Try a previous compilation
final InMemoryCompiledFunctionRepository previous = getPreviousCompilation(key);
if (previous != null) {
if (previous.getLatestInvocationTime() == null) {
return previous;
} else {
if (!atInstant.isAfter(previous.getLatestInvocationTime())) {
return previous;
}
}
}
// Try a future compilation
final InMemoryCompiledFunctionRepository next = getNextCompilation(key);
if (next != null) {
if (next.getEarliestInvocationTime() == null) {
return next;
} else {
if (!atInstant.isBefore(next.getEarliestInvocationTime())) {
return next;
}
}
}
// Try the exact timestamp
InMemoryCompiledFunctionRepository compiled = getCachedCompilation(key);
if (compiled != null) {
return compiled;
}
// Create a compilation, salvaging results from previous and next if possible
final CompiledFunctionRepository[] ref = new CompiledFunctionRepository[1];
final Callable<CompiledFunctionRepository> existing = _activeCompilations.putIfAbsent(key, new Callable<CompiledFunctionRepository>() {
@Override
public CompiledFunctionRepository call() throws Exception {
CompiledFunctionRepository repository;
synchronized (ref) {
repository = ref[0];
while (repository == null) {
s_logger.info("Waiting for concurrent call to compile {}", atInstant);
ref.wait();
repository = ref[0];
}
}
return repository;
}
});
if (existing != null) {
try {
s_logger.info("Using concurrent call to compile {}", atInstant);
return existing.call();
} catch (final Exception e) {
throw new OpenGammaRuntimeException("Exception from concurrent call", e);
}
}
compiled = compile(context, repository, atInstant, previous, next, executor);
cacheCompilation(key, compiled);
synchronized (ref) {
ref[0] = compiled;
ref.notifyAll();
}
_activeCompilations.remove(key);
return compiled;
}
protected synchronized void clearInvalidCache(final Long initId) {
if ((initId != null) && (_functionInitId != initId)) {
getCompilationCache().clear();
_functionInitId = initId;
}
}
}