/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.optaplanner.core.impl.partitionedsearch.queue; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.optaplanner.core.api.domain.solution.PlanningSolution; import org.optaplanner.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator; import org.optaplanner.core.impl.partitionedsearch.scope.PartitionChangeMove; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is thread-safe. * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation */ public class PartitionQueue<Solution_> implements Iterable<PartitionChangeMove<Solution_>> { protected final transient Logger logger = LoggerFactory.getLogger(getClass()); private BlockingQueue<PartitionChangedEvent<Solution_>> queue; private Map<Integer, PartitionChangedEvent<Solution_>> moveEventMap; // Key is partIndex // Only used by producers private final Map<Integer, AtomicLong> nextEventIndexMap; // Only used by consumer private int openPartCount; private final Map<Integer, Long> processedEventIndexMap; // Key is partIndex public PartitionQueue(int partCount) { // TODO partCount * 100 is pulled from thin air queue = new ArrayBlockingQueue<>(partCount * 100); moveEventMap = new ConcurrentHashMap<>(partCount); Map<Integer, AtomicLong> nextEventIndexMap = new HashMap<>(partCount); for (int i = 0; i < partCount; i++) { nextEventIndexMap.put(i, new AtomicLong(0)); } this.nextEventIndexMap = Collections.unmodifiableMap(nextEventIndexMap); openPartCount = partCount; // HashMap because only the consumer thread uses it processedEventIndexMap = new HashMap<>(partCount); for (int i = 0; i < partCount; i++) { processedEventIndexMap.put(i, -1L); } } /** * This method is thread-safe. * The previous move(s) for this partIndex (if it hasn't been consumed yet), will be skipped during iteration. * @param partIndex {@code 0 <= partIndex < partCount} * @param move never null */ public void addMove(int partIndex, PartitionChangeMove<Solution_> move) { long eventIndex = nextEventIndexMap.get(partIndex).getAndIncrement(); PartitionChangedEvent<Solution_> event = new PartitionChangedEvent<>( partIndex, eventIndex, move); moveEventMap.put(event.getPartIndex(), event); queue.add(event); } /** * This method is thread-safe. * The previous move for this partIndex (if it hasn't been consumed yet), will still be returned during iteration. * @param partIndex {@code 0 <= partIndex < partCount} */ public void addFinish(int partIndex) { long eventIndex = nextEventIndexMap.get(partIndex).getAndIncrement(); PartitionChangedEvent<Solution_> event = new PartitionChangedEvent<>( partIndex, eventIndex, PartitionChangedEvent.PartitionChangedEventType.FINISHED); queue.add(event); } /** * This method is thread-safe. * The previous move for this partIndex (if it hasn't been consumed yet), will still be returned during iteration * before the iteration throws an exception. * @param partIndex {@code 0 <= partIndex < partCount} * @param throwable never null */ public void addExceptionThrown(int partIndex, Throwable throwable) { long eventIndex = nextEventIndexMap.get(partIndex).getAndIncrement(); PartitionChangedEvent<Solution_> event = new PartitionChangedEvent<>( partIndex, eventIndex, throwable); queue.add(event); } @Override public Iterator<PartitionChangeMove<Solution_>> iterator() { // TODO Currently doesn't be support to be called twice on the same instance return new PartitionQueueIterator(); } private class PartitionQueueIterator extends UpcomingSelectionIterator<PartitionChangeMove<Solution_>> { @Override protected PartitionChangeMove<Solution_> createUpcomingSelection() { while (true) { PartitionChangedEvent<Solution_> triggerEvent; try { triggerEvent = queue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Solver thread was interrupted in Partitioned Search.", e); } switch (triggerEvent.getType()) { case MOVE: int partIndex = triggerEvent.getPartIndex(); long processedEventIndex = processedEventIndexMap.get(partIndex); if (triggerEvent.getEventIndex() <= processedEventIndex) { // Skip this one because it or a better version was already processed logger.trace(" Skipped event of partIndex ({}).", partIndex); continue; } PartitionChangedEvent<Solution_> latestMoveEvent = moveEventMap.get(partIndex); processedEventIndexMap.put(partIndex, latestMoveEvent.getEventIndex()); return latestMoveEvent.getMove(); case FINISHED: openPartCount--; if (openPartCount <= 0) { return noUpcomingSelection(); } else { continue; } case EXCEPTION_THROWN: throw new IllegalStateException("The partition child thread with partIndex (" + triggerEvent.getPartIndex() + ") has thrown an exception." + " Relayed here in the parent thread.", triggerEvent.getThrowable()); default: throw new IllegalStateException("The partitionChangedEventType (" + triggerEvent.getType() + ") is not implemented."); } } } } }