/** * Copyright (C) 2011 - 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.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Maps; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; /* package */final class FunctionApplicationWorker extends DirectResolvedValueProducer implements ResolvedValueCallback { private static final Logger s_logger = LoggerFactory.getLogger(FunctionApplicationWorker.class); private Map<ValueRequirement, ValueSpecification> _inputs = new HashMap<ValueRequirement, ValueSpecification>(); private Map<ValueRequirement, Cancelable> _inputHandles = new HashMap<ValueRequirement, Cancelable>(); private Collection<ResolutionPump> _pumps = new ArrayList<ResolutionPump>(); private int _pendingInputs; private int _validInputs; private boolean _invokingFunction; private boolean _deferredPump; private FunctionApplicationStep.PumpingState _taskState; public FunctionApplicationWorker(final ValueRequirement valueRequirement) { super(valueRequirement); } @Override protected void pumpImpl(final GraphBuildingContext context) { ResolutionPump[] pumps = null; FunctionApplicationStep.PumpingState finished = null; int refCount = 0; synchronized (this) { if (_invokingFunction) { s_logger.debug("Deferring pump until after function invocation"); _deferredPump = true; return; } if (_pendingInputs < 1) { if ((_pumps == null) || _pumps.isEmpty()) { s_logger.debug("{} finished (state={})", this, _taskState); finished = _taskState; _taskState = null; refCount = getRefCount(); } else { final int s = _pumps.size(); pumps = _pumps.toArray(new ResolutionPump[s]); _pumps.clear(); _pendingInputs = s; } } else { // If the state performs a direct pump as well as the trigger from AbstractResolvedValueProducer // on behalf of a subscriber, no action is needed. return; } } if (pumps != null) { for (ResolutionPump pump : pumps) { s_logger.debug("Pumping {} from {}", pump, this); context.pump(pump); } } else if (finished != null) { s_logger.debug("Finished producing function applications from {}", this); // If the last result was already pushed then finished will have already been called if (!isFinished()) { finished(context); } s_logger.debug("Calling finished on {} from {}", finished, this); finished.finished(context, refCount); } } @Override protected void finished(final GraphBuildingContext context) { super.finished(context); Collection<Cancelable> unsubscribes = null; Collection<ResolutionPump> pumps = null; synchronized (this) { _inputs = null; if (_inputHandles != null) { if (!_inputHandles.isEmpty()) { unsubscribes = _inputHandles.values(); } _inputHandles = null; } if (_pumps != null) { if (!_pumps.isEmpty()) { pumps = _pumps; } _pumps = null; } } if (unsubscribes != null) { for (Cancelable unsubscribe : unsubscribes) { if (unsubscribe != null) { unsubscribe.cancel(context); } } } if (pumps != null) { for (ResolutionPump pump : pumps) { context.close(pump); } } } // Caller must hold the monitor private Map<ValueSpecification, ValueRequirement> createResolvedValuesMap() { final Map<ValueSpecification, ValueRequirement> resolvedValues = Maps.<ValueSpecification, ValueRequirement>newHashMapWithExpectedSize(_inputs.size()); for (Map.Entry<ValueRequirement, ValueSpecification> input : _inputs.entrySet()) { assert input.getValue() != null; resolvedValues.put(input.getValue(), input.getKey()); } return resolvedValues; } private void inputsAvailable(final GraphBuildingContext context, final FunctionApplicationStep.PumpingState state, final Map<ValueSpecification, ValueRequirement> resolvedValues) { if (resolvedValues != null) { final boolean lastResult; synchronized (this) { if (_pumps == null) { // Already aborted/finished return; } lastResult = _pumps.isEmpty(); } boolean inputsAccepted = state.inputsAvailable(context, resolvedValues, lastResult); synchronized (this) { _invokingFunction = false; if (_deferredPump) { inputsAccepted = false; _deferredPump = false; } } if (!inputsAccepted) { pumpImpl(context); } } } @Override public void failed(final GraphBuildingContext context, final ValueRequirement value, final ResolutionFailure failure) { s_logger.debug("Resolution of {} failed at {}", value, this); FunctionApplicationStep.PumpingState state = null; int refCount = 0; ResolutionFailure requirementFailure = null; final Map<ValueSpecification, ValueRequirement> resolvedValues; do { final Collection<Cancelable> unsubscribes; synchronized (this) { _pendingInputs--; _validInputs--; if (_inputHandles != null) { _inputHandles.remove(value); if (_inputs.get(value) == null) { s_logger.info("Resolution of {} failed", value); if (_taskState != null) { if (_taskState.canHandleMissingInputs()) { state = _taskState; requirementFailure = state.functionApplication(context).requirement(value, null); _inputs.remove(value); if (_pendingInputs == 0) { // Got as full a set of inputs as we're going to get; notify the task state resolvedValues = createResolvedValuesMap(); _invokingFunction = true; s_logger.debug("Partial input set available"); } else { resolvedValues = null; if (s_logger.isDebugEnabled()) { s_logger.debug("Waiting for {} other inputs in {}", _pendingInputs, _inputs); } // Fall through so that failure is logged } break; } else { requirementFailure = _taskState.functionApplication(context).requirement(value, failure); } } } else { if (_taskState != null) { // Might be okay - we've already had at least one value for this requirement if (_pendingInputs > 0) { // Ok; still waiting on other inputs if (s_logger.isDebugEnabled()) { s_logger.debug("{} other inputs still pending", _pendingInputs); } return; } if (_validInputs > 0) { // Ok; we've got other new values to push up state = _taskState; resolvedValues = createResolvedValuesMap(); _invokingFunction = true; if (s_logger.isDebugEnabled()) { s_logger.debug("Partial input set available on {} new inputs", _validInputs); } break; } } } if (_inputHandles.isEmpty()) { unsubscribes = null; } else { unsubscribes = _inputHandles.values(); } _inputHandles = null; } else { unsubscribes = null; } state = _taskState; if (state != null) { _taskState = null; refCount = getRefCount(); } } // Not ok; we either couldn't satisfy anything or the pumped enumeration is complete s_logger.info("{} complete", this); if (state != null) { state.storeFailure(requirementFailure); } storeFailure(requirementFailure); // Unsubscribe from any inputs that are still valid if (unsubscribes != null) { if (s_logger.isDebugEnabled()) { s_logger.debug("Unsubscribing from {} handles", unsubscribes.size()); } for (Cancelable handle : unsubscribes) { if (handle != null) { handle.cancel(context); } } } // Propagate the failure message to anything subscribing to us if (state != null) { finished(context); s_logger.debug("Calling finished on {}", state); state.finished(context, refCount); } return; } while (false); // Ok; partial results may be available if (state != null) { state.storeFailure(requirementFailure); } storeFailure(requirementFailure); inputsAvailable(context, state, resolvedValues); } protected void discard(final GraphBuildingContext context) { Collection<Cancelable> unsubscribes = null; final Collection<ResolutionPump> pumps; synchronized (this) { _taskState = null; if (_inputs == null) { // Already discarded/finished return; } _inputs = null; if (_inputHandles != null) { if (!_inputHandles.isEmpty()) { unsubscribes = _inputHandles.values(); } _inputHandles = null; } if (_pumps.isEmpty()) { pumps = null; } else { pumps = _pumps; } _pumps = null; } if (unsubscribes != null) { for (Cancelable unsubscribe : unsubscribes) { if (unsubscribe != null) { unsubscribe.cancel(context); } } } if (pumps != null) { for (ResolutionPump pump : pumps) { context.close(pump); } } } @Override public void resolved(final GraphBuildingContext context, final ValueRequirement valueRequirement, final ResolvedValue resolvedValue, ResolutionPump pump) { s_logger.debug("Resolution complete at {}", this); s_logger.info("Resolved {} to {}", valueRequirement, resolvedValue); FunctionApplicationStep.PumpingState state = null; Map<ValueSpecification, ValueRequirement> resolvedValues = null; synchronized (this) { if (_taskState != null) { _inputs.put(valueRequirement, resolvedValue.getValueSpecification()); if (--_pendingInputs == 0) { // We've got a full (or partial) set of inputs; notify the task state resolvedValues = createResolvedValuesMap(); state = _taskState; _invokingFunction = true; s_logger.debug("Full input set available"); } else { if (s_logger.isDebugEnabled()) { s_logger.debug("Waiting for {} other inputs in {}", _pendingInputs, _inputs); } } if (pump != null) { _pumps.add(pump); pump = null; } } else { s_logger.debug("Already aborted resolution"); } } if (pump != null) { context.close(pump); } inputsAvailable(context, state, resolvedValues); } @Override protected void storeFailure(final ResolutionFailure failure) { final FunctionApplicationStep.PumpingState state; synchronized (this) { if (_pumps == null) { // Already discarded; don't bother storing the failure information return; } state = _taskState; } if (state != null) { state.storeFailure(failure); } super.storeFailure(failure); } public void addInput(final GraphBuildingContext context, final ResolvedValueProducer inputProducer) { final ValueRequirement valueRequirement = inputProducer.getValueRequirement(); s_logger.debug("Adding input {} to {}", valueRequirement, this); synchronized (this) { if ((_inputs == null) || (_inputHandles == null)) { // Already aborted or something has already failed _validInputs--; return; } _inputs.put(valueRequirement, null); _inputHandles.put(valueRequirement, null); _pendingInputs++; } final Cancelable handle = inputProducer.addCallback(context, this); synchronized (this) { if (_inputHandles != null) { if (handle != null) { // Only store the handle if the producer hasn't already failed if (_inputHandles.containsKey(valueRequirement)) { _inputHandles.put(valueRequirement, handle); } } else { // Remove the handle placeholder if the producer didn't give us a handle _inputHandles.remove(valueRequirement); } return; } } if (handle != null) { handle.cancel(context); } } @Override public void recursionDetected() { // No-op } @Override protected void setRecursionDetected() { super.setRecursionDetected(); synchronized (this) { _validInputs--; } } public void setPumpingState(final FunctionApplicationStep.PumpingState state, final int validInputs) { synchronized (this) { _taskState = state; _validInputs = validInputs; _pendingInputs = (validInputs > 0) ? 1 : 0; final int rc = getRefCount(); for (int i = 0; i < rc; i++) { state.getTask().addRef(); } } } public void start(final GraphBuildingContext context) { s_logger.debug("Starting {}", this); FunctionApplicationStep.PumpingState state = null; Map<ValueSpecification, ValueRequirement> resolvedValues = null; FunctionApplicationStep.PumpingState finished = null; int refCount = 0; boolean lastResult = false; synchronized (this) { if (_taskState != null) { if (--_pendingInputs == 0) { if (_validInputs > 0) { // We've got a full set of inputs; notify the task state resolvedValues = createResolvedValuesMap(); lastResult = _pumps.isEmpty(); state = _taskState; _invokingFunction = true; s_logger.debug("Full input set available at {}", this); } else { // We've got no valid inputs assert _pumps.isEmpty(); if (_taskState.canHandleMissingInputs()) { // Empty input set; notify the task state resolvedValues = Collections.emptyMap(); lastResult = true; state = _taskState; _invokingFunction = true; s_logger.debug("Empty input set available at {}", this); } else { s_logger.debug("No input set available, finished at {}", this); finished = _taskState; _taskState = null; refCount = getRefCount(); } } } } else { s_logger.debug("Already aborted resolution"); } } if (resolvedValues != null) { boolean inputsAccepted = state.inputsAvailable(context, resolvedValues, lastResult); synchronized (this) { _invokingFunction = false; if (_deferredPump) { inputsAccepted = false; _deferredPump = false; } } if (!inputsAccepted) { pumpImpl(context); } } else if (finished != null) { s_logger.debug("Calling finished on {} from {}", finished, this); finished(context); finished.finished(context, refCount); } } @Override public String toString() { return "Worker" + getObjectId() + "[" + getValueRequirement() + "]"; } @Override public synchronized boolean addRef() { if (super.addRef()) { if (_taskState != null) { _taskState.getTask().addRef(); } return true; } else { return false; } } @Override public int release(final GraphBuildingContext context) { final int count; FunctionApplicationStep.PumpingState state; // If setPumpingState is scheduled here, the task will include the caller's open-reference in its refCount. We'll see the // non-null state and issue a net release. synchronized (this) { state = _taskState; if (state != null) { // Hold an open reference state.getTask().addRef(); } count = super.release(context); } // If setPumpingState is scheduled here, the task will not include the caller's open-reference in its refCount. We'll see // the null state however and take no action. if (state != null) { state.getTask().release(context); synchronized (this) { state = _taskState; } // If the finished state was entered (state == null), the task will have seen a full set of releases from there plus the // explicit one above. The explicit one will balance against the reference we added before starting. If the finished state // was not entered then we need to clear the reference we added above. if (state != null) { state.getTask().release(context); } } return count; } @Override public int cancelLoopMembers(final GraphBuildingContext context, final Map<Chain, Chain.LoopState> visited) { FunctionApplicationStep.PumpingState state; synchronized (this) { state = _taskState; } int result = super.cancelLoopMembers(context, visited); if (state != null) { result += state.cancelLoopMembersImpl(context, visited); } return result; } }