Skip to content

Commit 1d4a1f7

Browse files
committed
SI-9126 Missing .seqs causes problems with parallel GenXs
Added `.seq` in two essential places so that a parallel collection passed as an argument won't mess up side-effecting sequential computations in `List.flatMap` and `GenericTraversableTemplate.transpose` (thanks to retronym for finding the danger spots). Tests that `.seq` is called by constructing a `GenSeq` whose `.seq` disagrees with anything non-`.seq` (idea & working implementation from retronym). Also updates the `.seq` test for `Vector#++` to use the new more efficient method.
1 parent 1887009 commit 1d4a1f7

File tree

4 files changed

+46
-22
lines changed

4 files changed

+46
-22
lines changed

src/library/scala/collection/generic/GenericTraversableTemplate.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNew
216216
val bs: IndexedSeq[Builder[B, CC[B]]] = IndexedSeq.fill(headSize)(genericBuilder[B])
217217
for (xs <- sequential) {
218218
var i = 0
219-
for (x <- asTraversable(xs)) {
219+
for (x <- asTraversable(xs).seq) {
220220
if (i >= headSize) fail
221221
bs(i) += x
222222
i += 1

src/library/scala/collection/immutable/List.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ sealed abstract class List[+A] extends AbstractSeq[A]
324324
var h: ::[B] = null
325325
var t: ::[B] = null
326326
while (rest ne Nil) {
327-
f(rest.head).foreach{ b =>
327+
f(rest.head).seq.foreach{ b =>
328328
if (!found) {
329329
h = new ::(b, Nil)
330330
t = h
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package scala.collection.immutable
2+
3+
import org.junit.Test
4+
import org.junit.runner.RunWith
5+
import org.junit.runners.JUnit4
6+
7+
@RunWith(classOf[JUnit4])
8+
class ParallelConsistencyTest {
9+
10+
private val theSeq = Seq(1,2,3)
11+
12+
// This collection will throw an exception if you do anything but call .length or .seq
13+
private val mustCallSeq: collection.GenSeq[Int] = new collection.parallel.ParSeq[Int] {
14+
def length = 3
15+
16+
// This method is surely sequential & safe -- want all access to go through here
17+
def seq = theSeq
18+
19+
def notSeq = throw new Exception("Access to parallel collection not via .seq")
20+
21+
// These methods could possibly be used dangerously explicitly or internally
22+
// (apply could also be used safely; if it is, do test with mustCallSeq)
23+
def apply(i: Int) = notSeq
24+
def splitter = notSeq
25+
}
26+
27+
// Test Vector ++ with a small parallel collection concatenation (SI-9072).
28+
@Test
29+
def testPlusPlus(): Unit = {
30+
assert((Vector.empty ++ mustCallSeq) == theSeq, "Vector ++ unsafe with parallel vectors")
31+
}
32+
33+
// SI-9126, 1 of 2
34+
@Test
35+
def testTranspose(): Unit = {
36+
assert(List(mustCallSeq).transpose.flatten == theSeq, "Transposing inner parallel collection unsafe")
37+
}
38+
39+
// SI-9126, 2 of 2
40+
@Test
41+
def testList_flatMap(): Unit = {
42+
assert(List(1).flatMap(_ => mustCallSeq) == theSeq, "List#flatMap on inner parallel collection unsafe")
43+
}
44+
}

test/junit/scala/collection/immutable/VectorTest.scala

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)