操作参数

In this tutorial, you’ll learn how to configure operation arguments.

Consider this straightforward MultiMap implementation below. It’s based on the ConcurrentHashMap, internally storing a list of values:

1. import java.util.concurrent.*

3. class MultiMap<K, V> {
4. private val map = ConcurrentHashMap<K, List<V>>()

6. // Maintains a list of values
7. // associated with the specified key.
8. fun add(key: K, value: V) {
9. val list = map[key]
10. if (list == null) {
11. map[key] = listOf(value)
12. } else {
13. map[key] = list + value
14. }
15. }

17. fun get(key: K): List<V> = map[key] ?: emptyList()
18. }

Is this MultiMap implementation linearizable? If not, an incorrect interleaving is more likely to be detected when accessing a small range of keys, thus, increasing the possibility of processing the same key concurrently.

For this, configure the generator for a key: Int parameter:

  1. Declare the @Param annotation.
  2. Specify the integer generator class: @Param(gen = IntGen::class). Lincheck supports random parameter generators for almost all primitives and strings out of the box.
  3. Define the range of values generated with the string configuration @Param(conf = "1:2").
  4. Specify the parameter configuration name (@Param(name = "key")) to share it for several operations.

Below is the stress test for MultiMap that generates keys for add(key, value) and get(key) operations in the range of [1..2]:

``` 1. import java.util.concurrent. 2. import org.jetbrains.kotlinx.lincheck.annotations. 3. import org.jetbrains.kotlinx.lincheck.check 4. import org.jetbrains.kotlinx.lincheck.paramgen. 5. import org.jetbrains.kotlinx.lincheck.strategy.stress. 6. import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking. 7. import org.junit.

  1. class MultiMap {
  2. private val map = ConcurrentHashMap<K, List>()

  3. // Maintains a list of values

  4. // associated with the specified key.
  5. fun add(key: K, value: V) {
  6. val list = map[key]
  7. if (list == null) {
  8. map[key] = listOf(value)
  9. } else {
  10. map[key] = list + value
  11. }
  12. }

  13. fun get(key: K): List = map[key] ?: emptyList()

  14. }

  15. @Param(name = "key", gen = IntGen::class, conf = "1:2")

  16. class MultiMapTest {
  17. private val map = MultiMap()

  18. @Operation

  19. fun add(@Param(name = "key") key: Int, value: Int) = map.add(key, value)

  20. @Operation

  21. fun get(@Param(name = "key") key: Int) = map.get(key)

  22. @Test

  23. fun stressTest() = StressOptions().check(this::class)

  24. @Test

  25. fun modelCheckingTest() = ModelCheckingOptions().check(this::class)
  26. } ```
  27. Run the stressTest() and see the following output:

1. = Invalid execution results = 2. | ---------------------------------- | 3. | Thread 1 | Thread 2 | 4. | ---------------------------------- | 5. | add(2, 0): void | add(2, -1): void | 6. | ---------------------------------- | 7. | get(2): [0] | | 8. | ---------------------------------- | 6. Finally, run modelCheckingTest(). It fails with the following output:

``` 1. = Invalid execution results = 2. | ---------------------------------- | 3. | Thread 1 | Thread 2 | 4. | ---------------------------------- | 5. | add(2, 0): void | add(2, -1): void | 6. | ---------------------------------- | 7. | get(2): [-1] | | 8. | ---------------------------------- |


  1. All operations above the horizontal line | ----- | happen before those below the line

  2. The following interleaving leads to the error:

  3. | ---------------------------------------------------------------------- |
  4. | Thread 1 | Thread 2 |
  5. | ---------------------------------------------------------------------- |
  6. | | add(2, -1) |
  7. | | add(2,-1) at MultiMapTest.add(MultiMap.kt:31) |
  8. | | get(2): null at MultiMap.add(MultiMap.kt:15) |
  9. | | switch |
  10. | add(2, 0): void | |
  11. | | put(2,[-1]): [0] at MultiMap.add(MultiMap.kt:17) |
  12. | | result: void |
  13. | ---------------------------------------------------------------------- | ```

Due to the small range of keys, Lincheck quickly reveals the race: when two values are being added concurrently by the same key, one of the values may be overwritten and lost.

Get the full code.

操作参数 - 图1

Next step

Learn how to test data structures that set access constraints on the execution, such as single-producer single-consumer queues.