Skip to content

Commit 27d26d5

Browse files
committed
fix: iterators must not survive step boundaries
1 parent e580921 commit 27d26d5

6 files changed

Lines changed: 27 additions & 27 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/BiRandomMoveIterator.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ai.timefold.solver.core.impl.neighborhood.stream;
22

33
import java.util.ArrayList;
4+
import java.util.HashMap;
45
import java.util.Iterator;
6+
import java.util.Map;
57
import java.util.NoSuchElementException;
68
import java.util.Objects;
79
import java.util.random.RandomGenerator;
@@ -60,18 +62,23 @@ final class BiRandomMoveIterator<Solution_, A, B> implements Iterator<Move<Solut
6062

6163
private final BiMoveStreamContext<Solution_, A, B> context;
6264
private final RandomGenerator workingRandom;
65+
/**
66+
* We cannot cache the right iterator on the left tuple,
67+
* because that cache would survive across steps and the right iterator would be invalid by then.
68+
* The alternative would be worse than this map,
69+
* likely requiring some loop over tuples at step end, clearing the cache.
70+
*/
71+
private final Map<UniTuple<A>, Iterator<UniTuple<B>>> leftTupleToRightIteratorMap = new HashMap<>();
6372

6473
// Fields required for iteration.
6574
private final Iterator<UniTuple<A>> leftTupleIterator;
66-
private final int rightIteratorStoreIndex;
6775
private @Nullable Move<Solution_> nextMove;
6876

6977
public BiRandomMoveIterator(BiMoveStreamContext<Solution_, A, B> context, RandomGenerator workingRandom) {
7078
this.context = Objects.requireNonNull(context);
7179
this.workingRandom = Objects.requireNonNull(workingRandom);
72-
var leftDatasetInstance = context.getLeftDatasetInstance();
73-
this.rightIteratorStoreIndex = leftDatasetInstance.getRightIteratorStoreIndex();
74-
this.leftTupleIterator = leftDatasetInstance.randomIterator(workingRandom);
80+
this.leftTupleIterator = context.getLeftDatasetInstance()
81+
.randomIterator(workingRandom);
7582
}
7683

7784
@Override
@@ -82,10 +89,9 @@ public boolean hasNext() {
8289

8390
while (leftTupleIterator.hasNext()) {
8491
var leftTuple = leftTupleIterator.next();
85-
var rightEmpty = pickNextMove(leftTuple);
86-
if (rightEmpty) {
92+
if (!pickNextMove(leftTuple)) {
8793
leftTupleIterator.remove();
88-
leftTuple.setStore(rightIteratorStoreIndex, null);
94+
leftTupleToRightIteratorMap.remove(leftTuple);
8995
}
9096
if (nextMove != null) {
9197
if (nextMove instanceof AbstractSelectorBasedMove<Solution_> legacyMove) {
@@ -101,17 +107,20 @@ Please refactor your code (%s) to use the new Move API."""
101107
}
102108

103109
private boolean pickNextMove(UniTuple<A> leftTuple) {
104-
var rightTupleIterator = (Iterator<UniTuple<B>>) leftTuple.getStore(rightIteratorStoreIndex);
110+
var rightTupleIterator = leftTupleToRightIteratorMap.get(leftTuple);
105111
if (rightTupleIterator == null) {
106112
rightTupleIterator = createRightTupleIterator(leftTuple);
107-
leftTuple.setStore(rightIteratorStoreIndex, rightTupleIterator);
113+
if (!rightTupleIterator.hasNext()) {
114+
return false;
115+
}
116+
leftTupleToRightIteratorMap.put(leftTuple, rightTupleIterator);
108117
}
109118
if (!rightTupleIterator.hasNext()) {
110-
return true;
119+
return false;
111120
}
112121
nextMove = context.buildMove(leftTuple.getA(), rightTupleIterator.next().getA());
113122
rightTupleIterator.remove();
114-
return false;
123+
return true;
115124
}
116125

117126
private Iterator<UniTuple<B>> createRightTupleIterator(UniTuple<A> leftTuple) {

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractLeftDataset.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ protected AbstractLeftDataset(AbstractUniEnumeratingStream<Solution_, A> parent)
1212
super(parent);
1313
}
1414

15-
public abstract AbstractLeftDatasetInstance<Solution_, UniTuple<A>> instantiate(int rightSequenceStoreIndex,
16-
int entryStoreIndex);
15+
public abstract AbstractLeftDatasetInstance<Solution_, UniTuple<A>> instantiate(int entryStoreIndex);
1716

1817
}

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/common/AbstractLeftDatasetInstance.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,9 @@ public abstract class AbstractLeftDatasetInstance<Solution_, Tuple_ extends Tupl
1515
implements Iterable<Tuple_> {
1616

1717
private final ElementAwareArrayList<Tuple_> tupleList = new ElementAwareArrayList<>();
18-
private final int rightIteratorStoreIndex;
1918

20-
protected AbstractLeftDatasetInstance(AbstractDataset<Solution_> parent, int rightIteratorStoreIndex, int entryStoreIndex) {
19+
protected AbstractLeftDatasetInstance(AbstractDataset<Solution_> parent, int entryStoreIndex) {
2120
super(parent, entryStoreIndex);
22-
this.rightIteratorStoreIndex = rightIteratorStoreIndex;
23-
}
24-
25-
public int getRightIteratorStoreIndex() {
26-
return rightIteratorStoreIndex;
2721
}
2822

2923
@Override

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/LeftTerminalUniEnumeratingStream.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ public LeftTerminalUniEnumeratingStream(EnumeratingStreamFactory<Solution_> enum
2222
@Override
2323
public void buildNode(DataNodeBuildHelper<Solution_> buildHelper) {
2424
assertEmptyChildStreamList();
25-
var datasetInstance = dataset.instantiate(
26-
buildHelper.reserveTupleStoreIndex(parent.getTupleSource()),
27-
buildHelper.reserveTupleStoreIndex(parent.getTupleSource()));
25+
var datasetInstance = dataset.instantiate(buildHelper.reserveTupleStoreIndex(parent.getTupleSource()));
2826
buildHelper.putInsertUpdateRetract(this, datasetInstance);
2927
}
3028

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniLeftDataset.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public UniLeftDataset(AbstractUniEnumeratingStream<Solution_, A> parent) {
1414
}
1515

1616
@Override
17-
public UniLeftDatasetInstance<Solution_, A> instantiate(int rightIteratorStoreIndex, int entryStoreIndex) {
18-
return new UniLeftDatasetInstance<>(this, rightIteratorStoreIndex, entryStoreIndex);
17+
public UniLeftDatasetInstance<Solution_, A> instantiate(int entryStoreIndex) {
18+
return new UniLeftDatasetInstance<>(this, entryStoreIndex);
1919
}
2020

2121
@Override

core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/enumerating/uni/UniLeftDatasetInstance.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
public final class UniLeftDatasetInstance<Solution_, A>
1111
extends AbstractLeftDatasetInstance<Solution_, UniTuple<A>> {
1212

13-
public UniLeftDatasetInstance(AbstractDataset<Solution_> parent, int rightIteratorStoreIndex, int entryStoreIndex) {
14-
super(parent, rightIteratorStoreIndex, entryStoreIndex);
13+
public UniLeftDatasetInstance(AbstractDataset<Solution_> parent, int entryStoreIndex) {
14+
super(parent, entryStoreIndex);
1515
}
1616

1717
}

0 commit comments

Comments
 (0)