[K/N] Stabilization of Atomics API

`AtomicInt`, `AtomicLong`, `AtomicReference` and `AtomicNativePtr` classes were moved to `kotlin.concurrent` package. The corresponding classes from `kotlin.native.concurrent` were deprecated with warning since Kotlin 1.9.

In order to prepare for further commonization of Atomics API the following changes were made:
* `kotlin.concurrent.AtomicInt`: 
    * `increment(): Unit` and `decrement(): Unit` methods were deprecated with error
    * New methods were added: `incrementAndGet(): Int` , `decrementAndGet(): Int`, `getAndIncrement(): Int`, `getAndDecrement(): Int`, `getAndSet(newValue: Int): Int` 
* `kotlin.concurrent.AtomicLong`:
    * `increment(): Unit` and `decrement(): Unit` methods were deprecated with error
    * New methods were added: `incrementAndGet(): Long`, `decrementAndGet(): Long`, `getAndIncrement(): Long`, `getAndDecrement(): Long`, `getAndSet(newValue: Long): Long`
    * Deprecated `AtomicLong()` constructor with default parameter value
* For all atomic classes `compareAndSwap` method was renamed to `compareAndExchange`

See KT-58074 for more details.

Merge-request: KT-MR-9272
Merged-by: Maria Sokolova <maria.sokolova@jetbrains.com>
This commit is contained in:
mvicsokolova
2023-04-25 16:55:42 +00:00
committed by Space Team
parent 1c56a71b14
commit 75b4469757
33 changed files with 1162 additions and 493 deletions
@@ -1,177 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class)
package runtime.workers.atomic0
import kotlin.test.*
import kotlin.native.concurrent.*
fun test1(workers: Array<Worker>) {
val atomic = AtomicInt(15)
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].execute(TransferMode.SAFE, { atomic }) {
input -> input.addAndGet(1)
}
})
futures.forEach {
it.result
}
println(atomic.value)
}
fun test2(workers: Array<Worker>) {
val atomic = AtomicInt(1)
val counter = AtomicInt(0)
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].execute(TransferMode.SAFE, { Triple(atomic, workerIndex, counter) }) {
(place, index, result) ->
// Here we simulate mutex using [place] location to store tag of the current worker.
// When it is negative - worker executes exclusively.
val tag = index + 1
while (place.compareAndSwap(tag, -tag) != tag) {}
val ok1 = result.addAndGet(1) == index + 1
// Now, let the next worker run.
val ok2 = place.compareAndSwap(-tag, tag + 1) == -tag
ok1 && ok2
}
})
futures.forEach {
assertEquals(it.result, true)
}
println(counter.value)
}
data class Data(val value: Int)
fun test3(workers: Array<Worker>) {
val common = AtomicReference<Data?>(null)
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].execute(TransferMode.SAFE, { Pair(common, workerIndex) }) {
(place, index) ->
val mine = Data(index).freeze()
// Try to publish our own data, until successful, in a tight loop.
while (!place.compareAndSet(null, mine)) {}
}
})
val seen = mutableSetOf<Data>()
for (i in 0 until workers.size) {
do {
val current = common.value
if (current != null && !seen.contains(current)) {
seen += current
// Let others publish.
assertEquals(common.compareAndSwap(current, null), current)
break
}
} while (true)
}
futures.forEach {
it.result
}
assertEquals(seen.size, workers.size)
}
fun test4LegacyMM() {
assertFailsWith<InvalidMutabilityException> {
AtomicReference(Data(1))
}
assertFailsWith<InvalidMutabilityException> {
AtomicReference<Data?>(null).compareAndSwap(null, Data(2))
}
}
fun test4() {
run {
val ref = AtomicReference(Data(1))
assertEquals(1, ref.value.value)
}
run {
val ref = AtomicReference<Data?>(null)
ref.compareAndSwap(null, Data(2))
assertEquals(2, ref.value!!.value)
}
if (Platform.isFreezingEnabled) {
run {
val ref = AtomicReference<Data?>(null).freeze()
assertFailsWith<InvalidMutabilityException> {
ref.compareAndSwap(null, Data(2))
}
}
}
}
fun test5LegacyMM() {
assertFailsWith<InvalidMutabilityException> {
AtomicReference<Data?>(null).value = Data(2)
}
val ref = AtomicReference<Data?>(null)
val value = Data(3).freeze()
assertEquals(null, ref.value)
ref.value = value
assertEquals(3, ref.value!!.value)
}
fun test5() {
val ref = AtomicReference<Data?>(null)
ref.value = Data(2)
assertEquals(2, ref.value!!.value)
ref.value = Data(3).freeze()
assertEquals(3, ref.value!!.value)
}
fun test6() {
val int = AtomicInt(0)
int.value = 239
assertEquals(239, int.value)
val long = AtomicLong(0)
long.value = 239L
assertEquals(239L, long.value)
}
fun test7() {
val ref = FreezableAtomicReference(Array(1) { "hey" })
ref.value[0] = "ho"
assertEquals(ref.value[0], "ho")
ref.value = Array(1) { "po" }
assertEquals(ref.value[0], "po")
ref.freeze()
if (Platform.isFreezingEnabled) {
assertFailsWith<InvalidMutabilityException> {
ref.value = Array(1) { "no" }
}
assertFailsWith<InvalidMutabilityException> {
ref.value[0] = "go"
}
}
ref.value = Array(1) { "so" }.freeze()
assertEquals(ref.value[0], "so")
}
@Test fun runTest() {
val COUNT = 20
val workers = Array(COUNT, { _ -> Worker.start()})
test1(workers)
test2(workers)
test3(workers)
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
test4()
test5()
} else {
test4LegacyMM()
test5LegacyMM()
}
test6()
test7()
workers.forEach {
it.requestTermination().result
}
println("OK")
}
@@ -1,3 +0,0 @@
35
20
OK
@@ -1,60 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
// Note: This test reproduces a race, so it'll start flaking if problem is reintroduced.
@file:OptIn(kotlin.native.runtime.NativeRuntimeApi::class)
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.native.internal.*
import kotlin.native.runtime.GC
val thrashGC = AtomicInt(1)
val canStartCreating = AtomicInt(0)
val createdCount = AtomicInt(0)
val canStartReading = AtomicInt(0)
const val atomicsCount = 1000
const val workersCount = 10
fun main() {
val gcWorker = Worker.start()
val future = gcWorker.execute(TransferMode.SAFE, {}, {
canStartCreating.value = 1
while (thrashGC.value != 0) {
GC.collectCyclic()
}
GC.collect()
})
while (canStartCreating.value == 0) {}
val workers = Array(workersCount) { Worker.start() }
val futures = workers.map {
it.execute(TransferMode.SAFE, {}, {
val atomics = Array(atomicsCount) {
AtomicReference<Any?>(Any().freeze())
}
createdCount.increment()
while (canStartReading.value == 0) {}
GC.collect()
atomics.all { it.value != null }
})
}
while (createdCount.value != workersCount) {}
thrashGC.value = 0
future.result
GC.collect()
canStartReading.value = 1
assertTrue(futures.all { it.result })
for (worker in workers) {
worker.requestTermination().result
}
gcWorker.requestTermination().result
}
@@ -1,94 +0,0 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
import kotlin.test.*
import kotlin.native.concurrent.*
@Test
fun ctor_Int() {
val x = AtomicInt(0)
assertEquals(x.value, 0)
}
@Test
fun ctor_Long() {
val x = AtomicLong(0)
assertEquals(x.value, 0)
}
@Test
fun setter_Int() {
val x = AtomicInt(0)
x.value = 1
assertEquals(x.value, 1)
}
@Test
fun setter_Long() {
val x = AtomicLong(0)
x.value = 1
assertEquals(x.value, 1)
}
@Test
fun addAndGet_Int() {
val x = AtomicInt(1)
val result = x.addAndGet(2)
assertEquals(result, 1 + 2)
assertEquals(x.value, result)
}
@Test
fun addAndGet_Long() {
val x = AtomicLong(1)
val result = x.addAndGet(2)
assertEquals(result, 1 + 2)
assertEquals(x.value, result)
}
@Test
fun compareAndSwap_Int() {
val x = AtomicInt(0)
val successValue = x.compareAndSwap(0, 1)
assertEquals(successValue, 0)
assertEquals(x.value, 1)
val failValue = x.compareAndSwap(0, 2)
assertEquals(failValue, 1)
assertEquals(x.value, 1)
}
@Test
fun compareAndSwap_Long() {
val x = AtomicLong(0)
val successValue = x.compareAndSwap(0, 1)
assertEquals(successValue, 0)
assertEquals(x.value, 1)
val failValue = x.compareAndSwap(0, 2)
assertEquals(failValue, 1)
assertEquals(x.value, 1)
}
@Test
fun compareAndSet_Int() {
val x = AtomicInt(0)
val successValue = x.compareAndSet(0, 1)
assertTrue(successValue)
assertEquals(x.value, 1)
val failValue = x.compareAndSet(0, 2)
assertFalse(failValue)
assertEquals(x.value, 1)
}
@Test
fun compareAndSet_Long() {
val x = AtomicLong(0)
val successValue = x.compareAndSet(0, 1)
assertTrue(successValue)
assertEquals(x.value, 1)
val failValue = x.compareAndSet(0, 2)
assertFalse(failValue)
assertEquals(x.value, 1)
}
@@ -9,6 +9,8 @@ package runtime.workers.lazy4
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.concurrent.*
import kotlin.concurrent.AtomicInt
const val WORKERS_COUNT = 20
@@ -24,7 +26,7 @@ fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) {
val initializerCallCount = AtomicInt(0)
val c = C(argumentMode) {
initializerCallCount.increment()
initializerCallCount.incrementAndGet()
IntHolder(42)
}
if (freeze) {
@@ -36,7 +38,7 @@ fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) {
val canStart = AtomicInt(0)
val futures = Array(workers.size) { i ->
workers[i].execute(TransferMode.SAFE, { Triple(inited, canStart, c) }) { (inited, canStart, c) ->
inited.increment()
inited.incrementAndGet()
while (canStart.value != 1) {}
c.data
}
@@ -4,6 +4,9 @@ package runtime.workers.worker10
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.concurrent.*
import kotlin.concurrent.AtomicInt
import kotlin.concurrent.AtomicReference
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.StableRef
@@ -93,14 +96,14 @@ val semaphore = AtomicInt(0)
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { null }) {
val value = atomicRef.value
semaphore.increment()
semaphore.incrementAndGet()
while (semaphore.value != 2) {}
println(value.toString() != "")
}
while (semaphore.value != 1) {}
atomicRef.value = null
kotlin.native.runtime.GC.collect()
semaphore.increment()
semaphore.incrementAndGet()
future.result
worker.requestTermination().result
}
@@ -110,14 +113,14 @@ val semaphore = AtomicInt(0)
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { null }) {
val value = stableRef.get()
semaphore.increment()
semaphore.incrementAndGet()
while (semaphore.value != 2) {}
println(value.toString() != "")
}
while (semaphore.value != 1) {}
stableRef.dispose()
kotlin.native.runtime.GC.collect()
semaphore.increment()
semaphore.incrementAndGet()
future.result
worker.requestTermination().result
}
@@ -133,7 +136,7 @@ val stableHolder1 = StableRef.create(("hello" to "world").freeze())
semaphore.value = 0
val future = worker.execute(TransferMode.SAFE, { WeakReference(stableHolder1.get()) }) {
ensureWeakIs(it, "hello" to "world")
semaphore.increment()
semaphore.incrementAndGet()
while (semaphore.value != 2) {}
kotlin.native.runtime.GC.collect()
ensureWeakIs(it, null)
@@ -141,7 +144,7 @@ val stableHolder1 = StableRef.create(("hello" to "world").freeze())
while (semaphore.value != 1) {}
stableHolder1.dispose()
kotlin.native.runtime.GC.collect()
semaphore.increment()
semaphore.incrementAndGet()
future.result
worker.requestTermination().result
}
@@ -153,7 +156,7 @@ val stableHolder2 = StableRef.create(("hello" to "world").freeze())
semaphore.value = 0
val future = worker.execute(TransferMode.SAFE, { WeakReference(stableHolder2.get()) }) {
val value = it.get()
semaphore.increment()
semaphore.incrementAndGet()
while (semaphore.value != 2) {}
kotlin.native.runtime.GC.collect()
assertEquals("hello" to "world", value)
@@ -161,7 +164,7 @@ val stableHolder2 = StableRef.create(("hello" to "world").freeze())
while (semaphore.value != 1) {}
stableHolder2.dispose()
kotlin.native.runtime.GC.collect()
semaphore.increment()
semaphore.incrementAndGet()
future.result
worker.requestTermination().result
}
@@ -171,15 +174,15 @@ val atomicRef2 = AtomicReference<Any?>(Any().freeze())
semaphore.value = 0
val worker = Worker.start()
val future = worker.execute(TransferMode.SAFE, { null }) {
val value = atomicRef2.compareAndSwap(null, null)
semaphore.increment()
val value = atomicRef2.compareAndExchange(null, null)
semaphore.incrementAndGet()
while (semaphore.value != 2) {}
assertEquals(true, value.toString() != "")
}
while (semaphore.value != 1) {}
atomicRef2.value = null
kotlin.native.runtime.GC.collect()
semaphore.increment()
semaphore.incrementAndGet()
future.result
worker.requestTermination().result
}
@@ -9,6 +9,8 @@ package runtime.workers.worker11
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.concurrent.*
import kotlin.concurrent.AtomicInt
import kotlinx.cinterop.convert
data class Job(val index: Int, var input: Int, var counter: Int)
@@ -79,7 +81,7 @@ val counters = Array(COUNT) { AtomicInt(0) }
workerIndex
}) { index ->
assertEquals(0, counters[index].value)
counters[index].increment()
counters[index].incrementAndGet()
}
}
futures2.forEach { it.result }
@@ -7,8 +7,8 @@
package runtime.workers.worker4
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.concurrent.AtomicInt
@Test fun runTest1() {
withWorker {
@@ -32,13 +32,14 @@ import kotlin.native.concurrent.*
assertTrue(Worker.current.processQueue())
assertEquals(1, counter.value)
// Let main proceed.
counter.increment() // counter becomes 2 here.
counter.incrementAndGet() // counter becomes 2 here.
assertTrue(Worker.current.park(10_000_000, true))
assertEquals(3, counter.value)
}.freeze())
executeAfter(0, {
counter.increment()
counter.incrementAndGet()
Unit
}.freeze())
while (counter.value < 2) {
@@ -46,7 +47,8 @@ import kotlin.native.concurrent.*
}
executeAfter(0, {
counter.increment()
counter.incrementAndGet()
Unit
}.freeze())
while (counter.value == 2) {
@@ -60,7 +62,8 @@ import kotlin.native.concurrent.*
val counter = AtomicInt(0)
worker.executeAfter(0, {
assertEquals("Lumberjack", Worker.current.name)
counter.increment()
counter.incrementAndGet()
Unit
}.freeze())
while (counter.value == 0) {
@@ -76,7 +79,8 @@ import kotlin.native.concurrent.*
@Test fun runTest4() {
val counter = AtomicInt(0)
Worker.current.executeAfter(10_000, {
counter.increment()
counter.incrementAndGet()
Unit
}.freeze())
assertTrue(Worker.current.park(1_000_000, process = true))
assertEquals(1, counter.value)
@@ -88,7 +92,8 @@ import kotlin.native.concurrent.*
withWorker {
executeAfter(1000, {
main.executeAfter(1, {
counter.increment()
counter.incrementAndGet()
Unit
}.freeze())
}.freeze())
assertTrue(main.park(1000L * 1000 * 1000, process = true))
@@ -106,13 +111,13 @@ import kotlin.native.concurrent.*
withWorker {
val f1 = execute(TransferMode.SAFE, { counter }) { counter ->
Worker.current.park(Long.MAX_VALUE / 1000L, process = true)
counter.increment()
counter.incrementAndGet()
}
// wait a bit
Worker.current.park(10_000L)
// submit a task
val f2 = execute(TransferMode.SAFE, { counter }) { counter ->
counter.increment()
counter.incrementAndGet()
}
f1.consume {}
f2.consume {}
@@ -139,14 +144,15 @@ import kotlin.native.concurrent.*
submitters.forEach {
it.executeAfter(0L, {
readySubmittersCounter.increment()
readySubmittersCounter.incrementAndGet()
// Wait for other submitters, to make them all start at the same time:
while (readySubmittersCounter.value != numberOfSubmitters) {}
// Concurrently submit tasks with matching scheduled execution time:
repeat(numberOfTasks) {
targetWorker.executeAfter(delayInMicroseconds, {
executedTasksCounter.increment()
executedTasksCounter.incrementAndGet()
Unit
}.freeze())
}
@@ -157,7 +163,8 @@ import kotlin.native.concurrent.*
// the test still might hang without a fix.
targetWorker.executeAfter(delayInMicroseconds + 1, {
mainWorker.executeAfter(0L, {
finishedBatchesCounter.increment()
finishedBatchesCounter.incrementAndGet()
Unit
}.freeze())
}.freeze())
}.freeze())