diff --git a/platform/platform-tests/testSrc/com/intellij/util/containers/ContainerUtilTest.java b/platform/platform-tests/testSrc/com/intellij/util/containers/ContainerUtilTest.java index c7a7dfa955f93..e15990ee9f1aa 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/containers/ContainerUtilTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/containers/ContainerUtilTest.java @@ -18,6 +18,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; public class ContainerUtilTest extends TestCase { @@ -94,6 +95,67 @@ public void testConcatedListsAfterModificationMustThrowCME() { } } + private static Future modifyListUntilStopped(List list, AtomicBoolean stopped) { + + return AppExecutorUtil.getAppExecutorService().submit(()-> { + + // simple random generator (may overflow) + for (int seed = 13; !stopped.get(); seed += 907) { + // random [0-31] (sign clipped) + int rand = (seed^(seed>>5)) & 0x1f; + + // grow or shrink the list randomly. + if (rand < list.size()) { + list.remove(rand); + } + else { + list.add(seed); + } + } + }); + } + + private static List createFilledList(int size) { + List list = ContainerUtil.createLockFreeCopyOnWriteList(); + + for(int i=0; i list1 = createFilledList(32); + List list2 = createFilledList(32); + List values = ContainerUtil.concat(list1, list2); + + AtomicBoolean stop = new AtomicBoolean(false); + Future future1 = modifyListUntilStopped(list1, stop); + Future future2 = modifyListUntilStopped(list2, stop); + + try { + long count = 0; + int n=0; + long until = System.currentTimeMillis() + 1000; + while (System.currentTimeMillis() < until) { + + for (Integer value : values) { + count += value; + } + + // must work on streams (even parallel), too. + count += values.parallelStream().count(); + ++n; + } + stop.set(true); + } finally { + stop.set(true); // finally stop even in case of an error + future1.get(); + future2.get(); + } + } + public void testIterateWithCondition() { Condition cond = integer -> integer > 2; diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index 864a02d904bb2..40e9f29dda689 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -1441,6 +1441,24 @@ public T get(int index) { public int size() { return size; } + + /** + * Returns an iterator over the actual elements in this list based on the underlying lists. + * + * @implNote + * This implementation replaces the straightforward implementation based on index operations. + * Those fail badly, if the underlying lists change since creation of this concatenated list: + * either by an {@link IndexOutOfBoundsException} if any list shrinks unexpectedly, + * or by missing all elements added later on. + * + * @return Returns an iterator over the actual elements of both lists. + */ + @Override + public Iterator iterator() { + final Iterable it1 = list1; + final Iterable it2 = list2; + return concat(it1, it2).iterator(); + } }; }