[LL] Add Lincheck tests for CleanableSoftValueCache
- Lincheck tests the linearizability of a concurrent data structure, which helps us verify that `CleanableSoftValueCache` works in concurrent scenarios. ^KT-62136 fixed
This commit is contained in:
committed by
Space Team
parent
b2cd29726b
commit
80a2382cf6
+5
-2
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.caches
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirInternals
|
||||
import java.lang.ref.ReferenceQueue
|
||||
import java.lang.ref.SoftReference
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -20,7 +21,8 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
* The cleaner may be invoked multiple times by the cache, in any thread. Implementations of [SoftValueCleaner] must ensure that the
|
||||
* operation is repeatable and thread-safe.
|
||||
*/
|
||||
internal fun interface SoftValueCleaner<V> {
|
||||
@LLFirInternals
|
||||
fun interface SoftValueCleaner<V> {
|
||||
/**
|
||||
* Cleans up after [value] has been removed from the cache or garbage-collected.
|
||||
*
|
||||
@@ -43,7 +45,8 @@ internal fun interface SoftValueCleaner<V> {
|
||||
* @param getCleaner Returns the [SoftValueCleaner] that should be invoked after [V] has been collected or removed from the cache. The
|
||||
* function will be invoked once when the value is added to the cache.
|
||||
*/
|
||||
internal class CleanableSoftValueCache<K : Any, V : Any>(
|
||||
@LLFirInternals
|
||||
class CleanableSoftValueCache<K : Any, V : Any>(
|
||||
private val getCleaner: (V) -> SoftValueCleaner<V>,
|
||||
) {
|
||||
private val backingMap = ConcurrentHashMap<K, SoftReferenceWithCleanup<K, V>>()
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.caches
|
||||
|
||||
import org.jetbrains.kotlinx.lincheck.annotations.Operation
|
||||
import org.jetbrains.kotlinx.lincheck.check
|
||||
import org.jetbrains.kotlinx.lincheck.strategy.managed.forClasses
|
||||
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions
|
||||
import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* Verifies the linearizability of [CleanableSoftValueCache].
|
||||
*
|
||||
* This test cannot rely on garbage collector and soft reference queue semantics, so we associate no cleaning operations with it.
|
||||
*
|
||||
* Various functions of the cache are not checked by Lincheck:
|
||||
*
|
||||
* - [CleanableSoftValueCache.clear] is not checked because it must be executed in a write action, which guarantees single-threadedness.
|
||||
* - [CleanableSoftValueCache.size] and [CleanableSoftValueCache.isEmpty] are not checked because the underlying `ConcurrentHashMap`'s
|
||||
* implementation of these properties isn't guaranteed to immediately take effect after map operations (see the `ConcurrentHashMap`
|
||||
* section in the book "Java Concurrency in Practice").
|
||||
* - [CleanableSoftValueCache.keys] is not checked for linearizability because it is only weakly consistent via the underlying concurrent
|
||||
* hash map implementation.
|
||||
*/
|
||||
class CleanableSoftValueCacheLincheckTest {
|
||||
private val cache = CleanableSoftValueCache<Int, Int> { SoftValueCleaner { } }
|
||||
|
||||
@Operation
|
||||
fun get(key: Int): Int? = cache.get(key)
|
||||
|
||||
@Operation
|
||||
fun computeIfAbsent(key: Int, value: Int): Int = cache.computeIfAbsent(key) { value }
|
||||
|
||||
@Operation
|
||||
fun compute(key: Int, offset: Int): Int? = cache.compute(key) { _, base -> (base ?: 0) + offset }
|
||||
|
||||
@Operation
|
||||
fun put(key: Int, value: Int): Int? = cache.put(key, value)
|
||||
|
||||
@Operation
|
||||
fun remove(key: Int): Int? = cache.remove(key)
|
||||
|
||||
/**
|
||||
* The guarantee for [ConcurrentHashMap][java.util.concurrent.ConcurrentHashMap] is required for model checking to succeed because
|
||||
* `ConcurrentHashMap` doesn't pass Lincheck model checking itself.
|
||||
*/
|
||||
@Test
|
||||
fun modelCheckingTest() = ModelCheckingOptions()
|
||||
.addGuarantee(forClasses("java.util.concurrent.ConcurrentHashMap").allMethods().treatAsAtomic())
|
||||
.check(this::class)
|
||||
|
||||
@Test
|
||||
fun stressTest() = StressOptions().check(this::class)
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.analysis.low.level.api.fir.caches
|
||||
|
||||
import org.jetbrains.kotlinx.lincheck.annotations.Operation
|
||||
import org.jetbrains.kotlinx.lincheck.check
|
||||
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions
|
||||
import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
// This is an example Lincheck test to ensure that the test infrastructure is working. It will be replaced with proper "cleanable soft value
|
||||
// cache" tests in the scope of KT-62136.
|
||||
|
||||
class ExampleLincheckTest {
|
||||
private val c = AtomicInteger(0)
|
||||
|
||||
@Operation
|
||||
fun inc(): Int = c.incrementAndGet()
|
||||
|
||||
@Operation
|
||||
fun get(): Int = c.get()
|
||||
|
||||
@Test
|
||||
fun stressTest() = StressOptions().check(this::class)
|
||||
|
||||
@Test
|
||||
fun modelCheckingTest() = ModelCheckingOptions().check(this::class)
|
||||
}
|
||||
Reference in New Issue
Block a user