Spec-Zone.ru › Kotlin 1.9

Аргументы операций

В этом руководстве вы узнаете, как настраивать аргументы операций.

Рассмотрим приведенную ниже простую MultiMap реализацию. Она основана на ConcurrentHashMap, внутренне хранящем список значений:

import java.util.concurrent.*

class MultiMap<K, V> {
    private val map = ConcurrentHashMap<K, List<V>>()
   
    // Maintains a list of values 
    // associated with the specified key.
    fun add(key: K, value: V) {
        val list = map[key]
        if (list == null) {
            map[key] = listOf(value)
        } else {
            map[key] = list + value
        }
    }

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

Является ли эта MultiMap реализация линеаризуемой? Если нет, некорректное переплетение (интерливинг) скорее всего будет обнаружено при доступе к небольшому диапазону ключей, увеличивая вероятность одновременной обработки одного и того же ключа.

Для этого настройте генератор для параметра key: Int.

  1. Объявите аннотацию @Param.

  2. Укажите класс целочисленного генератора: @Param(gen = IntGen::class). Lincheck поддерживает генераторы случайных параметров для практически всех примитивов и строк «из коробки».

  3. Определите диапазон генерируемых значений с помощью строковой конфигурации @Param(conf = "1:2").

  4. Укажите имя конфигурации параметра (@Param(name = "key")) для его совместного использования в нескольких операциях.

    Ниже представлен стресс-тест для MultiMap, который генерирует ключи для операций add(key, value) и get(key) в диапазоне [1..2]:

    import java.util.concurrent.*
    import org.jetbrains.kotlinx.lincheck.annotations.*
    import org.jetbrains.kotlinx.lincheck.check
    import org.jetbrains.kotlinx.lincheck.paramgen.*
    import org.jetbrains.kotlinx.lincheck.strategy.stress.*
    import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
    import org.junit.*
    
    class MultiMap<K, V> {
        private val map = ConcurrentHashMap<K, List<V>>()
    
        // Maintains a list of values 
        // associated with the specified key.
        fun add(key: K, value: V) {
            val list = map[key]
            if (list == null) {
                map[key] = listOf(value)
            } else {
                map[key] = list + value
            }
        }
    
        fun get(key: K): List<V> = map[key] ?: emptyList()
    }
    
    @Param(name = "key", gen = IntGen::class, conf = "1:2")
    class MultiMapTest {
        private val map = MultiMap<Int, Int>()
    
        @Operation
        fun add(@Param(name = "key") key: Int, value: Int) = map.add(key, value)
    
        @Operation
        fun get(@Param(name = "key") key: Int) = map.get(key)
    
        @Test
        fun stressTest() = StressOptions().check(this::class)
    
        @Test
        fun modelCheckingTest() = ModelCheckingOptions().check(this::class)
    }
    
  5. Запустите stressTest() и посмотрите следующий вывод:

    = Invalid execution results =
    | ---------------------------------- |
    |    Thread 1     |     Thread 2     |
    | ---------------------------------- |
    | add(2, 0): void | add(2, -1): void |
    | ---------------------------------- |
    | get(2): [0]     |                  |
    | ---------------------------------- |
    
  6. Наконец, запустите modelCheckingTest(). Он завершится сбоем с указанным ниже выводом:

    = Invalid execution results =
    | ---------------------------------- |
    |    Thread 1     |     Thread 2     |
    | ---------------------------------- |
    | add(2, 0): void | add(2, -1): void |
    | ---------------------------------- |
    | get(2): [-1]    |                  |
    | ---------------------------------- |
    
    ---
    All operations above the horizontal line | ----- | happen before those below the line
    ---
    
    The following interleaving leads to the error:
    | ---------------------------------------------------------------------- |
    |    Thread 1     |                       Thread 2                       |
    | ---------------------------------------------------------------------- |
    |                 | add(2, -1)                                           |
    |                 |   add(2,-1) at MultiMapTest.add(MultiMap.kt:31)      |
    |                 |     get(2): null at MultiMap.add(MultiMap.kt:15)     |
    |                 |     switch                                           |
    | add(2, 0): void |                                                      |
    |                 |     put(2,[-1]): [0] at MultiMap.add(MultiMap.kt:17) |
    |                 |   result: void                                       |
    | ---------------------------------------------------------------------- |
    

Из-за небольшого диапазона ключей Lincheck быстро обнаруживает гонку: когда два значения добавляются одновременно одним и тем же ключом, одно из значений может быть перезаписано и потеряно.

Получить полный код.

Следующий шаг

Узнайте, как тестировать структуры данных, которые устанавливают ограничения доступа к выполнению, такие как очереди с одним производителем и одним потребителем.

Последнее изменение: 25 июля 2023 г.
Стресс-тестирование и проверка моделей Ограничения структуры данных

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/operation-arguments.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API