-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[bug#12009] Make ListBuffer's iterator fail-fast
Make `ListBuffer`'s iterator fail-fast when the buffer is mutated after the iterator's creation. Co-authored-by: Jason Zaugg <jzaugg@gmail.com>
- Loading branch information
Showing
11 changed files
with
435 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
src/library/scala/collection/mutable/MutationTracker.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Scala (https://www.scala-lang.org) | ||
* | ||
* Copyright EPFL and Lightbend, Inc. | ||
* | ||
* Licensed under Apache License 2.0 | ||
* (http://www.apache.org/licenses/LICENSE-2.0). | ||
* | ||
* See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
*/ | ||
|
||
package scala | ||
package collection | ||
package mutable | ||
|
||
import java.util.ConcurrentModificationException | ||
|
||
/** | ||
* Utilities to check that mutations to a client that tracks | ||
* its mutations have not occurred since a given point. | ||
* [[Iterator `Iterator`]]s that perform this check automatically | ||
* during iteration can be created by wrapping an `Iterator` | ||
* in a [[MutationTracker.CheckedIterator `CheckedIterator`]], | ||
* or by manually using the [[MutationTracker.checkMutations() `checkMutations`]] | ||
* and [[MutationTracker.checkMutationsForIteration() `checkMutationsForIteration`]] | ||
* methods. | ||
*/ | ||
private object MutationTracker { | ||
|
||
/** | ||
* Checks whether or not the actual mutation count differs from | ||
* the expected one, throwing an exception, if it does. | ||
* | ||
* @param expectedCount the expected mutation count | ||
* @param actualCount the actual mutation count | ||
* @param message the exception message in case of mutations | ||
* @throws ConcurrentModificationException if the expected and actual | ||
* mutation counts differ | ||
*/ | ||
@throws[ConcurrentModificationException] | ||
def checkMutations(expectedCount: Int, actualCount: Int, message: String): Unit = { | ||
if (actualCount != expectedCount) throw new ConcurrentModificationException(message) | ||
} | ||
|
||
/** | ||
* Checks whether or not the actual mutation count differs from | ||
* the expected one, throwing an exception, if it does. This method | ||
* produces an exception message saying that it was called because a | ||
* backing collection was mutated during iteration. | ||
* | ||
* @param expectedCount the expected mutation count | ||
* @param actualCount the actual mutation count | ||
* @throws ConcurrentModificationException if the expected and actual | ||
* mutation counts differ | ||
*/ | ||
@throws[ConcurrentModificationException] | ||
@inline def checkMutationsForIteration(expectedCount: Int, actualCount: Int): Unit = | ||
checkMutations(expectedCount, actualCount, "mutation occurred during iteration") | ||
|
||
/** | ||
* An iterator wrapper that checks if the underlying collection has | ||
* been mutated. | ||
* | ||
* @param underlying the underlying iterator | ||
* @param mutationCount a by-name provider of the current mutation count | ||
* @tparam A the type of the iterator's elements | ||
*/ | ||
final class CheckedIterator[A](underlying: Iterator[A], mutationCount: => Int) extends AbstractIterator[A] { | ||
private[this] val expectedCount = mutationCount | ||
|
||
def hasNext: Boolean = { | ||
checkMutationsForIteration(expectedCount, mutationCount) | ||
underlying.hasNext | ||
} | ||
def next(): A = underlying.next() | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
test/benchmarks/src/main/scala/scala/collection/mutable/ConstructionBenchmark.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Scala (https://www.scala-lang.org) | ||
* | ||
* Copyright EPFL and Lightbend, Inc. | ||
* | ||
* Licensed under Apache License 2.0 | ||
* (http://www.apache.org/licenses/LICENSE-2.0). | ||
* | ||
* See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
*/ | ||
|
||
package scala.collection | ||
package mutable | ||
|
||
import java.util.concurrent.TimeUnit | ||
|
||
import org.openjdk.jmh.annotations._ | ||
import org.openjdk.jmh.infra._ | ||
|
||
@BenchmarkMode(Array(Mode.AverageTime)) | ||
@Fork(2) | ||
@Threads(1) | ||
@Warmup(iterations = 20) | ||
@Measurement(iterations = 20) | ||
@OutputTimeUnit(TimeUnit.NANOSECONDS) | ||
@State(Scope.Benchmark) | ||
class ConstructionBenchmark { | ||
@Param(Array("0", "1", "10", "100")) | ||
var size: Int = _ | ||
|
||
var values: Range = _ | ||
|
||
@Setup(Level.Trial) def init(): Unit = { | ||
values = 1 to size | ||
} | ||
|
||
@Benchmark def listBuffer_new: Any = { | ||
new ListBuffer ++= values | ||
} | ||
|
||
@Benchmark def listBuffer_from: Any = { | ||
ListBuffer from values | ||
} | ||
|
||
@Benchmark def listBuffer_to: Any = { | ||
values to ListBuffer | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
32 changes: 32 additions & 0 deletions
32
test/junit/scala/collection/mutable/MutationTrackerTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Scala (https://www.scala-lang.org) | ||
* | ||
* Copyright EPFL and Lightbend, Inc. | ||
* | ||
* Licensed under Apache License 2.0 | ||
* (http://www.apache.org/licenses/LICENSE-2.0). | ||
* | ||
* See the NOTICE file distributed with this work for | ||
* additional information regarding copyright ownership. | ||
*/ | ||
|
||
package scala.collection.mutable | ||
|
||
import java.util.ConcurrentModificationException | ||
|
||
import org.junit.Test | ||
|
||
import scala.tools.testkit.AssertUtil.assertThrows | ||
|
||
class MutationTrackerTest { | ||
@Test | ||
def checkedIterator(): Unit = { | ||
var mutationCount = 0 | ||
def it = new MutationTracker.CheckedIterator(List(1, 2, 3).iterator, mutationCount) | ||
val it1 = it | ||
it1.toList // does not throw | ||
val it2 = it | ||
mutationCount += 1 | ||
assertThrows[ConcurrentModificationException](it2.toList, _ contains "iteration") | ||
} | ||
} |
Oops, something went wrong.