/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.cache; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.engine.value.ComputedValue; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * A wrapper around an existing {@link ViewComputationCache} implementation that will attempt to buffer data in memory to speed up writes rapidly followed by a read. */ public class WriteThroughViewComputationCache implements ViewComputationCache { private static final Logger s_logger = LoggerFactory.getLogger(WriteThroughViewComputationCache.class); private static final Object NULL = new Object(); private static final Object PENDING = new Object(); private static final ConcurrentMap<ViewComputationCache, WriteThroughViewComputationCache> s_instances = new MapMaker().weakKeys().weakValues().makeMap(); /* package */static final class Pending { private final ValueSpecification _specification; private Object _value; public Pending(final ValueSpecification specification) { _specification = specification; } public synchronized Object waitFor() { try { s_logger.debug("Waiting for {}", _specification); while (_value == null) { wait(); } return _value; } catch (InterruptedException e) { throw new OpenGammaRuntimeException("Interrupted", e); } } public Pair<ValueSpecification, Object> waitForPair() { final Object value = waitFor(); if (value == NULL) { return Pairs.of(_specification, null); } else { return Pairs.of(_specification, value); } } public synchronized void post(final Object value) { s_logger.debug("Posting result for {}", _specification); _value = value; notifyAll(); } } private final ViewComputationCache _underlying; private final ConcurrentMap<ValueSpecification, Object> _readCache = new MapMaker().softValues().makeMap(); private final ConcurrentMap<ValueSpecification, Pending> _pending = new MapMaker().weakValues().makeMap(); protected WriteThroughViewComputationCache(final ViewComputationCache underlying) { ArgumentChecker.notNull(underlying, "underlying"); _underlying = underlying; } public static WriteThroughViewComputationCache of(final ViewComputationCache underlying) { WriteThroughViewComputationCache cached = s_instances.get(underlying); if (cached != null) { return cached; } cached = new WriteThroughViewComputationCache(underlying); final WriteThroughViewComputationCache existing = s_instances.putIfAbsent(underlying, cached); if (existing == null) { return cached; } else { return existing; } } /** * This method will clear all instances of this class. It should be called after confirming with OpenGamma support that it is necessary to handle certain memory situations regarding custom View * Processor configurations. */ public static void clearAllWriteThroughCaches() { for (WriteThroughViewComputationCache cache : s_instances.values()) { cache.clear(); } s_instances.clear(); } public void clear() { _readCache.clear(); } protected ViewComputationCache getUnderlying() { return _underlying; } protected Pending waitFor(final ValueSpecification specification) { final Pending newPending = new Pending(specification); final Pending existingPending = _pending.putIfAbsent(specification, newPending); if (existingPending == null) { return newPending; } else { return existingPending; } } protected void post(final ValueSpecification specification, Object value) { if (value == null) { value = NULL; } _readCache.put(specification, value); Pending pending = _pending.remove(specification); if (pending != null) { pending.post(value); } } @Override public Object getValue(final ValueSpecification specification) { Object value = _readCache.putIfAbsent(specification, PENDING); if (value == PENDING) { final Pending pending = waitFor(specification); value = _readCache.get(specification); if (value == PENDING) { value = pending.waitFor(); } } if (value == NULL) { //s_logger.debug("Cached NULL for {}", specification); value = null; } else if (value == null) { //s_logger.debug("Cached miss for {}", specification); value = getUnderlying().getValue(specification); post(specification, value); } else { s_logger.debug("Cache hit for {}", specification); } return value; } @Override public Object getValue(final ValueSpecification specification, final CacheSelectHint filter) { Object value = _readCache.putIfAbsent(specification, PENDING); if (value == PENDING) { final Pending pending = waitFor(specification); value = _readCache.get(specification); if (value == PENDING) { value = pending.waitFor(); } } if (value == NULL) { //s_logger.debug("Cached NULL for {}", specification); value = null; } else if (value == null) { //s_logger.debug("Cached miss for {}", specification); value = getUnderlying().getValue(specification, filter); post(specification, value); } else { s_logger.debug("Cache hit for {}", specification); } return value; } @Override public Collection<Pair<ValueSpecification, Object>> getValues(final Collection<ValueSpecification> specifications) { final Collection<Pair<ValueSpecification, Object>> result = new ArrayList<Pair<ValueSpecification, Object>>(specifications.size()); Collection<Pending> pending = null; Collection<ValueSpecification> query = null; for (ValueSpecification specification : specifications) { Object value = _readCache.putIfAbsent(specification, PENDING); if (value == PENDING) { final Pending handle = waitFor(specification); value = _readCache.get(specification); if (value == PENDING) { if (pending == null) { pending = new ArrayList<Pending>(specifications.size()); } pending.add(handle); continue; } } if (value == NULL) { //s_logger.debug("Cached NULL for {}", specification); result.add(Pairs.of(specification, null)); } else if (value == null) { //s_logger.debug("Cache miss for {}", specification); if (query == null) { query = Sets.<ValueSpecification>newHashSetWithExpectedSize(specifications.size()); } query.add(specification); } else { s_logger.debug("Cache hit for {}", specification); result.add(Pairs.of(specification, value)); } } if (query != null) { final Collection<Pair<ValueSpecification, Object>> values = getUnderlying().getValues(query); for (Pair<ValueSpecification, Object> value : values) { final ValueSpecification valueSpec = value.getFirst(); post(valueSpec, value.getSecond()); query.remove(valueSpec); } result.addAll(values); if (!query.isEmpty()) { for (ValueSpecification value : query) { post(value, null); } } } if (pending != null) { for (Pending handle : pending) { result.add(handle.waitForPair()); } } return result; } @Override public Collection<Pair<ValueSpecification, Object>> getValues(final Collection<ValueSpecification> specifications, final CacheSelectHint filter) { final Collection<Pair<ValueSpecification, Object>> result = new ArrayList<Pair<ValueSpecification, Object>>(specifications.size()); Collection<Pending> pending = null; Collection<ValueSpecification> query = null; for (ValueSpecification specification : specifications) { Object value = _readCache.putIfAbsent(specification, PENDING); if (value == PENDING) { final Pending handle = waitFor(specification); value = _readCache.get(specification); if (value == PENDING) { if (pending == null) { pending = new ArrayList<Pending>(specifications.size()); } pending.add(handle); continue; } } if (value == NULL) { //s_logger.debug("Cached NULL for {}", specification); result.add(Pairs.of(specification, null)); } else if (value == null) { //s_logger.debug("Cache miss for {}", specification); if (query == null) { query = Sets.<ValueSpecification>newHashSetWithExpectedSize(specifications.size()); } query.add(specification); } else { s_logger.debug("Cache hit for {}", specification); result.add(Pairs.of(specification, value)); } } if (query != null) { final Collection<Pair<ValueSpecification, Object>> values = getUnderlying().getValues(query, filter); for (Pair<ValueSpecification, Object> value : values) { final ValueSpecification valueSpec = value.getFirst(); post(valueSpec, value.getSecond()); query.remove(valueSpec); } result.addAll(values); if (!query.isEmpty()) { for (ValueSpecification value : query) { post(value, null); } } } if (pending != null) { for (Pending handle : pending) { result.add(handle.waitForPair()); } } return result; } @Override public void putSharedValue(final ComputedValue value) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); getUnderlying().putSharedValue(value); } @Override public void putPrivateValue(final ComputedValue value) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); getUnderlying().putPrivateValue(value); } @Override public void putValue(final ComputedValue value, final CacheSelectHint filter) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); getUnderlying().putValue(value, filter); } @Override public void putSharedValues(final Collection<? extends ComputedValue> values) { for (ComputedValue value : values) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); } getUnderlying().putSharedValues(values); } @Override public void putPrivateValues(final Collection<? extends ComputedValue> values) { for (ComputedValue value : values) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); } getUnderlying().putPrivateValues(values); } @Override public void putValues(final Collection<? extends ComputedValue> values, final CacheSelectHint filter) { for (ComputedValue value : values) { _readCache.putIfAbsent(value.getSpecification(), value.getValue()); } getUnderlying().putValues(values, filter); } @Override public Integer estimateValueSize(final ComputedValue value) { return getUnderlying().estimateValueSize(value); } }