/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.view.worker; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.Instant; import com.opengamma.engine.view.ViewComputationResultModel; import com.opengamma.engine.view.ViewDefinition; import com.opengamma.engine.view.compilation.CompiledViewDefinitionWithGraphs; import com.opengamma.engine.view.cycle.ViewCycle; import com.opengamma.engine.view.cycle.ViewCycleMetadata; import com.opengamma.engine.view.execution.ArbitraryViewCycleExecutionSequence; import com.opengamma.engine.view.execution.ExecutionOptions; import com.opengamma.engine.view.execution.ViewCycleExecutionOptions; import com.opengamma.engine.view.execution.ViewCycleExecutionSequence; import com.opengamma.engine.view.execution.ViewExecutionFlags; import com.opengamma.engine.view.execution.ViewExecutionOptions; import com.opengamma.engine.view.impl.ViewProcessContext; /** * Implementation of {@link ViewProcessWorker} for partitioning a sequence and delegating to other workers to handle each partition. */ public class SequencePartitioningViewProcessWorker implements ViewProcessWorker, ViewProcessWorkerContext { private static final Logger s_logger = LoggerFactory.getLogger(SequencePartitioningViewProcessWorker.class); private final ViewProcessWorkerFactory _delegate; private final ViewProcessWorkerContext _context; private final EnumSet<ViewExecutionFlags> _executionFlags; private final Integer _maxSuccessiveDeltaCycles; private final ViewCycleExecutionSequence _sequence; private final ViewCycleExecutionOptions _defaultExecutionOptions; private final Queue<ViewProcessWorker> _workers = new LinkedList<ViewProcessWorker>(); private volatile ViewDefinition _viewDefinition; private int _partition; private boolean _terminated; private int _spawnedWorkerCount; private int _spawnedCycleCount; private int _spawnedWorkers; private int _trigger; public SequencePartitioningViewProcessWorker(final ViewProcessWorkerFactory delegate, final ViewProcessWorkerContext context, final ViewExecutionOptions executionOptions, final ViewDefinition viewDefinition, final int partition, final int maxWorkers) { _delegate = delegate; _context = context; _executionFlags = EnumSet.copyOf(executionOptions.getFlags()); _maxSuccessiveDeltaCycles = executionOptions.getMaxSuccessiveDeltaCycles(); _defaultExecutionOptions = executionOptions.getDefaultExecutionOptions(); _sequence = executionOptions.getExecutionSequence(); _viewDefinition = viewDefinition; _partition = partition; _trigger = maxWorkers; if (!_executionFlags.remove(ViewExecutionFlags.WAIT_FOR_INITIAL_TRIGGER)) { // Kick off first batch of workers triggerCycle(); } } private ViewProcessWorkerFactory getDelegate() { return _delegate; } private ViewProcessWorkerContext getWorkerContext() { return _context; } private EnumSet<ViewExecutionFlags> getExecutionFlags() { return _executionFlags; } private ViewCycleExecutionOptions getDefaultExecutionOptions() { return _defaultExecutionOptions; } private Integer getMaxSuccessiveDeltaCycles() { return _maxSuccessiveDeltaCycles; } private ViewExecutionOptions getExecutionOptions(ViewCycleExecutionSequence newSequence) { return new ExecutionOptions(newSequence, getExecutionFlags(), getMaxSuccessiveDeltaCycles(), getDefaultExecutionOptions()); } private ViewCycleExecutionSequence getSequence() { return _sequence; } private ViewDefinition getViewDefinition() { return _viewDefinition; } private int getPartitionSize() { return _partition; } private synchronized void spawnWorker() { ViewCycleExecutionSequence sequence = getSequence(); final int partitionSize = getPartitionSize(); final List<ViewCycleExecutionOptions> partition = new ArrayList<ViewCycleExecutionOptions>(partitionSize); for (int i = 0; i < partitionSize; i++) { final ViewCycleExecutionOptions step = sequence.poll(getDefaultExecutionOptions()); if (step != null) { partition.add(step); } else { break; } } if (partition.isEmpty()) { s_logger.info("No more cycles to execute"); } else { final int firstCycle = _spawnedCycleCount; _spawnedCycleCount += partition.size(); s_logger.info("Spawning worker {} for {} cycles {} - {}", new Object[] {++_spawnedWorkerCount, getWorkerContext(), firstCycle, _spawnedCycleCount }); ViewProcessWorker delegate = getDelegate().createWorker(this, getExecutionOptions(new ArbitraryViewCycleExecutionSequence(partition)), getViewDefinition()); _workers.add(delegate); _spawnedWorkers++; } } // ViewProcessWorker @Override public synchronized boolean triggerCycle() { if (_trigger == 0) { s_logger.debug("Ignoring triggerCycle on run-as-fast-as-possible sequence"); return false; } while (_trigger > 0) { spawnWorker(); _trigger--; } return true; } @Override public boolean requestCycle() { s_logger.debug("Ignoring requestCycle on run-as-fast-as-possible sequence"); return false; } @Override public void updateViewDefinition(ViewDefinition viewDefinition) { // This is not a good state of affairs as the caller has little or no control or knowledge over the sequence we're working on s_logger.warn("View definition updated on run-as-fast-as-possible sequence"); _viewDefinition = viewDefinition; Collection<ViewProcessWorker> delegates; synchronized (this) { delegates = new ArrayList<ViewProcessWorker>(_workers); } for (ViewProcessWorker delegate : delegates) { delegate.updateViewDefinition(_viewDefinition); } } @Override public void terminate() { Collection<ViewProcessWorker> delegates; synchronized (this) { _terminated = true; delegates = new ArrayList<ViewProcessWorker>(_workers); } for (ViewProcessWorker delegate : delegates) { delegate.terminate(); } } @Override public void join() throws InterruptedException { Collection<ViewProcessWorker> delegates; synchronized (this) { delegates = new ArrayList<ViewProcessWorker>(_workers); } for (ViewProcessWorker delegate : delegates) { delegate.join(); } } @Override public boolean join(long timeout) throws InterruptedException { Collection<ViewProcessWorker> delegates; final long maxTime = System.nanoTime() + (timeout * 1000000); long time; do { synchronized (this) { delegates = new ArrayList<ViewProcessWorker>(_workers); } for (ViewProcessWorker delegate : delegates) { time = (maxTime - System.nanoTime()) / 1000000; if (time <= 0) { return false; } if (!delegate.join(time)) { return false; } } if (isTerminated()) { return true; } time = (maxTime - System.nanoTime()) / 1000000; } while (time > 0); return false; } /** * Tests whether at least one of the workers in the buffer is still active. This will also housekeep the buffer of workers, removing any that have terminated. * * @return true if the worker buffer is empty, or all return true from {@code isTerminated} (which leaves the buffer empty) */ @Override public boolean isTerminated() { ViewProcessWorker worker; synchronized (this) { worker = _workers.peek(); } while (worker != null) { if (!worker.isTerminated()) { return false; } synchronized (this) { ViewProcessWorker worker2 = _workers.poll(); if (worker2 == worker) { s_logger.debug("Removing completed worker from head of queue"); worker = worker2; } else if (worker2 == null) { s_logger.debug("Completed worker already removed from queue"); break; } else { s_logger.debug("Concurrent worker queue access"); _workers.add(worker2); worker = _workers.peek(); } } } return true; } @Override public void forceGraphRebuild() { Collection<ViewProcessWorker> delegates; synchronized (this) { delegates = new ArrayList<>(_workers); } for (ViewProcessWorker delegate : delegates) { delegate.forceGraphRebuild(); } } // ViewProcessWorkerContext @Override public ViewProcessContext getProcessContext() { return getWorkerContext().getProcessContext(); } @Override public void viewDefinitionCompiled(ViewExecutionDataProvider dataProvider, CompiledViewDefinitionWithGraphs compiled) { s_logger.debug("View definition compiled"); getWorkerContext().viewDefinitionCompiled(dataProvider, compiled); } @Override public void viewDefinitionCompilationFailed(Instant compilationTime, Exception exception) { s_logger.debug("View definition compilation failed"); getWorkerContext().viewDefinitionCompilationFailed(compilationTime, exception); } @Override public void cycleStarted(ViewCycleMetadata cycleMetadata) { s_logger.debug("Cycle started"); getWorkerContext().cycleStarted(cycleMetadata); } @Override public void cycleFragmentCompleted(ViewComputationResultModel result, ViewDefinition viewDefinition) { s_logger.debug("Cycle fragment completed"); getWorkerContext().cycleFragmentCompleted(result, viewDefinition); } @Override public void cycleCompleted(ViewCycle cycle) { s_logger.debug("Cycle completed"); getWorkerContext().cycleCompleted(cycle); } @Override public void cycleExecutionFailed(ViewCycleExecutionOptions options, Exception exception) { s_logger.debug("Cycle execution failed"); getWorkerContext().cycleExecutionFailed(options, exception); } @Override public void workerCompleted() { s_logger.debug("Worker completed"); final boolean finished; synchronized (this) { finished = (--_spawnedWorkers) == 0; if (!_terminated) { spawnWorker(); } } // isTerminated will housekeep the queue for us, but may not return TRUE as the worker that called us might not be considered terminated yet isTerminated(); if (finished) { getWorkerContext().workerCompleted(); } } // Object @Override public String toString() { return "Partition[" + getWorkerContext() + "]"; } }