/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.depgraph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.engine.depgraph.ResolvedValueCallback.ResolvedValueCallbackChain; import com.opengamma.engine.value.ValueRequirement; /* package */class AggregateResolvedValueProducer extends AbstractResolvedValueProducer implements ResolvedValueCallbackChain { private static final Logger s_logger = LoggerFactory.getLogger(AggregateResolvedValueProducer.class); private int _pendingTasks = 1; private boolean _wantResult = true; private final List<ResolutionPump> _pumps = new ArrayList<ResolutionPump>(); public AggregateResolvedValueProducer(final ValueRequirement valueRequirement) { super(valueRequirement); } /** * Returns the number of pending tasks. The caller must hold the monitor. */ protected int getPendingTasks() { return _pendingTasks; } @Override public void failed(final GraphBuildingContext context, final ValueRequirement value, final ResolutionFailure failure) { s_logger.debug("Failed on {} for {}", value, this); Collection<ResolutionPump> pumps = null; synchronized (this) { if (_pendingTasks == Integer.MIN_VALUE) { // We were discarded after we requested the callback s_logger.debug("Failed resolution after discard of {}", this); return; } assert _pendingTasks > 0; if (--_pendingTasks == 0) { if (_wantResult) { s_logger.debug("Pumping underlying after last input failed for {}", this); pumps = pumpImpl(); } else { s_logger.debug("No pending tasks after last input failed for {} but no results requested", this); } } else { if (s_logger.isDebugEnabled()) { s_logger.debug("{} pending tasks for {}", _pendingTasks, this); } } } storeFailure(failure); pumpImpl(context, pumps); } /** * Tests if the result about to be pushed from {@link #resolved} can be considered the "last result". The result has come from the last pending task. The default behavior is to return true but a * sub-class that hooks the {@link #finished} call to introduce more productions must return false to avoid an intermediate last result being passed to the consumer of this aggregate. * <p> * This is called holding the monitor. * * @return true if the result really is the last result, false otherwise */ protected boolean isLastResult() { return true; } @Override public void resolved(final GraphBuildingContext context, final ValueRequirement valueRequirement, final ResolvedValue value, final ResolutionPump pump) { do { s_logger.debug("Received {} for {}", value, valueRequirement); boolean wantedResult = false; final boolean lastResult; synchronized (this) { if (_pendingTasks == Integer.MIN_VALUE) { // We were discarded after we requested the callback s_logger.debug("Successful resolution after discard of {}", this); break; } assert _pendingTasks > 0; if (_wantResult) { s_logger.debug("Clearing \"want result\" flag for {}", this); wantedResult = true; _wantResult = false; } lastResult = (pump == null) && (_pendingTasks == 1) && _pumps.isEmpty() && isLastResult(); } // Note that the lastResult indicator isn't 100% if there are concurrent calls to resolved. The "last" condition may // not be seen. The alternative would be to serialize the calls through pushResult so that we can guarantee spotting // the final one. if (pushResult(context, value, lastResult)) { Collection<ResolutionPump> pumps = null; synchronized (this) { if (_pendingTasks == Integer.MIN_VALUE) { // We were discarded while the result was handled s_logger.debug("Discard of {} while pushing result", this); break; } assert _pendingTasks > 0; if (pump != null) { _pumps.add(pump); } if (--_pendingTasks == 0) { if (_wantResult && !lastResult) { s_logger.debug("Pumping underlying after last input resolved for {}", this); pumps = pumpImpl(); } else { s_logger.debug("No pending tasks after last input resolved for {} but no further results requested", this); } } } pumpImpl(context, pumps); } else { if (wantedResult) { synchronized (this) { if (_pendingTasks == Integer.MIN_VALUE) { // We were discarded while the result was rejected s_logger.debug("Discard of {} while pushing rejected result", this); break; } assert _pendingTasks > 0; s_logger.debug("Reinstating \"want result\" flag for {}", this); _wantResult = true; } } if (pump != null) { context.pump(pump); } else { context.failed(this, valueRequirement, null); } } return; } while (false); if (pump != null) { context.close(pump); } } @Override public void recursionDetected() { // No-op by default } @Override protected void pumpImpl(final GraphBuildingContext context) { Collection<ResolutionPump> pumps = null; synchronized (this) { assert _pendingTasks >= 0; if (_pendingTasks == 0) { s_logger.debug("Pumping underlying since no pending tasks for {}", this); pumps = pumpImpl(); } else { if (s_logger.isDebugEnabled()) { s_logger.debug("Deferring pump while {} task(s) pending for {}", _pendingTasks, this); } _wantResult = true; } } pumpImpl(context, pumps); } // Caller must hold the monitor private Collection<ResolutionPump> pumpImpl() { if (_pumps.isEmpty()) { return Collections.emptyList(); } else { final List<ResolutionPump> pumps = new ArrayList<ResolutionPump>(_pumps); _pumps.clear(); _pendingTasks = pumps.size(); _wantResult = true; return pumps; } } private void pumpImpl(final GraphBuildingContext context, final Collection<ResolutionPump> pumps) { if (pumps != null) { if (pumps.isEmpty()) { // We have nothing to pump, so must have finished (failed) s_logger.debug("Finished {}", this); finished(context); } else { if (s_logger.isDebugEnabled()) { s_logger.debug("Pumping {} origin tasks from {}", pumps.size(), this); } for (ResolutionPump pump : pumps) { context.pump(pump); } } } } public void addProducer(final GraphBuildingContext context, final ResolvedValueProducer producer) { synchronized (this) { if (_pendingTasks == Integer.MIN_VALUE) { s_logger.debug("Discarded before fallback producer {} added to {}", producer, this); return; } assert _pendingTasks >= 0; if (_pendingTasks == 0) { _wantResult = true; } _pendingTasks++; if (s_logger.isDebugEnabled()) { s_logger.debug("{} pending tasks for {}", _pendingTasks, this); } } producer.addCallback(context, this); } public void start(final GraphBuildingContext context) { Collection<ResolutionPump> pumps = null; synchronized (this) { assert _pendingTasks >= 1; if (--_pendingTasks == 0) { if (_wantResult) { s_logger.debug("Pumping underlying after startup tasks completed for {}", this); pumps = pumpImpl(); } else { s_logger.debug("Startup tasks completed for {} but no further results requested", this); } } } pumpImpl(context, pumps); } @Override protected void finished(final GraphBuildingContext context) { assert _pendingTasks <= 1; super.finished(context); } @Override public int release(final GraphBuildingContext context) { final int count = super.release(context); if (count == 0) { List<ResolutionPump> pumps; synchronized (this) { if (s_logger.isDebugEnabled()) { s_logger.debug("Releasing {} - with {} pumped inputs", this, _pumps.size()); } // If _pendingTasks > 0 then there may be calls to failure or resolved from one or more of them. Setting _pendingTasks to // Integer.MIN_VALUE means we can detect these and discard them. Our reference count is zero so nothing subscribing to us // cares. _pendingTasks = Integer.MIN_VALUE; if (_pumps.isEmpty()) { return count; } pumps = new ArrayList<ResolutionPump>(_pumps); _pumps.clear(); } for (ResolutionPump pump : pumps) { context.close(pump); } } return count; } @Override public String toString() { return "AGGREGATE" + getObjectId(); } }