diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6f81dd60b..62fe305d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,6 +38,12 @@ jobs:
- name: Grant execute permission for Gradlew
run: chmod +x gradlew
+ - name: Check Kotlin formatting
+ run: ./gradlew spotlessCheck :tooling:plugins:spotlessCheck
+
+ - name: Run Kotlin static analysis
+ run: ./gradlew detekt :tooling:plugins:detekt
+
- name: Build and Test with Coverage
run: ./gradlew clean build koverXmlReport --stacktrace
diff --git a/build.gradle.kts b/build.gradle.kts
index b3dfe068a..46f404152 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,6 @@
plugins {
- alias(libs.plugins.ktlint)
- id("com.diffplug.spotless") version "6.4.1"
+ alias(libs.plugins.spotless)
+ alias(libs.plugins.detekt)
}
buildscript {
@@ -15,7 +15,6 @@ buildscript {
classpath(libs.kotlin.gradle.plugin)
classpath(libs.kotlin.serialization.plugin)
classpath(libs.dokka.gradle.plugin)
- classpath(libs.ktlint.gradle.plugin)
classpath(libs.jacoco.gradle.plugin)
classpath(libs.maven.publish.plugin)
classpath(libs.atomic.fu.gradle.plugin)
@@ -32,18 +31,31 @@ allprojects {
}
subprojects {
- apply(plugin = "org.jlleitschuh.gradle.ktlint")
apply(plugin = "com.diffplug.spotless")
-
- ktlint {
- disabledRules.add("import-ordering")
- }
+ apply(plugin = "io.gitlab.arturbosch.detekt")
spotless {
kotlin {
+ ktfmt(libs.versions.ktfmt.get()).googleStyle()
target("src/**/*.kt")
+ trimTrailingWhitespace()
+ endWithNewline()
+ }
+
+ kotlinGradle {
+ ktfmt(libs.versions.ktfmt.get()).googleStyle()
+ target("*.kts")
+ trimTrailingWhitespace()
+ endWithNewline()
}
}
+
+ detekt {
+ buildUponDefaultConfig = true
+ baseline = file("${projectDir}/config/detekt/baseline.xml")
+ config.setFrom("$rootDir/config/detekt/rules.yml")
+ source.setFrom("src")
+ }
}
tasks {
diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts
index e1aecc671..811b513df 100644
--- a/cache/build.gradle.kts
+++ b/cache/build.gradle.kts
@@ -1,26 +1,21 @@
-plugins {
- id("org.mobilenativefoundation.store.multiplatform")
-}
+plugins { id("org.mobilenativefoundation.store.multiplatform") }
kotlin {
-
- sourceSets {
- val commonMain by getting {
- dependencies {
- api(libs.kotlinx.atomic.fu)
- api(projects.core)
- implementation(libs.kotlinx.coroutines.core)
- }
- }
- val commonTest by getting {
- dependencies {
- implementation(libs.junit)
- implementation(libs.kotlinx.coroutines.test)
- }
- }
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ api(libs.kotlinx.atomic.fu)
+ api(projects.core)
+ implementation(libs.kotlinx.coroutines.core)
+ }
}
+ val commonTest by getting {
+ dependencies {
+ implementation(libs.junit)
+ implementation(libs.kotlinx.coroutines.test)
+ }
+ }
+ }
}
-android {
- namespace = "org.mobilenativefoundation.store.cache"
-}
+android { namespace = "org.mobilenativefoundation.store.cache" }
diff --git a/cache/config/detekt/baseline.xml b/cache/config/detekt/baseline.xml
new file mode 100644
index 000000000..6beb6d4d1
--- /dev/null
+++ b/cache/config/detekt/baseline.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ EmptyFunctionBlock:LocalCache.kt$LocalCache.Companion.<no name provided>${}
+ EmptyFunctionBlock:LocalCache.kt$LocalCache.StrongValueReference${}
+ LongMethod:LocalCache.kt$LocalCache.Segment$fun put( key: K, hash: Int, value: V, onlyIfAbsent: Boolean, ): V?
+ LongParameterList:LocalCache.kt$LocalCache.Segment$( first: ReferenceEntry<K, V>, entry: ReferenceEntry<K, V>, key: K, hash: Int, valueReference: ValueReference<K, V>, cause: RemovalCause?, )
+ MagicNumber:CacheBuilder.kt$CacheBuilder$16
+ MagicNumber:CacheBuilder.kt$CacheBuilder$4
+ MagicNumber:LocalCache.kt$LocalCache$20
+ MagicNumber:LocalCache.kt$LocalCache$32
+ MagicNumber:LocalCache.kt$LocalCache.Companion$10
+ MagicNumber:LocalCache.kt$LocalCache.Companion$14
+ MagicNumber:LocalCache.kt$LocalCache.Companion$15
+ MagicNumber:LocalCache.kt$LocalCache.Companion$16
+ MagicNumber:LocalCache.kt$LocalCache.Companion$3
+ MagicNumber:LocalCache.kt$LocalCache.Companion$6
+ MagicNumber:LocalCache.kt$LocalCache.Segment$3
+ MagicNumber:LocalCache.kt$LocalCache.Segment$4
+ MaxLineLength:Cache.kt$Cache$*
+ MaxLineLength:LocalCache.kt$LocalCache.WriteQueue$*
+ MaxLineLength:MonotonicTicker.kt$internal
+ MaxLineLength:RemovalCause.kt$RemovalCause$*
+ MaxLineLength:StoreMultiCache.kt$StoreMultiCache$class
+ MaxLineLength:StoreMultiCache.kt$StoreMultiCache$collectionsCache: Cache<StoreKey.Collection<Id>, Collection> = CacheBuilder<StoreKey.Collection<Id>, Collection>().build()
+ MaxLineLength:StoreMultiCache.kt$StoreMultiCache.Companion$fun invalidKeyErrorMessage(key: Any)
+ MaxLineLength:Weigher.kt$* @return Weight of a cache entry. Must be non-negative. There is no unit for entry weights. Rather, they are simply relative to each other.
+ NestedBlockDepth:LocalCache.kt$LocalCache.Segment$fun activeEntries(): Map<K, V>
+ NestedBlockDepth:LocalCache.kt$LocalCache.Segment$fun clear()
+ NestedBlockDepth:LocalCache.kt$LocalCache.Segment$fun put( key: K, hash: Int, value: V, onlyIfAbsent: Boolean, ): V?
+ NestedBlockDepth:LocalCache.kt$LocalCache.Segment$fun remove( key: K, hash: Int, ): V?
+ NestedBlockDepth:LocalCache.kt$LocalCache.Segment$private fun expand()
+ ReturnCount:LocalCache.kt$LocalCache.Segment$fun get( key: K, hash: Int, ): V?
+ ReturnCount:LocalCache.kt$LocalCache.Segment$fun remove( key: K, hash: Int, ): V?
+ ReturnCount:LocalCache.kt$LocalCache.Segment$private fun getLiveEntry( key: K, hash: Int, now: Long, ): ReferenceEntry<K, V>?
+ TooManyFunctions:LocalCache.kt$LocalCache$Segment<K : Any, V : Any>
+ TooManyFunctions:LocalCache.kt$LocalCache<K : Any, V : Any>
+ TooManyFunctions:StoreMultiCache.kt$StoreMultiCache<Id : Any, Key : StoreKey<Id>, Single : StoreData.Single<Id>, Collection : StoreData.Collection<Id, Single>, Output : StoreData<Id>> : Cache
+ UnusedParameter:LocalCache.kt$LocalCache.Segment$cause: RemovalCause?
+ UnusedParameter:LocalCache.kt$LocalCache.Segment$hash: Int
+ UnusedParameter:LocalCache.kt$LocalCache.Segment$key: K?
+ UnusedPrivateMember:LocalCache.kt$LocalCache$private fun newValueReference( entry: ReferenceEntry<K, V>, value: V, weight: Int, ): ValueReference<K, V>
+ UseCheckOrError:CacheBuilder.kt$CacheBuilder$throw IllegalStateException("Maximum size cannot be combined with weigher.")
+ UseCheckOrError:LocalCache.kt$LocalCache.Segment$throw IllegalStateException("Weights must be non-negative")
+ UseRequire:CacheBuilder.kt$CacheBuilder$throw IllegalArgumentException("Duration must be non-negative.")
+ UseRequire:CacheBuilder.kt$CacheBuilder$throw IllegalArgumentException("Maximum size must be non-negative.")
+ UseRequire:CacheBuilder.kt$CacheBuilder$throw IllegalArgumentException("Maximum weight must be non-negative.")
+
+
diff --git a/cache/config/ktlint/baseline.xml b/cache/config/ktlint/baseline.xml
deleted file mode 100644
index 7d1ab2676..000000000
--- a/cache/config/ktlint/baseline.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Cache.kt b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Cache.kt
index b2e89d042..8bc0202dc 100644
--- a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Cache.kt
+++ b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Cache.kt
@@ -1,69 +1,55 @@
package org.mobilenativefoundation.store.cache5
interface Cache {
- /**
- * @return [Value] associated with [key] or `null` if there is no cached value for [key].
- */
- fun getIfPresent(key: Key): Value?
-
- /**
- * @return [Value] associated with [key], obtaining the value from [valueProducer] if necessary.
- * No observable state associated with this cache is modified until loading completes.
- * @param [valueProducer] Must not return `null`. It may either return a non-null value or throw an exception.
- * @throws ExecutionExeption If a checked exception was thrown while loading the value.
- * @throws UncheckedExecutionException If an unchecked exception was thrown while loading the value.
- * @throws ExecutionError If an error was thrown while loading the value.
- */
- fun getOrPut(
- key: Key,
- valueProducer: () -> Value,
- ): Value
-
- /**
- * @return Map of the [Value] associated with each [Key] in [keys]. Returned map only contains entries already present in the cache.
- * The default implementation provided here throws a [NotImplementedError] to maintain backward compatibility for existing implementations.
- */
- fun getAllPresent(keys: List<*>): Map
-
- /**
- * @return Map of the [Value] associated with each [Key] in the cache.
- */
- fun getAllPresent(): Map = throw NotImplementedError()
-
- /**
- * Associates [value] with [key].
- * If the cache previously contained a value associated with [key], the old value is replaced by [value].
- * Prefer [getOrPut] when using the conventional "If cached, then return. Otherwise create, cache, and then return" pattern.
- */
- fun put(
- key: Key,
- value: Value,
- )
-
- /**
- * Copies all of the mappings from the specified map to the cache. The effect of this call is
- * equivalent to that of calling [put] on this map once for each mapping from [Key] to [Value] in the specified map.
- * The behavior of this operation is undefined if the specified map is modified while the operation is in progress.
- */
- fun putAll(map: Map)
-
- /**
- * Discards any cached value associated with [key].
- */
- fun invalidate(key: Key)
-
- /**
- * Discards any cached value associated for [keys].
- */
- fun invalidateAll(keys: List)
-
- /**
- * Discards all entries in the cache.
- */
- fun invalidateAll()
-
- /**
- * @return Approximate number of entries in the cache.
- */
- fun size(): Long
+ /** @return [Value] associated with [key] or `null` if there is no cached value for [key]. */
+ fun getIfPresent(key: Key): Value?
+
+ /**
+ * @param [valueProducer] Must not return `null`. It may either return a non-null value or throw
+ * an exception.
+ * @return [Value] associated with [key], obtaining the value from [valueProducer] if necessary.
+ * No observable state associated with this cache is modified until loading completes.
+ * @throws ExecutionExeption If a checked exception was thrown while loading the value.
+ * @throws UncheckedExecutionException If an unchecked exception was thrown while loading the
+ * value.
+ * @throws ExecutionError If an error was thrown while loading the value.
+ */
+ fun getOrPut(key: Key, valueProducer: () -> Value): Value
+
+ /**
+ * @return Map of the [Value] associated with each [Key] in [keys]. Returned map only contains
+ * entries already present in the cache. The default implementation provided here throws a
+ * [NotImplementedError] to maintain backward compatibility for existing implementations.
+ */
+ fun getAllPresent(keys: List<*>): Map
+
+ /** @return Map of the [Value] associated with each [Key] in the cache. */
+ fun getAllPresent(): Map = throw NotImplementedError()
+
+ /**
+ * Associates [value] with [key]. If the cache previously contained a value associated with [key],
+ * the old value is replaced by [value]. Prefer [getOrPut] when using the conventional "If cached,
+ * then return. Otherwise create, cache, and then return" pattern.
+ */
+ fun put(key: Key, value: Value)
+
+ /**
+ * Copies all of the mappings from the specified map to the cache. The effect of this call is
+ * equivalent to that of calling [put] on this map once for each mapping from [Key] to [Value] in
+ * the specified map. The behavior of this operation is undefined if the specified map is modified
+ * while the operation is in progress.
+ */
+ fun putAll(map: Map)
+
+ /** Discards any cached value associated with [key]. */
+ fun invalidate(key: Key)
+
+ /** Discards any cached value associated for [keys]. */
+ fun invalidateAll(keys: List)
+
+ /** Discards all entries in the cache. */
+ fun invalidateAll()
+
+ /** @return Approximate number of entries in the cache. */
+ fun size(): Long
}
diff --git a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/CacheBuilder.kt b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/CacheBuilder.kt
index 9a0782da5..b915a2b0a 100644
--- a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/CacheBuilder.kt
+++ b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/CacheBuilder.kt
@@ -3,77 +3,73 @@ package org.mobilenativefoundation.store.cache5
import kotlin.time.Duration
class CacheBuilder {
- internal var concurrencyLevel = 4
- private set
- internal val initialCapacity = 16
- internal var maximumSize = UNSET
- private set
- internal var maximumWeight = UNSET
- private set
- internal var expireAfterAccess: Duration = Duration.INFINITE
- private set
- internal var expireAfterWrite: Duration = Duration.INFINITE
- private set
- internal var weigher: Weigher? = null
- private set
- internal var ticker: Ticker? = null
- private set
-
- fun concurrencyLevel(producer: () -> Int): CacheBuilder =
- apply {
- concurrencyLevel = producer.invoke()
- }
-
- fun maximumSize(maximumSize: Long): CacheBuilder =
- apply {
- if (maximumSize < 0) {
- throw IllegalArgumentException("Maximum size must be non-negative.")
- }
- this.maximumSize = maximumSize
- }
-
- fun expireAfterAccess(duration: Duration): CacheBuilder =
- apply {
- if (duration.isNegative()) {
- throw IllegalArgumentException("Duration must be non-negative.")
- }
- expireAfterAccess = duration
- }
-
- fun expireAfterWrite(duration: Duration): CacheBuilder =
- apply {
- if (duration.isNegative()) {
- throw IllegalArgumentException("Duration must be non-negative.")
- }
- expireAfterWrite = duration
- }
-
- fun ticker(ticker: Ticker): CacheBuilder =
- apply {
- this.ticker = ticker
- }
-
- fun weigher(
- maximumWeight: Long,
- weigher: Weigher,
- ): CacheBuilder =
- apply {
- if (maximumWeight < 0) {
- throw IllegalArgumentException("Maximum weight must be non-negative.")
- }
-
- this.maximumWeight = maximumWeight
- this.weigher = weigher
- }
-
- fun build(): Cache {
- if (maximumSize != -1L && weigher != null) {
- throw IllegalStateException("Maximum size cannot be combined with weigher.")
- }
- return LocalCache.LocalManualCache(this)
+ internal var concurrencyLevel = 4
+ private set
+
+ internal val initialCapacity = 16
+ internal var maximumSize = UNSET
+ private set
+
+ internal var maximumWeight = UNSET
+ private set
+
+ internal var expireAfterAccess: Duration = Duration.INFINITE
+ private set
+
+ internal var expireAfterWrite: Duration = Duration.INFINITE
+ private set
+
+ internal var weigher: Weigher? = null
+ private set
+
+ internal var ticker: Ticker? = null
+ private set
+
+ fun concurrencyLevel(producer: () -> Int): CacheBuilder = apply {
+ concurrencyLevel = producer.invoke()
+ }
+
+ fun maximumSize(maximumSize: Long): CacheBuilder = apply {
+ if (maximumSize < 0) {
+ throw IllegalArgumentException("Maximum size must be non-negative.")
}
+ this.maximumSize = maximumSize
+ }
- companion object {
- private const val UNSET = -1L
+ fun expireAfterAccess(duration: Duration): CacheBuilder = apply {
+ if (duration.isNegative()) {
+ throw IllegalArgumentException("Duration must be non-negative.")
}
+ expireAfterAccess = duration
+ }
+
+ fun expireAfterWrite(duration: Duration): CacheBuilder = apply {
+ if (duration.isNegative()) {
+ throw IllegalArgumentException("Duration must be non-negative.")
+ }
+ expireAfterWrite = duration
+ }
+
+ fun ticker(ticker: Ticker): CacheBuilder = apply { this.ticker = ticker }
+
+ fun weigher(maximumWeight: Long, weigher: Weigher): CacheBuilder =
+ apply {
+ if (maximumWeight < 0) {
+ throw IllegalArgumentException("Maximum weight must be non-negative.")
+ }
+
+ this.maximumWeight = maximumWeight
+ this.weigher = weigher
+ }
+
+ fun build(): Cache {
+ if (maximumSize != -1L && weigher != null) {
+ throw IllegalStateException("Maximum size cannot be combined with weigher.")
+ }
+ return LocalCache.LocalManualCache(this)
+ }
+
+ companion object {
+ private const val UNSET = -1L
+ }
}
diff --git a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/LocalCache.kt b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/LocalCache.kt
index a71382a9f..0be801964 100644
--- a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/LocalCache.kt
+++ b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/LocalCache.kt
@@ -18,2065 +18,1845 @@
*/
package org.mobilenativefoundation.store.cache5
+import kotlin.math.min
+import kotlin.time.Duration
import kotlinx.atomicfu.AtomicArray
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.atomicArrayOfNulls
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.loop
-import kotlin.math.min
-import kotlin.time.Duration
internal class LocalCache(builder: CacheBuilder) {
- /**
- * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose
- * the segment.
- */
- private val segmentMask: Int
+ /**
+ * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose
+ * the segment.
+ */
+ private val segmentMask: Int
- /**
- * Shift value for indexing within segments. Helps prevent entries that end up in the same segment
- * from also ending up in the same bucket.
- */
- private val segmentShift: Int
+ /**
+ * Shift value for indexing within segments. Helps prevent entries that end up in the same segment
+ * from also ending up in the same bucket.
+ */
+ private val segmentShift: Int
- /**
- * The segments, each of which is a specialized hash table.
- */
+ /** The segments, each of which is a specialized hash table. */
+ private val segments: Array?>
- private val segments: Array?>
+ /** Strategy for referencing values. */
+ private val valueStrength: Strength = Strength.Strong
- /**
- * Strategy for referencing values.
- */
- private val valueStrength: Strength = Strength.Strong
+ /** The maximum weight of this map. UNSET_LONG if there is no maximum. */
+ private val maxWeight: Long
- /**
- * The maximum weight of this map. UNSET_LONG if there is no maximum.
- */
- private val maxWeight: Long
+ /** Weigher to weigh cache entries. */
+ private val weigher: Weigher
- /**
- * Weigher to weigh cache entries.
- */
- private val weigher: Weigher
+ /** How long after the last access to an entry the map will retain that entry. */
+ private val expireAfterAccessNanos: Long
- /**
- * How long after the last access to an entry the map will retain that entry.
- */
- private val expireAfterAccessNanos: Long
+ /** How long after the last write to an entry the map will retain that entry. */
+ private val expireAfterWriteNanos: Long
- /**
- * How long after the last write to an entry the map will retain that entry.
- */
- private val expireAfterWriteNanos: Long
-
- /**
- * Measures time in a testable way.
- */
- private val ticker: Ticker
+ /** Measures time in a testable way. */
+ private val ticker: Ticker
- /**
- * Factory used to create new entries.
- */
- private val entryFactory: EntryFactory
+ /** Factory used to create new entries. */
+ private val entryFactory: EntryFactory
- private val evictsBySize: Boolean get() = maxWeight >= 0
+ private val evictsBySize: Boolean
+ get() = maxWeight >= 0
- private val customWeigher: Boolean get() = weigher !== OneWeigher
+ private val customWeigher: Boolean
+ get() = weigher !== OneWeigher
- private val expiresAfterWrite: Boolean get() = expireAfterWriteNanos > 0
+ private val expiresAfterWrite: Boolean
+ get() = expireAfterWriteNanos > 0
- private val expiresAfterAccess: Boolean get() = expireAfterAccessNanos > 0
+ private val expiresAfterAccess: Boolean
+ get() = expireAfterAccessNanos > 0
- private val usesAccessQueue: Boolean get() = expiresAfterAccess || evictsBySize
+ private val usesAccessQueue: Boolean
+ get() = expiresAfterAccess || evictsBySize
- private val usesWriteQueue: Boolean get() = expiresAfterWrite
+ private val usesWriteQueue: Boolean
+ get() = expiresAfterWrite
- private val recordsWrite: Boolean get() = expiresAfterWrite
+ private val recordsWrite: Boolean
+ get() = expiresAfterWrite
- private val recordsAccess: Boolean get() = expiresAfterAccess
+ private val recordsAccess: Boolean
+ get() = expiresAfterAccess
- private val recordsTime: Boolean get() = recordsWrite || recordsAccess
+ private val recordsTime: Boolean
+ get() = recordsWrite || recordsAccess
- private val usesWriteEntries: Boolean get() = usesWriteQueue || recordsWrite
+ private val usesWriteEntries: Boolean
+ get() = usesWriteQueue || recordsWrite
- private val usesAccessEntries: Boolean get() = usesAccessQueue || recordsAccess
+ private val usesAccessEntries: Boolean
+ get() = usesAccessQueue || recordsAccess
- private sealed class Strength {
- /*
- * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the
- * value. This could save ~8 bytes per entry.
- */
- object Strong : Strength() {
- override fun referenceValue(
- segment: Segment?,
- entry: ReferenceEntry?,
- value: V,
- weight: Int,
- ): ValueReference {
- return if (weight == 1) {
- StrongValueReference(value)
- } else {
- WeightedStrongValueReference(
- value,
- weight,
- )
- }
- }
+ private sealed class Strength {
+ /*
+ * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the
+ * value. This could save ~8 bytes per entry.
+ */
+ object Strong : Strength() {
+ override fun referenceValue(
+ segment: Segment?,
+ entry: ReferenceEntry?,
+ value: V,
+ weight: Int,
+ ): ValueReference {
+ return if (weight == 1) {
+ StrongValueReference(value)
+ } else {
+ WeightedStrongValueReference(value, weight)
}
+ }
+ }
- /**
- * Creates a reference for the given value according to this value strength.
- */
- abstract fun referenceValue(
- segment: Segment?,
- entry: ReferenceEntry?,
- value: V,
- weight: Int,
- ): ValueReference
+ /** Creates a reference for the given value according to this value strength. */
+ abstract fun referenceValue(
+ segment: Segment?,
+ entry: ReferenceEntry?,
+ value: V,
+ weight: Int,
+ ): ValueReference
+ }
+
+ /** Creates new entries. */
+ private sealed class EntryFactory {
+ object Strong : EntryFactory() {
+ override fun newEntry(
+ segment: Segment?,
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ): ReferenceEntry {
+ return StrongEntry(key, hash, next)
+ }
}
- /**
- * Creates new entries.
- */
- private sealed class EntryFactory {
- object Strong : EntryFactory() {
- override fun newEntry(
- segment: Segment?,
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ): ReferenceEntry {
- return StrongEntry(key, hash, next)
- }
- }
+ object StrongAccess : EntryFactory() {
+ override fun newEntry(
+ segment: Segment?,
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ): ReferenceEntry {
+ return StrongAccessEntry(key, hash, next)
+ }
- object StrongAccess : EntryFactory() {
- override fun newEntry(
- segment: Segment?,
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ): ReferenceEntry {
- return StrongAccessEntry(key, hash, next)
- }
+ override fun copyEntry(
+ segment: Segment?,
+ original: ReferenceEntry,
+ newNext: ReferenceEntry?,
+ ): ReferenceEntry {
+ val newEntry = super.copyEntry(segment, original, newNext)
+ copyAccessEntry(original, newEntry)
+ return newEntry
+ }
+ }
- override fun copyEntry(
- segment: Segment?,
- original: ReferenceEntry,
- newNext: ReferenceEntry?,
- ): ReferenceEntry {
- val newEntry = super.copyEntry(segment, original, newNext)
- copyAccessEntry(original, newEntry)
- return newEntry
- }
- }
+ object StrongWrite : EntryFactory() {
+ override fun newEntry(
+ segment: Segment?,
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ): ReferenceEntry {
+ return StrongWriteEntry(key, hash, next)
+ }
- object StrongWrite : EntryFactory() {
- override fun newEntry(
- segment: Segment?,
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ): ReferenceEntry {
- return StrongWriteEntry(key, hash, next)
- }
+ override fun copyEntry(
+ segment: Segment?,
+ original: ReferenceEntry,
+ newNext: ReferenceEntry?,
+ ): ReferenceEntry {
+ val newEntry = super.copyEntry(segment, original, newNext)
+ copyWriteEntry(original, newEntry)
+ return newEntry
+ }
+ }
- override fun copyEntry(
- segment: Segment?,
- original: ReferenceEntry,
- newNext: ReferenceEntry?,
- ): ReferenceEntry {
- val newEntry = super.copyEntry(segment, original, newNext)
- copyWriteEntry(original, newEntry)
- return newEntry
- }
- }
+ object StrongAccessWrite : EntryFactory() {
+ override fun newEntry(
+ segment: Segment?,
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ): ReferenceEntry {
+ return StrongAccessWriteEntry(key, hash, next)
+ }
- object StrongAccessWrite : EntryFactory() {
- override fun newEntry(
- segment: Segment?,
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ): ReferenceEntry {
- return StrongAccessWriteEntry(key, hash, next)
- }
+ override fun copyEntry(
+ segment: Segment?,
+ original: ReferenceEntry,
+ newNext: ReferenceEntry?,
+ ): ReferenceEntry {
+ val newEntry = super.copyEntry(segment, original, newNext)
+ copyAccessEntry(original, newEntry)
+ copyWriteEntry(original, newEntry)
+ return newEntry
+ }
+ }
- override fun copyEntry(
- segment: Segment?,
- original: ReferenceEntry,
- newNext: ReferenceEntry?,
- ): ReferenceEntry {
- val newEntry = super.copyEntry(segment, original, newNext)
- copyAccessEntry(original, newEntry)
- copyWriteEntry(original, newEntry)
- return newEntry
- }
- }
+ /**
+ * Creates a new entry.
+ *
+ * @param segment to create the entry for
+ * @param key of the entry
+ * @param hash of the key
+ * @param next entry in the same bucket
+ */
+ abstract fun newEntry(
+ segment: Segment?,
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ): ReferenceEntry
- /**
- * Creates a new entry.
- *
- * @param segment to create the entry for
- * @param key of the entry
- * @param hash of the key
- * @param next entry in the same bucket
- */
- abstract fun newEntry(
- segment: Segment?,
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ): ReferenceEntry
-
- /**
- * Copies an entry, assigning it a new `next` entry.
- *
- * @param original the entry to copy
- * @param newNext entry in the same bucket
- */
- // Guarded By Segment.this
- open fun copyEntry(
- segment: Segment?,
- original: ReferenceEntry,
- newNext: ReferenceEntry?,
- ): ReferenceEntry {
- return newEntry(segment, original.key, original.hash, newNext)
- }
+ /**
+ * Copies an entry, assigning it a new `next` entry.
+ *
+ * @param original the entry to copy
+ * @param newNext entry in the same bucket
+ */
+ // Guarded By Segment.this
+ open fun copyEntry(
+ segment: Segment?,
+ original: ReferenceEntry,
+ newNext: ReferenceEntry?,
+ ): ReferenceEntry {
+ return newEntry(segment, original.key, original.hash, newNext)
+ }
- // Guarded By Segment.this
- fun copyAccessEntry(
- original: ReferenceEntry,
- newEntry: ReferenceEntry,
- ) {
- // TODO(fry): when we link values instead of entries this method can go
- // away, as can connectAccessOrder, nullifyAccessOrder.
- newEntry.accessTime = original.accessTime
- connectAccessOrder(original.previousInAccessQueue, newEntry)
- connectAccessOrder(newEntry, original.nextInAccessQueue)
- nullifyAccessOrder(original)
- }
+ // Guarded By Segment.this
+ fun copyAccessEntry(
+ original: ReferenceEntry,
+ newEntry: ReferenceEntry,
+ ) {
+ // TODO(fry): when we link values instead of entries this method can go
+ // away, as can connectAccessOrder, nullifyAccessOrder.
+ newEntry.accessTime = original.accessTime
+ connectAccessOrder(original.previousInAccessQueue, newEntry)
+ connectAccessOrder(newEntry, original.nextInAccessQueue)
+ nullifyAccessOrder(original)
+ }
- // Guarded By Segment.this
- fun copyWriteEntry(
- original: ReferenceEntry,
- newEntry: ReferenceEntry,
- ) {
- // TODO(fry): when we link values instead of entries this method can go
- // away, as can connectWriteOrder, nullifyWriteOrder.
- newEntry.writeTime = original.writeTime
- connectWriteOrder(original.previousInWriteQueue, newEntry)
- connectWriteOrder(newEntry, original.nextInWriteQueue)
- nullifyWriteOrder(original)
- }
+ // Guarded By Segment.this
+ fun copyWriteEntry(
+ original: ReferenceEntry,
+ newEntry: ReferenceEntry,
+ ) {
+ // TODO(fry): when we link values instead of entries this method can go
+ // away, as can connectWriteOrder, nullifyWriteOrder.
+ newEntry.writeTime = original.writeTime
+ connectWriteOrder(original.previousInWriteQueue, newEntry)
+ connectWriteOrder(newEntry, original.nextInWriteQueue)
+ nullifyWriteOrder(original)
+ }
- companion object {
- /**
- * Masks used to compute indices in the following table.
- */
- private const val ACCESS_MASK = 1
- private const val WRITE_MASK = 2
-
- /**
- * Look-up table for factories.
- */
- private val factories = arrayOf(Strong, StrongAccess, StrongWrite, StrongAccessWrite)
-
- fun getFactory(
- usesAccessQueue: Boolean,
- usesWriteQueue: Boolean,
- ): EntryFactory {
- val flags = ((if (usesAccessQueue) ACCESS_MASK else 0) or if (usesWriteQueue) WRITE_MASK else 0)
- return factories[flags]
- }
- }
+ companion object {
+ /** Masks used to compute indices in the following table. */
+ private const val ACCESS_MASK = 1
+ private const val WRITE_MASK = 2
+
+ /** Look-up table for factories. */
+ private val factories = arrayOf(Strong, StrongAccess, StrongWrite, StrongAccessWrite)
+
+ fun getFactory(usesAccessQueue: Boolean, usesWriteQueue: Boolean): EntryFactory {
+ val flags =
+ ((if (usesAccessQueue) ACCESS_MASK else 0) or if (usesWriteQueue) WRITE_MASK else 0)
+ return factories[flags]
+ }
}
+ }
+
+ /** A reference to a value. */
+ private interface ValueReference {
+ /** Returns the value. Does not block or throw exceptions. */
+ fun get(): V?
+
+ /** Returns the weight of this entry. This is assumed to be static between calls to setValue. */
+ val weight: Int
/**
- * A reference to a value.
+ * Returns the entry associated with this value reference, or `null` if this value reference is
+ * independent of any entry.
*/
- private interface ValueReference {
- /**
- * Returns the value. Does not block or throw exceptions.
- */
- fun get(): V?
-
- /**
- * Returns the weight of this entry. This is assumed to be static between calls to setValue.
- */
- val weight: Int
-
- /**
- * Returns the entry associated with this value reference, or `null` if this value
- * reference is independent of any entry.
- */
- val entry: ReferenceEntry?
-
- /**
- * Creates a copy of this reference for the given entry.
- *
- *
- *
- * `value` may be null only for a loading reference.
- */
-
- fun copyFor(
- value: V?,
- entry: ReferenceEntry?,
- ): ValueReference
-
- /**
- * Notifify pending loads that a new value was set. This is only relevant to loading
- * value references.
- */
- fun notifyNewValue(newValue: V)
-
- /**
- * Returns true if this reference contains an active value, meaning one that is still considered
- * present in the cache. Active values consist of live values, which are returned by cache
- * lookups, and dead values, which have been evicted but awaiting removal. Non-active values
- * consist strictly of loading values, though during refresh a value may be both active and
- * loading.
- */
- val isActive: Boolean
- }
+ val entry: ReferenceEntry?
/**
- * An entry in a reference map.
- *
- *
- * Entries in the map can be in the following states:
- *
- *
- * Valid:
- * - Live: valid key/value are set
- * - Loading: loading is pending
+ * Creates a copy of this reference for the given entry.
*
- *
- * Invalid:
- * - Expired: time expired (key/value may still be set)
- * - Collected: key/value was partially collected, but not yet cleaned up
- * - Unset: marked as unset, awaiting cleanup or reuse
+ * `value` may be null only for a loading reference.
*/
- private interface ReferenceEntry {
- /**
- * Returns the value reference from this entry.
- */
- /**
- * Sets the value reference for this entry.
- */
- var valueReference: ValueReference?
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
-
- /**
- * Returns the next entry in the chain.
- */
- val next: ReferenceEntry?
- get() = throw UnsupportedOperationException()
-
- /**
- * Returns the entry's hash.
- */
- val hash: Int
- get() = throw UnsupportedOperationException()
-
- /**
- * Returns the key for this entry.
- */
- val key: K
- get() = throw UnsupportedOperationException()
- /*
- * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
- * New entries are added at the tail of the list at write time; stale entries are expired from
- * the head of the list.
- */
- /**
- * Returns the time that this entry was last accessed, in ns.
- */
- /**
- * Sets the entry access time in ns.
- */
- var accessTime: Long
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- /**
- * Returns the next entry in the access queue.
- */
- /**
- * Sets the next entry in the access queue.
- */
- var nextInAccessQueue: ReferenceEntry
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- /**
- * Returns the previous entry in the access queue.
- */
- /**
- * Sets the previous entry in the access queue.
- */
- var previousInAccessQueue: ReferenceEntry
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- /*
- * Implemented by entries that use write order. Write entries are maintained in a
- * doubly-linked list. New entries are added at the tail of the list at write time and stale
- * entries are expired from the head of the list.
- */
- /**
- * Returns the time that this entry was last written, in ns.
- */
- /**
- * Sets the entry write time in ns.
- */
- var writeTime: Long
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- /**
- * Returns the next entry in the write queue.
- */
- /**
- * Sets the next entry in the write queue.
- */
- var nextInWriteQueue: ReferenceEntry
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- /**
- * Returns the previous entry in the write queue.
- */
- /**
- * Sets the previous entry in the write queue.
- */
- var previousInWriteQueue: ReferenceEntry
- get() = throw UnsupportedOperationException()
- set(_) = throw UnsupportedOperationException()
- }
-
- private object NullEntry : ReferenceEntry {
- override var valueReference: ValueReference?
- get() = null
- set(_) {}
-
- override val next: ReferenceEntry?
- get() = null
-
- override val hash: Int
- get() = 0
-
- override val key: Any
- get() = Unit
-
- override var accessTime: Long
- get() = 0
- set(_) {}
-
- override var nextInAccessQueue: ReferenceEntry
- get() = this
- set(_) {}
-
- override var previousInAccessQueue: ReferenceEntry
- get() = this
- set(_) {}
-
- override var writeTime: Long
- get() = 0
- set(_) {}
+ fun copyFor(value: V?, entry: ReferenceEntry?): ValueReference
- override var nextInWriteQueue: ReferenceEntry
- get() = this
- set(_) {}
+ /**
+ * Notifify pending loads that a new value was set. This is only relevant to loading value
+ * references.
+ */
+ fun notifyNewValue(newValue: V)
- override var previousInWriteQueue: ReferenceEntry
- get() = this
- set(_) {}
- }
+ /**
+ * Returns true if this reference contains an active value, meaning one that is still considered
+ * present in the cache. Active values consist of live values, which are returned by cache
+ * lookups, and dead values, which have been evicted but awaiting removal. Non-active values
+ * consist strictly of loading values, though during refresh a value may be both active and
+ * loading.
+ */
+ val isActive: Boolean
+ }
+
+ /**
+ * An entry in a reference map.
+ *
+ * Entries in the map can be in the following states:
+ *
+ * Valid:
+ * - Live: valid key/value are set
+ * - Loading: loading is pending
+ *
+ * Invalid:
+ * - Expired: time expired (key/value may still be set)
+ * - Collected: key/value was partially collected, but not yet cleaned up
+ * - Unset: marked as unset, awaiting cleanup or reuse
+ */
+ private interface ReferenceEntry {
+ /** Returns the value reference from this entry. */
+ /** Sets the value reference for this entry. */
+ var valueReference: ValueReference?
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+
+ /** Returns the next entry in the chain. */
+ val next: ReferenceEntry?
+ get() = throw UnsupportedOperationException()
+
+ /** Returns the entry's hash. */
+ val hash: Int
+ get() = throw UnsupportedOperationException()
+
+ /** Returns the key for this entry. */
+ val key: K
+ get() = throw UnsupportedOperationException()
/*
- * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins!
- * To maintain this code, make a change for the strong reference type. Then, cut and paste, and
- * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that
- * strong entries store the key reference directly while soft and weak entries delegate to their
- * respective superclasses.
+ * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
+ * New entries are added at the tail of the list at write time; stale entries are expired from
+ * the head of the list.
*/
+ /** Returns the time that this entry was last accessed, in ns. */
+ /** Sets the entry access time in ns. */
+ var accessTime: Long
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+
+ /** Returns the next entry in the access queue. */
+ /** Sets the next entry in the access queue. */
+ var nextInAccessQueue: ReferenceEntry
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+
+ /** Returns the previous entry in the access queue. */
+ /** Sets the previous entry in the access queue. */
+ var previousInAccessQueue: ReferenceEntry
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
- /**
- * Used for strongly-referenced keys.
+ /*
+ * Implemented by entries that use write order. Write entries are maintained in a
+ * doubly-linked list. New entries are added at the tail of the list at write time and stale
+ * entries are expired from the head of the list.
*/
- private open class StrongEntry(
- override val key: K, // The code below is exactly the same for each entry type.
- override val hash: Int,
- override val next: ReferenceEntry?,
- ) : ReferenceEntry {
- private val _valueReference = atomic?>(unset())
- override var valueReference: ValueReference? = _valueReference.value
- }
+ /** Returns the time that this entry was last written, in ns. */
+ /** Sets the entry write time in ns. */
+ var writeTime: Long
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+
+ /** Returns the next entry in the write queue. */
+ /** Sets the next entry in the write queue. */
+ var nextInWriteQueue: ReferenceEntry
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+
+ /** Returns the previous entry in the write queue. */
+ /** Sets the previous entry in the write queue. */
+ var previousInWriteQueue: ReferenceEntry
+ get() = throw UnsupportedOperationException()
+ set(_) = throw UnsupportedOperationException()
+ }
+
+ private object NullEntry : ReferenceEntry {
+ override var valueReference: ValueReference?
+ get() = null
+ set(_) {}
+
+ override val next: ReferenceEntry?
+ get() = null
+
+ override val hash: Int
+ get() = 0
+
+ override val key: Any
+ get() = Unit
+
+ override var accessTime: Long
+ get() = 0
+ set(_) {}
+
+ override var nextInAccessQueue: ReferenceEntry
+ get() = this
+ set(_) {}
+
+ override var previousInAccessQueue: ReferenceEntry
+ get() = this
+ set(_) {}
+
+ override var writeTime: Long
+ get() = 0
+ set(_) {}
+
+ override var nextInWriteQueue: ReferenceEntry
+ get() = this
+ set(_) {}
+
+ override var previousInWriteQueue: ReferenceEntry
+ get() = this
+ set(_) {}
+ }
+
+ /*
+ * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins!
+ * To maintain this code, make a change for the strong reference type. Then, cut and paste, and
+ * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that
+ * strong entries store the key reference directly while soft and weak entries delegate to their
+ * respective superclasses.
+ */
+
+ /** Used for strongly-referenced keys. */
+ private open class StrongEntry(
+ override val key: K, // The code below is exactly the same for each entry type.
+ override val hash: Int,
+ override val next: ReferenceEntry?,
+ ) : ReferenceEntry {
+ private val _valueReference = atomic?>(unset())
+ override var valueReference: ValueReference? = _valueReference.value
+ }
+
+ private class StrongAccessEntry(
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ) : StrongEntry(key, hash, next) {
+ // The code below is exactly the same for each access entry type.
+
+ private val _accessTime = atomic(Long.MAX_VALUE)
+ override var accessTime = _accessTime.value
- private class StrongAccessEntry(
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ) :
- StrongEntry(key, hash, next) {
- // The code below is exactly the same for each access entry type.
+ // Guarded By Segment.this
+ override var nextInAccessQueue: ReferenceEntry = nullEntry()
- private val _accessTime = atomic(Long.MAX_VALUE)
- override var accessTime = _accessTime.value
+ // Guarded By Segment.this
+ override var previousInAccessQueue: ReferenceEntry = nullEntry()
+ }
- // Guarded By Segment.this
- override var nextInAccessQueue: ReferenceEntry = nullEntry()
+ private class StrongWriteEntry(key: K, hash: Int, next: ReferenceEntry?) :
+ StrongEntry(key, hash, next) {
+ // The code below is exactly the same for each write entry type.
+ private val _writeTime = atomic(Long.MAX_VALUE)
+ override var writeTime = _writeTime.value
- // Guarded By Segment.this
- override var previousInAccessQueue: ReferenceEntry = nullEntry()
- }
+ // Guarded By Segment.this
+ override var nextInWriteQueue: ReferenceEntry = nullEntry()
- private class StrongWriteEntry(
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ) :
- StrongEntry(key, hash, next) {
- // The code below is exactly the same for each write entry type.
- private val _writeTime = atomic(Long.MAX_VALUE)
- override var writeTime = _writeTime.value
+ // Guarded By Segment.this
+ override var previousInWriteQueue: ReferenceEntry = nullEntry()
+ }
+
+ private class StrongAccessWriteEntry(
+ key: K,
+ hash: Int,
+ next: ReferenceEntry?,
+ ) : StrongEntry(key, hash, next) {
+ // The code below is exactly the same for each access entry type.
+ private val _accessTime = atomic(Long.MAX_VALUE)
+ override var accessTime: Long = _accessTime.value
- // Guarded By Segment.this
- override var nextInWriteQueue: ReferenceEntry = nullEntry()
+ // Guarded By Segment.this
+ override var nextInAccessQueue: ReferenceEntry = nullEntry()
- // Guarded By Segment.this
- override var previousInWriteQueue: ReferenceEntry = nullEntry()
- }
+ // Guarded By Segment.this
+ override var previousInAccessQueue: ReferenceEntry = nullEntry()
- private class StrongAccessWriteEntry(
- key: K,
- hash: Int,
- next: ReferenceEntry?,
- ) :
- StrongEntry(key, hash, next) {
- // The code below is exactly the same for each access entry type.
- private val _accessTime = atomic(Long.MAX_VALUE)
- override var accessTime: Long = _accessTime.value
+ // The code below is exactly the same for each write entry type.
+ private val _writeTime = atomic(Long.MAX_VALUE)
+ override var writeTime: Long = _writeTime.value
- // Guarded By Segment.this
- override var nextInAccessQueue: ReferenceEntry = nullEntry()
+ // Guarded By Segment.this
+ override var nextInWriteQueue: ReferenceEntry = nullEntry()
- // Guarded By Segment.this
- override var previousInAccessQueue: ReferenceEntry = nullEntry()
+ // Guarded By Segment.this
+ override var previousInWriteQueue: ReferenceEntry = nullEntry()
+ }
+
+ /** References a strong value. */
+ private open class StrongValueReference(private val referent: V) :
+ ValueReference {
+ override fun get(): V = referent
+
+ override val weight: Int = 1
+ override val entry: ReferenceEntry? = null
+
+ override fun copyFor(value: V?, entry: ReferenceEntry?): ValueReference = this
+
+ override val isActive: Boolean = true
+
+ override fun notifyNewValue(newValue: V) {}
+ }
+
+ /** References a strong value. */
+ private class WeightedStrongValueReference(
+ referent: V,
+ override val weight: Int,
+ ) : StrongValueReference(referent)
+
+ /** This method is a convenience for testing. Code should call [Segment.newEntry] directly. */
+ private fun newEntry(key: K, hash: Int, next: ReferenceEntry?): ReferenceEntry {
+ val segment = segmentFor(hash)
+ segment.reentrantLock.lock()
+ return try {
+ segment.newEntry(key, hash, next)
+ } finally {
+ segment.reentrantLock.unlock()
+ }
+ }
+
+ /** This method is a convenience for testing. Code should call [Segment.copyEntry] directly. */
+ // Guarded By Segment.this
+ private fun copyEntry(
+ original: ReferenceEntry,
+ newNext: ReferenceEntry?,
+ ): ReferenceEntry? {
+ val hash = original.hash
+ return segmentFor(hash).copyEntry(original, newNext)
+ }
+
+ /** This method is a convenience for testing. Code should call [Segment.setValue] instead. */
+ // Guarded By Segment.this
+ private fun newValueReference(
+ entry: ReferenceEntry,
+ value: V,
+ weight: Int,
+ ): ValueReference {
+ val hash = entry.hash
+ return valueStrength.referenceValue(segmentFor(hash), entry, value, weight)
+ }
+
+ private fun hash(key: K): Int = rehash(key.hashCode())
+
+ /**
+ * Returns the segment that should be used for a key with the given hash.
+ *
+ * @param hash the hash code for the key
+ * @return the segment
+ */
+ private fun segmentFor(hash: Int): Segment =
+ // TODO(fry): Lazily create segments?
+ segments[hash ushr segmentShift and segmentMask] as Segment
+
+ private fun createSegment(initialCapacity: Int, maxSegmentWeight: Long): Segment =
+ Segment(this, initialCapacity, maxSegmentWeight)
+
+ // expiration
+
+ /** Returns true if the entry has expired. */
+ private fun isExpired(entry: ReferenceEntry, now: Long): Boolean =
+ if (expiresAfterAccess && now - entry.accessTime >= expireAfterAccessNanos) {
+ true
+ } else {
+ expiresAfterWrite && now - entry.writeTime >= expireAfterWriteNanos
+ }
- // The code below is exactly the same for each write entry type.
- private val _writeTime = atomic(Long.MAX_VALUE)
- override var writeTime: Long = _writeTime.value
+ // Inner Classes
- // Guarded By Segment.this
- override var nextInWriteQueue: ReferenceEntry = nullEntry()
+ private class SegmentTable(val size: Int) {
+ private val table: AtomicArray?> = atomicArrayOfNulls(size)
- // Guarded By Segment.this
- override var previousInWriteQueue: ReferenceEntry = nullEntry()
+ operator fun get(idx: Int) = table[idx].value
+
+ operator fun set(idx: Int, value: ReferenceEntry?) {
+ table[idx].value = value
}
+ }
+
+ /** Segments are specialized versions of hash tables. */
+ private class Segment(
+ private val map: LocalCache,
+ initialCapacity: Int,
+ private val maxSegmentWeight: Long,
+ ) {
+ /*
+ * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class.
+ * It will require more memory but will reduce indirection.
+ */
+ /*
+ * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can
+ * be read without locking. Next fields of nodes are immutable (final). All list additions are
+ * performed at the front of each bin. This makes it easy to check changes, and also fast to
+ * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This
+ * works well for hash tables since the bin lists tend to be short. (The average length is less
+ * than two.)
+ *
+ * Read operations can thus proceed without locking, but rely on selected uses of volatiles to
+ * ensure that completed write operations performed by other threads are noticed. For most
+ * purposes, the "count" field, tracking the number of elements, serves as that volatile
+ * variable ensuring visibility. This is convenient because this field needs to be read in many
+ * read operations anyway:
+ *
+ * - All (unsynchronized) read operations must first read the "count" field, and should not
+ * look at table entries if it is 0.
+ *
+ * - All (synchronized) write operations should write to the "count" field after structurally
+ * changing any bin. The operations must not take any action that could even momentarily
+ * cause a concurrent read operation to see inconsistent data. This is made easier by the
+ * nature of the read operations in Map. For example, no operation can reveal that the table
+ * has grown but the threshold has not yet been updated, so there are no atomicity requirements
+ * for this with respect to reads.
+ *
+ * As a guide, all critical volatile reads and writes to the count field are marked in code
+ * comments.
+ */
+
+ val reentrantLock = reentrantLock()
+
+ /** The number of live elements in this segment's region. */
+ private val count = atomic(0)
+
+ /** The weight of the live elements in this segment's region. */
+ private var totalWeight: Long = 0
/**
- * References a strong value.
+ * Number of updates that alter the size of the table. This is used during bulk-read methods to
+ * make sure they see a consistent snapshot: If modCounts change during a traversal of segments
+ * loading size or checking containsValue, then we might have an inconsistent view of state so
+ * (usually) must retry.
*/
- private open class StrongValueReference(private val referent: V) :
- ValueReference {
- override fun get(): V = referent
+ private var modCount = 0
- override val weight: Int = 1
- override val entry: ReferenceEntry? = null
+ /**
+ * The table is expanded when its size exceeds this threshold. (The value of this field is
+ * always `(int) (capacity * 0.75)`.)
+ */
+ private var threshold = 0
- override fun copyFor(
- value: V?,
- entry: ReferenceEntry?,
- ): ValueReference = this
+ /** The per-segment table. */
+ private val table: AtomicRef>
- override val isActive: Boolean = true
+ /**
+ * The recency queue is used to record which entries were accessed for updating the access
+ * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is
+ * crossed or a write occurs on the segment.
+ */
+ private val recencyQueue: Queue>
- override fun notifyNewValue(newValue: V) {}
- }
+ /**
+ * A counter of the number of reads since the last write, used to drain queues on a small
+ * fraction of read operations.
+ */
+ private val readCount = atomic(0)
/**
- * References a strong value.
+ * A queue of elements currently in the map, ordered by write time. Elements are added to the
+ * tail of the queue on write.
*/
- private class WeightedStrongValueReference(
- referent: V,
- override val weight: Int,
- ) :
- StrongValueReference(referent)
+ private val writeQueue: MutableQueue