[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:
@@ -8,6 +8,7 @@ val K = if (true) "K" else "A" // to avoid const init
|
||||
// FILE: main.kt
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.concurrent.AtomicInt
|
||||
|
||||
val sem = AtomicInt(0)
|
||||
|
||||
@@ -18,7 +19,7 @@ fun box() : String {
|
||||
mode = TransferMode.SAFE,
|
||||
{ },
|
||||
{
|
||||
sem.increment();
|
||||
sem.incrementAndGet();
|
||||
while (sem.value != 3) {}
|
||||
O
|
||||
}
|
||||
@@ -27,7 +28,7 @@ fun box() : String {
|
||||
mode = TransferMode.SAFE,
|
||||
{ },
|
||||
{
|
||||
sem.increment();
|
||||
sem.incrementAndGet();
|
||||
while (sem.value != 3) {}
|
||||
K
|
||||
}
|
||||
@@ -38,4 +39,4 @@ fun box() : String {
|
||||
w1.requestTermination().result
|
||||
w2.requestTermination().result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,5 +22,5 @@ import kotlin.concurrent.*
|
||||
fun box() : String {
|
||||
val o = "O"
|
||||
val x = Box(o)
|
||||
return x::value.compareAndSwapField(o, "K") + x.value
|
||||
return x::value.compareAndExchangeField(o, "K") + x.value
|
||||
}
|
||||
+6
-6
@@ -38,28 +38,28 @@ interface RefWrapper<T> : Wrapper<T> {
|
||||
|
||||
|
||||
class IntWrapper(@Volatile var x : Int) : IncWrapper<Int> {
|
||||
override fun compareAndSwap(expected: Int, new: Int) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: Int, new: Int) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: Int, new: Int) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: Int) = this::x.getAndSetField(new)
|
||||
override fun getAndAdd(delta: Int) = this::x.getAndAddFieldLocal(delta)
|
||||
}
|
||||
|
||||
class LongWrapper(@Volatile var x : Long) : IncWrapper<Long> {
|
||||
override fun compareAndSwap(expected: Long, new: Long) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: Long, new: Long) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: Long, new: Long) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: Long) = this::x.getAndSetField(new)
|
||||
override fun getAndAdd(delta: Long) = this::x.getAndAddFieldLocal(delta)
|
||||
}
|
||||
|
||||
class ShortWrapper(@Volatile var x : Short) : IncWrapper<Short> {
|
||||
override fun compareAndSwap(expected: Short, new: Short) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: Short, new: Short) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: Short, new: Short) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: Short) = this::x.getAndSetField(new)
|
||||
override fun getAndAdd(delta: Short) = this::x.getAndAddFieldLocal(delta)
|
||||
}
|
||||
|
||||
class ByteWrapper(@Volatile var x : Byte) : IncWrapper<Byte> {
|
||||
override fun compareAndSwap(expected: Byte, new: Byte) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: Byte, new: Byte) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: Byte, new: Byte) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: Byte) = this::x.getAndSetField(new)
|
||||
override fun getAndAdd(delta: Byte) = this::x.getAndAddFieldLocal(delta)
|
||||
@@ -67,13 +67,13 @@ class ByteWrapper(@Volatile var x : Byte) : IncWrapper<Byte> {
|
||||
|
||||
|
||||
class StringWrapper(@Volatile var x : String) : RefWrapper<String> {
|
||||
override fun compareAndSwap(expected: String, new: String) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: String, new: String) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: String, new: String) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: String) = this::x.getAndSetField(new)
|
||||
}
|
||||
|
||||
class GenericWrapper<T>(@Volatile var x : T) : RefWrapper<T> {
|
||||
override fun compareAndSwap(expected: T, new: T) = this::x.compareAndSwapField(expected, new)
|
||||
override fun compareAndSwap(expected: T, new: T) = this::x.compareAndExchangeField(expected, new)
|
||||
override fun compareAndSet(expected: T, new: T) = this::x.compareAndSetField(expected, new)
|
||||
override fun getAndSet(new: T) = this::x.getAndSetField(new)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ val c = "3"
|
||||
fun box() : String {
|
||||
if (::x.compareAndSetField(1, 2) != true) return "FAIL Int: 1"
|
||||
if (::x.compareAndSetField(1, 2) != false) return "FAIL Int: 2"
|
||||
if (::x.compareAndSwapField(2, 1) != 2) return "FAIL Int: 3"
|
||||
if (::x.compareAndSwapField(2, 1) != 1) return "FAIL Int: 4"
|
||||
if (::x.compareAndExchangeField(2, 1) != 2) return "FAIL Int: 3"
|
||||
if (::x.compareAndExchangeField(2, 1) != 1) return "FAIL Int: 4"
|
||||
if (::x.getAndSetField(3) != 1) return "FAIL Int: 5"
|
||||
if (::x.getAndSetField(1) != 3) return "FAIL Int: 6"
|
||||
if (::x.getAndAddFieldLocal(1) != 1) return "FAIL Int: 7"
|
||||
@@ -45,8 +45,8 @@ fun box() : String {
|
||||
|
||||
if (::y.compareAndSetField(1L, 2L) != true) return "FAIL Long: 1"
|
||||
if (::y.compareAndSetField(1L, 2L) != false) return "FAIL Long: 2"
|
||||
if (::y.compareAndSwapField(2L, 1L) != 2L) return "FAIL Long: 3"
|
||||
if (::y.compareAndSwapField(2L, 1L) != 1L) return "FAIL Long: 4"
|
||||
if (::y.compareAndExchangeField(2L, 1L) != 2L) return "FAIL Long: 3"
|
||||
if (::y.compareAndExchangeField(2L, 1L) != 1L) return "FAIL Long: 4"
|
||||
if (::y.getAndSetField(3L) != 1L) return "FAIL Long: 5"
|
||||
if (::y.getAndSetField(1L) != 3L) return "FAIL Long: 6"
|
||||
if (::y.getAndAddFieldLocal(1L) != 1L) return "FAIL Long: 7"
|
||||
@@ -57,8 +57,8 @@ fun box() : String {
|
||||
if (isExperimentalMM()) {
|
||||
if (::z.compareAndSetField(a, b) != true) return "FAIL String: 1"
|
||||
if (::z.compareAndSetField(a, b) != false) return "FAIL String: 2"
|
||||
if (::z.compareAndSwapField(b, a) != b) return "FAIL String: 3"
|
||||
if (::z.compareAndSwapField(b, a) != a) return "FAIL String: 4"
|
||||
if (::z.compareAndExchangeField(b, a) != b) return "FAIL String: 3"
|
||||
if (::z.compareAndExchangeField(b, a) != a) return "FAIL String: 4"
|
||||
if (::z.getAndSetField(c) != a) return "FAIL String: 5"
|
||||
if (::z.getAndSetField(a) != c) return "FAIL String: 6"
|
||||
if (z != a) return "FAIL String: 7"
|
||||
@@ -66,8 +66,8 @@ fun box() : String {
|
||||
|
||||
if (::t.compareAndSetField(true, false) != true) return "FAIL Bool: 1"
|
||||
if (::t.compareAndSetField(true, false) != false) return "FAIL Bool: 2"
|
||||
if (::t.compareAndSwapField(false, true) != false) return "FAIL Bool: 3"
|
||||
if (::t.compareAndSwapField(false, true) != true) return "FAIL Bool: 4"
|
||||
if (::t.compareAndExchangeField(false, true) != false) return "FAIL Bool: 3"
|
||||
if (::t.compareAndExchangeField(false, true) != true) return "FAIL Bool: 4"
|
||||
if (::t.getAndSetField(false) != true) return "FAIL Bool: 5"
|
||||
if (::t.getAndSetField(true) != false) return "FAIL Bool: 6"
|
||||
if (t != true) return "FAIL Bool: 7"
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
internal class NativeMapping : DefaultMapping() {
|
||||
data class BridgeKey(val target: IrSimpleFunction, val bridgeDirections: BridgeDirections)
|
||||
enum class AtomicFunctionType {
|
||||
COMPARE_AND_SWAP, COMPARE_AND_SET, GET_AND_SET, GET_AND_ADD;
|
||||
COMPARE_AND_EXCHANGE, COMPARE_AND_SET, GET_AND_SET, GET_AND_ADD;
|
||||
}
|
||||
data class AtomicFunctionKey(val field: IrField, val type: AtomicFunctionType)
|
||||
|
||||
|
||||
+4
-4
@@ -94,11 +94,11 @@ internal enum class IntrinsicType {
|
||||
WORKER_EXECUTE,
|
||||
// Atomics
|
||||
COMPARE_AND_SET_FIELD,
|
||||
COMPARE_AND_SWAP_FIELD,
|
||||
COMPARE_AND_EXCHANGE_FIELD,
|
||||
GET_AND_SET_FIELD,
|
||||
GET_AND_ADD_FIELD,
|
||||
COMPARE_AND_SET,
|
||||
COMPARE_AND_SWAP,
|
||||
COMPARE_AND_EXCHANGE,
|
||||
GET_AND_SET,
|
||||
GET_AND_ADD,
|
||||
}
|
||||
@@ -255,7 +255,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
||||
IntrinsicType.IS_EXPERIMENTAL_MM -> emitIsExperimentalMM()
|
||||
IntrinsicType.THE_UNIT_INSTANCE -> theUnitInstanceRef.llvm
|
||||
IntrinsicType.COMPARE_AND_SET -> emitCompareAndSet(callSite, args)
|
||||
IntrinsicType.COMPARE_AND_SWAP -> emitCompareAndSwap(callSite, args, resultSlot)
|
||||
IntrinsicType.COMPARE_AND_EXCHANGE -> emitCompareAndSwap(callSite, args, resultSlot)
|
||||
IntrinsicType.GET_AND_SET -> emitGetAndSet(callSite, args, resultSlot)
|
||||
IntrinsicType.GET_AND_ADD -> emitGetAndAdd(callSite, args)
|
||||
IntrinsicType.GET_CONTINUATION,
|
||||
@@ -271,7 +271,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
||||
IntrinsicType.ENUM_VALUE_OF,
|
||||
IntrinsicType.WORKER_EXECUTE,
|
||||
IntrinsicType.COMPARE_AND_SET_FIELD,
|
||||
IntrinsicType.COMPARE_AND_SWAP_FIELD,
|
||||
IntrinsicType.COMPARE_AND_EXCHANGE_FIELD,
|
||||
IntrinsicType.GET_AND_SET_FIELD,
|
||||
IntrinsicType.GET_AND_ADD_FIELD ->
|
||||
reportNonLoweredIntrinsic(intrinsicType)
|
||||
|
||||
+6
-6
@@ -110,8 +110,8 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
|
||||
private fun compareAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SET) {
|
||||
this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SET, this.context.irBuiltIns.booleanType)
|
||||
}
|
||||
private fun compareAndSwapFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SWAP) {
|
||||
this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SWAP, irField.type)
|
||||
private fun compareAndExchangeFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_EXCHANGE) {
|
||||
this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_EXCHANGE, irField.type)
|
||||
}
|
||||
private fun getAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.GET_AND_SET) {
|
||||
this.buildAtomicRWMFunction(irField, IntrinsicType.GET_AND_SET)
|
||||
@@ -152,7 +152,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
|
||||
} else {
|
||||
listOfNotNull(it,
|
||||
compareAndSetFunction(field),
|
||||
compareAndSwapFunction(field),
|
||||
compareAndExchangeFunction(field),
|
||||
getAndSetFunction(field),
|
||||
if (field.isInteger()) getAndAddFunction(field) else null
|
||||
)
|
||||
@@ -198,7 +198,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
|
||||
|
||||
private val intrinsicMap = mapOf(
|
||||
IntrinsicType.COMPARE_AND_SET_FIELD to ::compareAndSetFunction,
|
||||
IntrinsicType.COMPARE_AND_SWAP_FIELD to ::compareAndSwapFunction,
|
||||
IntrinsicType.COMPARE_AND_EXCHANGE_FIELD to ::compareAndExchangeFunction,
|
||||
IntrinsicType.GET_AND_SET_FIELD to ::getAndSetFunction,
|
||||
IntrinsicType.GET_AND_ADD_FIELD to ::getAndAddFunction,
|
||||
)
|
||||
@@ -221,7 +221,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
|
||||
return builder.irCall(function).apply {
|
||||
dispatchReceiver = reference.dispatchReceiver
|
||||
putValueArgument(0, expression.getValueArgument(0))
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_SET_FIELD || intrinsicType == IntrinsicType.COMPARE_AND_SWAP_FIELD) {
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_SET_FIELD || intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE_FIELD) {
|
||||
putValueArgument(1, expression.getValueArgument(1))
|
||||
}
|
||||
}.let {
|
||||
@@ -229,7 +229,7 @@ internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
|
||||
for (arg in 0 until it.valueArgumentsCount) {
|
||||
it.putValueArgument(arg, builder.irBoolToByte(it.getValueArgument(arg)!!))
|
||||
}
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_SWAP_FIELD || intrinsicType == IntrinsicType.GET_AND_SET_FIELD) {
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE_FIELD || intrinsicType == IntrinsicType.GET_AND_SET_FIELD) {
|
||||
builder.irByteToBool(it)
|
||||
} else {
|
||||
it
|
||||
|
||||
+1
-1
@@ -346,7 +346,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
||||
expressions += jobInvocation to currentLoop
|
||||
}
|
||||
val intrinsicType = tryGetIntrinsicType(expression)
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_SET || intrinsicType == IntrinsicType.COMPARE_AND_SWAP) {
|
||||
if (intrinsicType == IntrinsicType.COMPARE_AND_SET || intrinsicType == IntrinsicType.COMPARE_AND_EXCHANGE) {
|
||||
expressions += IrSetFieldImpl(
|
||||
expression.startOffset, expression.endOffset,
|
||||
context.mapping.functionToVolatileField[expression.symbol.owner]!!.symbol,
|
||||
|
||||
+2
-1
@@ -803,7 +803,8 @@ internal object EscapeAnalysis {
|
||||
context.log { "An external call: $callee" }
|
||||
if (callee.name?.startsWith("kfun:kotlin.") == true
|
||||
// TODO: Is it possible to do it in a more fine-grained fashion?
|
||||
&& !callee.name.startsWith("kfun:kotlin.native.concurrent")) {
|
||||
&& !callee.name.startsWith("kfun:kotlin.native.concurrent")
|
||||
&& !callee.name.startsWith("kfun:kotlin.concurrent")) {
|
||||
context.log { "A function from K/N runtime - can use annotations" }
|
||||
FunctionEscapeAnalysisResult.fromBits(
|
||||
callee.escapes ?: 0,
|
||||
|
||||
@@ -1165,21 +1165,26 @@ standaloneTest("freeze_disabled") {
|
||||
testLogger = KonanTest.Logger.SILENT
|
||||
}
|
||||
|
||||
task atomicSmokeTest(type: KonanLocalTest) {
|
||||
enabled = isExperimentalMM // do not run the test on the legacy MM
|
||||
source = "runtime/atomics/atomic_smoke.kt"
|
||||
}
|
||||
|
||||
task atomicStressTest(type: KonanLocalTest) {
|
||||
enabled = isExperimentalMM // do not run the test on the legacy MM
|
||||
source = "runtime/atomics/atomic_stress.kt"
|
||||
}
|
||||
|
||||
task atomic0(type: KonanLocalTest) {
|
||||
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||
useGoldenData = true
|
||||
source = "runtime/workers/atomic0.kt"
|
||||
source = "runtime/atomics/atomic0.kt"
|
||||
}
|
||||
|
||||
standaloneTest("atomic1") {
|
||||
// Note: This test reproduces a race, so it'll start flaking if problem is reintroduced.
|
||||
enabled = false // Needs USE_CYCLIC_GC, which is disabled.
|
||||
source = "runtime/workers/atomic1.kt"
|
||||
}
|
||||
|
||||
task atomic2(type: KonanLocalTest) {
|
||||
source = "runtime/workers/atomic2.kt"
|
||||
source = "runtime/atomics/atomic1.kt"
|
||||
}
|
||||
|
||||
task lazy0(type: KonanLocalTest) {
|
||||
|
||||
+13
-10
@@ -1,14 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class)
|
||||
package runtime.workers.atomic0
|
||||
package runtime.atomics.atomic0
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.concurrent.AtomicInt
|
||||
import kotlin.concurrent.AtomicLong
|
||||
import kotlin.concurrent.AtomicReference
|
||||
|
||||
fun test1(workers: Array<Worker>) {
|
||||
val atomic = AtomicInt(15)
|
||||
@@ -32,10 +35,10 @@ fun test2(workers: Array<Worker>) {
|
||||
// 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) {}
|
||||
while (place.compareAndExchange(tag, -tag) != tag) {}
|
||||
val ok1 = result.addAndGet(1) == index + 1
|
||||
// Now, let the next worker run.
|
||||
val ok2 = place.compareAndSwap(-tag, tag + 1) == -tag
|
||||
val ok2 = place.compareAndExchange(-tag, tag + 1) == -tag
|
||||
ok1 && ok2
|
||||
}
|
||||
})
|
||||
@@ -64,7 +67,7 @@ fun test3(workers: Array<Worker>) {
|
||||
if (current != null && !seen.contains(current)) {
|
||||
seen += current
|
||||
// Let others publish.
|
||||
assertEquals(common.compareAndSwap(current, null), current)
|
||||
assertEquals(common.compareAndExchange(current, null), current)
|
||||
break
|
||||
}
|
||||
} while (true)
|
||||
@@ -80,7 +83,7 @@ fun test4LegacyMM() {
|
||||
AtomicReference(Data(1))
|
||||
}
|
||||
assertFailsWith<InvalidMutabilityException> {
|
||||
AtomicReference<Data?>(null).compareAndSwap(null, Data(2))
|
||||
AtomicReference<Data?>(null).compareAndExchange(null, Data(2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +94,14 @@ fun test4() {
|
||||
}
|
||||
run {
|
||||
val ref = AtomicReference<Data?>(null)
|
||||
ref.compareAndSwap(null, Data(2))
|
||||
ref.compareAndExchange(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))
|
||||
ref.compareAndExchange(null, Data(2))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +135,7 @@ fun test6() {
|
||||
assertEquals(239L, long.value)
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
fun test7() {
|
||||
val ref = FreezableAtomicReference(Array(1) { "hey" })
|
||||
ref.value[0] = "ho"
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(FreezingIsDeprecated::class)
|
||||
package runtime.atomics.atomic_smoke
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.concurrent.AtomicInt
|
||||
import kotlin.concurrent.AtomicLong
|
||||
import kotlin.concurrent.AtomicReference
|
||||
import kotlin.concurrent.AtomicNativePtr
|
||||
import kotlin.native.internal.NativePtr
|
||||
|
||||
@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(2L)
|
||||
assertEquals(result, 1 + 2)
|
||||
assertEquals(x.value, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compareAndSwap_Int() {
|
||||
val x = AtomicInt(0)
|
||||
val successValue = x.compareAndExchange(0, 1)
|
||||
assertEquals(successValue, 0)
|
||||
assertEquals(x.value, 1)
|
||||
val failValue = x.compareAndExchange(0, 2)
|
||||
assertEquals(failValue, 1)
|
||||
assertEquals(x.value, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compareAndSwap_Long() {
|
||||
val x = AtomicLong(0)
|
||||
val successValue = x.compareAndExchange(0, 1)
|
||||
assertEquals(successValue, 0)
|
||||
assertEquals(x.value, 1)
|
||||
val failValue = x.compareAndExchange(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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAtomicInt() {
|
||||
val atomic = AtomicInt(3)
|
||||
assertEquals(3, atomic.value)
|
||||
atomic.value = 5
|
||||
assertEquals(5, atomic.value)
|
||||
assertEquals(5, atomic.getAndSet(6))
|
||||
assertEquals(6, atomic.value)
|
||||
assertTrue(atomic.compareAndSet(6, 8))
|
||||
assertFalse(atomic.compareAndSet(9, 1))
|
||||
assertEquals(8, atomic.value)
|
||||
assertEquals(8, atomic.getAndAdd(5))
|
||||
assertEquals(13, atomic.value)
|
||||
assertEquals(18, atomic.addAndGet(5))
|
||||
assertEquals(18, atomic.getAndIncrement())
|
||||
assertEquals(19, atomic.value)
|
||||
assertEquals(20, atomic.incrementAndGet())
|
||||
assertEquals(20, atomic.value)
|
||||
assertEquals(20, atomic.getAndDecrement())
|
||||
assertEquals(19, atomic.value)
|
||||
assertEquals(18, atomic.decrementAndGet())
|
||||
assertEquals(18, atomic.value)
|
||||
assertEquals(18, atomic.compareAndExchange(18, 56))
|
||||
assertEquals(56, atomic.compareAndExchange(18, 56))
|
||||
assertEquals(56, atomic.compareAndExchange(18, 56))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAtomicLong() {
|
||||
val atomic = AtomicLong(1424920024888900000)
|
||||
assertEquals(1424920024888900000, atomic.value)
|
||||
atomic.value = 2424920024888900000
|
||||
assertEquals(2424920024888900000, atomic.value)
|
||||
assertEquals(2424920024888900000, atomic.getAndSet(3424920024888900000))
|
||||
assertEquals(3424920024888900000, atomic.value)
|
||||
assertTrue(atomic.compareAndSet(3424920024888900000, 4424920024888900000))
|
||||
assertFalse(atomic.compareAndSet(9, 1))
|
||||
assertEquals(4424920024888900000, atomic.value)
|
||||
assertEquals(4424920024888900000, atomic.getAndAdd(100000))
|
||||
assertEquals(4424920024889000000, atomic.value)
|
||||
assertEquals(4424920024890000000, atomic.addAndGet(1000000L))
|
||||
assertEquals(4424920024890000000, atomic.getAndIncrement())
|
||||
assertEquals(4424920024890000001, atomic.value)
|
||||
assertEquals(4424920024890000002, atomic.incrementAndGet())
|
||||
assertEquals(4424920024890000002, atomic.value)
|
||||
assertEquals(4424920024890000002, atomic.getAndDecrement())
|
||||
assertEquals(4424920024890000001, atomic.value)
|
||||
assertEquals(4424920024890000000, atomic.decrementAndGet())
|
||||
assertEquals(4424920024890000000, atomic.value)
|
||||
assertEquals(4424920024890000000, atomic.compareAndExchange(4424920024890000000, 5424920024890000000))
|
||||
assertEquals(5424920024890000000, atomic.compareAndExchange(18, 56))
|
||||
assertEquals(5424920024890000000, atomic.compareAndExchange(18, 56))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAtomicRef() {
|
||||
val atomic = AtomicReference<List<String>>(listOf("a", "b", "c"))
|
||||
assertEquals(listOf("a", "b", "c"), atomic.value)
|
||||
atomic.value = listOf("a", "b", "a")
|
||||
assertEquals(listOf("a", "b", "a"), atomic.value)
|
||||
assertEquals(listOf("a", "b", "a"), atomic.getAndSet(listOf("a", "a", "a")))
|
||||
assertEquals(listOf("a", "a", "a"), atomic.value)
|
||||
var cur = atomic.value
|
||||
assertTrue(atomic.compareAndSet(cur, listOf("b", "b", "b")))
|
||||
assertFalse(atomic.compareAndSet(listOf("a", "a", "a"), listOf("b", "b", "b")))
|
||||
assertEquals(listOf("b", "b", "b"), atomic.value)
|
||||
cur = atomic.value
|
||||
assertEquals(listOf("b", "b", "b"), atomic.compareAndExchange(cur, listOf("c", "c", "c")))
|
||||
assertEquals(listOf("c", "c", "c"), atomic.compareAndExchange(cur, listOf("d", "d", "d")))
|
||||
assertEquals(listOf("c", "c", "c"), atomic.compareAndExchange(cur, listOf("c", "c", "c")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNativePtr() {
|
||||
val atomic = AtomicNativePtr(NativePtr.NULL)
|
||||
assertEquals(NativePtr.NULL, atomic.value)
|
||||
atomic.value = NativePtr.NULL.plus(10L)
|
||||
assertEquals(10L, atomic.value.toLong())
|
||||
assertTrue(atomic.compareAndSet(NativePtr.NULL.plus(10L), NativePtr.NULL.plus(20L)))
|
||||
assertEquals(20L, atomic.value.toLong())
|
||||
assertFalse(atomic.compareAndSet(NativePtr.NULL.plus(10L), NativePtr.NULL.plus(20L)))
|
||||
assertEquals(20L, atomic.value.toLong())
|
||||
assertEquals(NativePtr.NULL.plus(20L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(30L)))
|
||||
assertEquals(NativePtr.NULL.plus(30L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(40L)))
|
||||
assertEquals(NativePtr.NULL.plus(30L), atomic.compareAndExchange(NativePtr.NULL.plus(20L), NativePtr.NULL.plus(50L)))
|
||||
assertEquals(30L, atomic.getAndSet(NativePtr.NULL.plus(55L)).toLong())
|
||||
assertEquals(55L, atomic.value.toLong())
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(FreezingIsDeprecated::class)
|
||||
package runtime.atomics.atomic_stress
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.concurrent.*
|
||||
import kotlin.concurrent.AtomicInt
|
||||
import kotlin.concurrent.AtomicLong
|
||||
import kotlin.concurrent.AtomicReference
|
||||
import kotlin.native.internal.NativePtr
|
||||
|
||||
fun testAtomicIntStress(workers: Array<Worker>) {
|
||||
val atomic = AtomicInt(10)
|
||||
val futures = Array(workers.size, { workerIndex ->
|
||||
workers[workerIndex].execute(TransferMode.SAFE, { atomic }) {
|
||||
atomic -> atomic.addAndGet(1000)
|
||||
}
|
||||
})
|
||||
futures.forEach {
|
||||
it.result
|
||||
}
|
||||
assertEquals(10 + 1000 * workers.size, atomic.value)
|
||||
}
|
||||
|
||||
fun testAtomicLongStress(workers: Array<Worker>) {
|
||||
val atomic = AtomicLong(10L)
|
||||
val futures = Array(workers.size, { workerIndex ->
|
||||
workers[workerIndex].execute(TransferMode.SAFE, { atomic }) {
|
||||
atomic -> atomic.addAndGet(9999999999)
|
||||
}
|
||||
})
|
||||
futures.forEach {
|
||||
it.result
|
||||
}
|
||||
assertEquals(10L + 9999999999 * workers.size, atomic.value)
|
||||
}
|
||||
|
||||
private class LockFreeStack<T> {
|
||||
private val top = AtomicReference<Node<T>?>(null)
|
||||
|
||||
private class Node<T>(val value: T, val next: Node<T>?)
|
||||
|
||||
fun isEmpty(): Boolean = top.value == null
|
||||
|
||||
fun push(value: T) {
|
||||
while(true) {
|
||||
val cur = top.value
|
||||
val upd = Node(value, cur)
|
||||
if (top.compareAndSet(cur, upd)) return
|
||||
}
|
||||
}
|
||||
|
||||
fun pop(): T? {
|
||||
while(true) {
|
||||
val cur = top.value
|
||||
if (cur == null) return null
|
||||
if (top.compareAndSet(cur, cur.next)) return cur.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testAtomicReferenceStress(workers: Array<Worker>) {
|
||||
val stack = LockFreeStack<Int>()
|
||||
val writers = Array(workers.size, { workerIndex ->
|
||||
workers[workerIndex].execute(TransferMode.SAFE, { stack to workerIndex}) {
|
||||
(stack, workerIndex) -> stack.push(workerIndex)
|
||||
}
|
||||
})
|
||||
writers.forEach { it.result }
|
||||
|
||||
val seen = mutableSetOf<Int>()
|
||||
while(!stack.isEmpty()) {
|
||||
val value = stack.pop()
|
||||
assertNotNull(value)
|
||||
seen.add(value)
|
||||
}
|
||||
assertEquals(workers.size, seen.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runStressTest() {
|
||||
val COUNT = 20
|
||||
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||
testAtomicIntStress(workers)
|
||||
testAtomicLongStress(workers)
|
||||
testAtomicReferenceStress(workers)
|
||||
}
|
||||
@@ -9,6 +9,8 @@ package runtime.basic.initializers6
|
||||
import kotlin.test.*
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.concurrent.*
|
||||
import kotlin.concurrent.AtomicInt
|
||||
|
||||
val aWorkerId = AtomicInt(0)
|
||||
val bWorkersCount = 3
|
||||
@@ -21,7 +23,7 @@ object A {
|
||||
// Must be called by aWorker only.
|
||||
assertEquals(aWorkerId.value, Worker.current.id)
|
||||
// Only allow b workers to run, when a worker has started initialization.
|
||||
bWorkerUnlocker.increment()
|
||||
bWorkerUnlocker.incrementAndGet()
|
||||
// Only proceed with initialization, when all b workers have started executing.
|
||||
while (aWorkerUnlocker.value < bWorkersCount) {}
|
||||
// And now wait a bit, to increase probability of races.
|
||||
@@ -57,7 +59,7 @@ fun produceB(): String {
|
||||
// Wait until A has started to initialize.
|
||||
while (bWorkerUnlocker.value < 1) {}
|
||||
// Now allow A initialization to continue.
|
||||
aWorkerUnlocker.increment()
|
||||
aWorkerUnlocker.incrementAndGet()
|
||||
// And this should not've tried to init A itself.
|
||||
A.a + A.b
|
||||
})
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.
|
||||
*/
|
||||
|
||||
package kotlin.concurrent
|
||||
|
||||
import kotlinx.cinterop.NativePtr
|
||||
import kotlin.native.internal.*
|
||||
import kotlin.reflect.*
|
||||
import kotlin.concurrent.*
|
||||
import kotlin.native.concurrent.*
|
||||
|
||||
/**
|
||||
* An [Int] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
* So shared frozen objects can have mutable fields of [AtomicInt] type.
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
@SinceKotlin("1.9")
|
||||
public class AtomicInt(@Volatile public var value: Int) {
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: Int): Int = this::value.getAndSetField(newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSet(expected: Int, newValue: Int): Boolean = this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndExchange(expected: Int, newValue: Int): Int = this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the old value.
|
||||
*/
|
||||
public fun getAndAdd(delta: Int): Int = this::value.getAndAddField(delta)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
public fun addAndGet(delta: Int): Int = this::value.getAndAddField(delta) + delta
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndIncrement(): Int = this::value.getAndAddField(1)
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one and returns the new value.
|
||||
*/
|
||||
public fun incrementAndGet(): Int = this::value.getAndAddField(1) + 1
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the new value.
|
||||
*/
|
||||
public fun decrementAndGet(): Int = this::value.getAndAddField(-1) - 1
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndDecrement(): Int = this::value.getAndAddField(-1)
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one.
|
||||
*/
|
||||
@Deprecated(level = DeprecationLevel.ERROR, message = "Use incrementAndGet() or getAndIncrement() instead.",
|
||||
replaceWith = ReplaceWith("this.incrementAndGet()"))
|
||||
public fun increment(): Unit {
|
||||
addAndGet(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one.
|
||||
*/
|
||||
@Deprecated(level = DeprecationLevel.ERROR, message = "Use decrementAndGet() or getAndDecrement() instead.",
|
||||
replaceWith = ReplaceWith("this.decrementAndGet()"))
|
||||
public fun decrement(): Unit {
|
||||
addAndGet(-1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the current [value].
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Long] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
* So shared frozen objects can have mutable fields of [AtomicLong] type.
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
@SinceKotlin("1.9")
|
||||
public class AtomicLong(@Volatile public var value: Long) {
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: Long): Long = this::value.getAndSetField(newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSet(expected: Long, newValue: Long): Boolean = this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndExchange(expected: Long, newValue: Long): Long = this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the old value.
|
||||
*/
|
||||
public fun getAndAdd(delta: Long): Long = this::value.getAndAddField(delta)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
public fun addAndGet(delta: Long): Long = this::value.getAndAddField(delta) + delta
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndIncrement(): Long = this::value.getAndAddField(1L)
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one and returns the new value.
|
||||
*/
|
||||
public fun incrementAndGet(): Long = this::value.getAndAddField(1L) + 1L
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the new value.
|
||||
*/
|
||||
public fun decrementAndGet(): Long = this::value.getAndAddField(-1L) - 1L
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndDecrement(): Long = this::value.getAndAddField(-1L)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
@Deprecated(level = DeprecationLevel.ERROR, message = "Use addAndGet(delta: Long) instead.")
|
||||
public fun addAndGet(delta: Int): Long = addAndGet(delta.toLong())
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one.
|
||||
*/
|
||||
@Deprecated(level = DeprecationLevel.ERROR, message = "Use incrementAndGet() or getAndIncrement() instead.",
|
||||
replaceWith = ReplaceWith("this.incrementAndGet()"))
|
||||
public fun increment(): Unit {
|
||||
addAndGet(1L)
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one.
|
||||
*/
|
||||
@Deprecated(level = DeprecationLevel.ERROR, message = "Use decrementAndGet() or getAndDecrement() instead.",
|
||||
replaceWith = ReplaceWith("this.decrementAndGet()"))
|
||||
public fun decrement(): Unit {
|
||||
addAndGet(-1L)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the current [value].
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* An object reference that is always updated atomically.
|
||||
*
|
||||
* Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
||||
* but frequently shall be of nullable type and be zeroed out once no longer needed.
|
||||
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||
* in debug mode could be helpful.
|
||||
*/
|
||||
@FrozenLegacyMM
|
||||
@LeakDetectorCandidate
|
||||
@NoReorderFields
|
||||
@OptIn(FreezingIsDeprecated::class)
|
||||
@SinceKotlin("1.9")
|
||||
public class AtomicReference<T> {
|
||||
private var value_: T
|
||||
|
||||
// A spinlock to fix potential ARC race.
|
||||
private var lock: Int = 0
|
||||
|
||||
// Optimization for speeding up access.
|
||||
private var cookie: Int = 0
|
||||
|
||||
/**
|
||||
* Creates a new atomic reference pointing to the [given value][value].
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if reference is not frozen.
|
||||
*/
|
||||
constructor(value: T) {
|
||||
if (this.isFrozen) {
|
||||
checkIfFrozen(value)
|
||||
}
|
||||
value_ = value
|
||||
}
|
||||
|
||||
/**
|
||||
* The current value.
|
||||
* Gets the current value or sets to the given [new value][newValue].
|
||||
*
|
||||
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
*/
|
||||
public var value: T
|
||||
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
|
||||
set(newValue) = setImpl(newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: T): T {
|
||||
while (true) {
|
||||
val old = value
|
||||
if (old === newValue) {
|
||||
return old
|
||||
}
|
||||
if (compareAndSet(old, newValue)) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by reference.
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
|
||||
external public fun compareAndSet(expected: T, newValue: T): Boolean
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by reference.
|
||||
*
|
||||
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
|
||||
external public fun compareAndExchange(expected: T, newValue: T): T
|
||||
|
||||
/**
|
||||
* Returns the string representation of the current [value].
|
||||
*/
|
||||
public override fun toString(): String =
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// Implementation details.
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_set")
|
||||
private external fun setImpl(newValue: Any?): Unit
|
||||
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_get")
|
||||
private external fun getImpl(): Any?
|
||||
}
|
||||
|
||||
/**
|
||||
* A [kotlinx.cinterop.NativePtr] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing
|
||||
* and [compareAndSet], [compareAndExchange] operations perform comparison by value.
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
* So shared frozen objects can have mutable fields of [AtomicNativePtr] type.
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
@SinceKotlin("1.9")
|
||||
public class AtomicNativePtr(@Volatile public var value: NativePtr) {
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: NativePtr): NativePtr {
|
||||
// Pointer types are allowed for atomicrmw xchg operand since LLVM 15.0,
|
||||
// after LLVM version update, it may be implemented via getAndSetField intrinsic.
|
||||
// Check: https://youtrack.jetbrains.com/issue/KT-57557
|
||||
while (true) {
|
||||
val old = value
|
||||
if (this::value.compareAndSetField(old, newValue)) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by value.
|
||||
*/
|
||||
public fun compareAndSet(expected: NativePtr, newValue: NativePtr): Boolean =
|
||||
this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by value.
|
||||
*/
|
||||
public fun compareAndExchange(expected: NativePtr, newValue: NativePtr): NativePtr =
|
||||
this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Returns the string representation of the current [value].
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
|
||||
private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}"
|
||||
|
||||
private fun debugString(value: Any?): String {
|
||||
if (value == null) return "null"
|
||||
return "${value::class.qualifiedName}: ${idString(value)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the value of the field referenced by [this] to [expectedValue], and if they are equal,
|
||||
* atomically replaces it with [newValue].
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* Comparison is done by reference or value depending on field representation.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Returns true if the actual field value matched [expectedValue]
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.COMPARE_AND_SET_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.compareAndSetField(expectedValue: T, newValue: T): Boolean
|
||||
|
||||
/**
|
||||
* Compares the value of the field referenced by [this] to [expectedValue], and if they are equal,
|
||||
* atomically replaces it with [newValue].
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* Comparison is done by reference or value depending on field representation.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Returns the field value before operation.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.COMPARE_AND_EXCHANGE_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.compareAndExchangeField(expectedValue: T, newValue: T): T
|
||||
|
||||
/**
|
||||
* Atomically sets value of the field referenced by [this] to [newValue] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_SET_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.getAndSetField(newValue: T): T
|
||||
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Short>.getAndAddField(delta: Short): Short
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Int>.getAndAddField(newValue: Int): Int
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Long>.getAndAddField(newValue: Long): Long
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Byte>.getAndAddField(newValue: Byte): Byte
|
||||
@@ -25,6 +25,7 @@ internal actual constructor(
|
||||
public actual override val context: CoroutineContext
|
||||
get() = delegate.context
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private var resultRef = FreezableAtomicReference<Any?>(initialResult)
|
||||
|
||||
public actual override fun resumeWith(result: Result<T>) {
|
||||
@@ -54,4 +55,4 @@ internal actual constructor(
|
||||
else -> result // either COROUTINE_SUSPENDED or data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import kotlin.reflect.*
|
||||
import kotlin.concurrent.*
|
||||
|
||||
/**
|
||||
* Wrapper around [Int] with atomic synchronized operations.
|
||||
* An [Int] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
@@ -19,57 +20,85 @@ import kotlin.concurrent.*
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
@Deprecated("Use kotlin.concurrent.AtomicInt instead.", ReplaceWith("kotlin.concurrent.AtomicInt"))
|
||||
@DeprecatedSinceKotlin(warningSince = "1.9")
|
||||
public class AtomicInt(public @Volatile var value: Int) {
|
||||
/**
|
||||
* Increments the value by [delta] and returns the new value.
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: Int): Int = this::value.getAndSetField(newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* @param delta the value to add
|
||||
* @return the new value
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSet(expected: Int, newValue: Int): Boolean = this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSwap(expected: Int, newValue: Int): Int = this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the old value.
|
||||
*/
|
||||
public fun getAndAdd(delta: Int): Int = this::value.getAndAddField(delta)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
public fun addAndGet(delta: Int): Int = this::value.getAndAddField(delta) + delta
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return the old value
|
||||
* Atomically increments the current value by one and returns the old value.
|
||||
*/
|
||||
public fun compareAndSwap(expected: Int, new: Int): Int = this::value.compareAndSwapField(expected, new)
|
||||
public fun getAndIncrement(): Int = this::value.getAndAddField(1)
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return true if successful
|
||||
* Atomically increments the current value by one and returns the new value.
|
||||
*/
|
||||
public fun compareAndSet(expected: Int, new: Int): Boolean = this::value.compareAndSetField(expected, new)
|
||||
public fun incrementAndGet(): Int = this::value.getAndAddField(1) + 1
|
||||
|
||||
/**
|
||||
* Increments value by one.
|
||||
* Atomically decrements the current value by one and returns the new value.
|
||||
*/
|
||||
public fun decrementAndGet(): Int = this::value.getAndAddField(-1) - 1
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndDecrement(): Int = this::value.getAndAddField(-1)
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one.
|
||||
*/
|
||||
@Deprecated("Use incrementAndGet() or getAndIncrement() instead.", ReplaceWith("this.incrementAndGet()"))
|
||||
public fun increment(): Unit {
|
||||
addAndGet(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements value by one.
|
||||
* Atomically decrements the current value by one.
|
||||
*/
|
||||
@Deprecated("Use decrementAndGet() or getAndDecrement() instead.", ReplaceWith("this.decrementAndGet()"))
|
||||
public fun decrement(): Unit {
|
||||
addAndGet(-1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*
|
||||
* @return the string representation
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around [Long] with atomic synchronized operations.
|
||||
* A [Long] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
@@ -77,113 +106,90 @@ public class AtomicInt(public @Volatile var value: Int) {
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
public class AtomicLong(public @Volatile var value: Long = 0) {
|
||||
@Deprecated("Use kotlin.concurrent.AtomicLong instead.", ReplaceWith("kotlin.concurrent.AtomicLong"))
|
||||
@DeprecatedSinceKotlin(warningSince = "1.9")
|
||||
public class AtomicLong(public @Volatile var value: Long = 0L) {
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: Long): Long = this::value.getAndSetField(newValue)
|
||||
|
||||
/**
|
||||
* Increments the value by [delta] and returns the new value.
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* @param delta the value to add
|
||||
* @return the new value
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSet(expected: Long, newValue: Long): Boolean = this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*/
|
||||
public fun compareAndSwap(expected: Long, newValue: Long): Long = this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the old value.
|
||||
*/
|
||||
public fun getAndAdd(delta: Long): Long = this::value.getAndAddField(delta)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
public fun addAndGet(delta: Long): Long = this::value.getAndAddField(delta) + delta
|
||||
|
||||
/**
|
||||
* Increments the value by [delta] and returns the new value.
|
||||
*
|
||||
* @param delta the value to add
|
||||
* @return the new value
|
||||
* Atomically increments the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndIncrement(): Long = this::value.getAndAddField(1L)
|
||||
|
||||
/**
|
||||
* Atomically increments the current value by one and returns the new value.
|
||||
*/
|
||||
public fun incrementAndGet(): Long = this::value.getAndAddField(1L) + 1L
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the new value.
|
||||
*/
|
||||
public fun decrementAndGet(): Long = this::value.getAndAddField(-1L) - 1L
|
||||
|
||||
/**
|
||||
* Atomically decrements the current value by one and returns the old value.
|
||||
*/
|
||||
public fun getAndDecrement(): Long = this::value.getAndAddField(-1L)
|
||||
|
||||
/**
|
||||
* Atomically adds the [given value][delta] to the current value and returns the new value.
|
||||
*/
|
||||
@Deprecated("Use addAndGet(delta: Long) instead.")
|
||||
public fun addAndGet(delta: Int): Long = addAndGet(delta.toLong())
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return the old value
|
||||
*/
|
||||
public fun compareAndSwap(expected: Long, new: Long): Long = this::value.compareAndSwapField(expected, new)
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return true if successful, false if state is unchanged
|
||||
*/
|
||||
public fun compareAndSet(expected: Long, new: Long): Boolean = this::value.compareAndSetField(expected, new)
|
||||
|
||||
/**
|
||||
* Increments value by one.
|
||||
* Atomically increments the current value by one.
|
||||
*/
|
||||
@Deprecated("Use incrementAndGet() or getAndIncrement() instead.", ReplaceWith("this.incrementAndGet()"))
|
||||
public fun increment(): Unit {
|
||||
addAndGet(1L)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements value by one.
|
||||
* Atomically decrements the current value by one.
|
||||
*/
|
||||
@Deprecated("Use decrementAndGet() or getAndDecrement() instead.", ReplaceWith("this.decrementAndGet()"))
|
||||
fun decrement(): Unit {
|
||||
addAndGet(-1L)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*
|
||||
* @return the string representation of this object
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around [kotlinx.cinterop.NativePtr] with atomic synchronized operations.
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
* So shared frozen objects can have mutable fields of [AtomicNativePtr] type.
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
public class AtomicNativePtr(public @Volatile var value: NativePtr) {
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return the old value
|
||||
*/
|
||||
public fun compareAndSwap(expected: NativePtr, new: NativePtr): NativePtr =
|
||||
this::value.compareAndSwapField(expected, new)
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return true if successful
|
||||
*/
|
||||
public fun compareAndSet(expected: NativePtr, new: NativePtr): Boolean =
|
||||
this::value.compareAndSetField(expected, new)
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*
|
||||
* @return string representation of this object
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
|
||||
private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}"
|
||||
|
||||
private fun debugString(value: Any?): String {
|
||||
if (value == null) return "null"
|
||||
return "${value::class.qualifiedName}: ${idString(value)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around Kotlin object with atomic operations.
|
||||
* An object reference that is always updated atomically.
|
||||
*
|
||||
* Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
||||
* but frequently shall be of nullable type and be zeroed out once no longer needed.
|
||||
@@ -194,6 +200,8 @@ private fun debugString(value: Any?): String {
|
||||
@LeakDetectorCandidate
|
||||
@NoReorderFields
|
||||
@OptIn(FreezingIsDeprecated::class)
|
||||
@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference"))
|
||||
@DeprecatedSinceKotlin(warningSince = "1.9")
|
||||
public class AtomicReference<T> {
|
||||
private var value_: T
|
||||
|
||||
@@ -204,7 +212,8 @@ public class AtomicReference<T> {
|
||||
private var cookie: Int = 0
|
||||
|
||||
/**
|
||||
* Creates a new atomic reference pointing to given [ref].
|
||||
* Creates a new atomic reference pointing to the [given value][value].
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if reference is not frozen.
|
||||
*/
|
||||
constructor(value: T) {
|
||||
@@ -215,70 +224,139 @@ public class AtomicReference<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* The referenced value.
|
||||
* Gets the value or sets the [new] value.
|
||||
* Legacy MM: if [new] value is not null, it must be frozen or permanent object.
|
||||
* The current value.
|
||||
* Gets the current value or sets to the given [new value][newValue].
|
||||
*
|
||||
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
*/
|
||||
public var value: T
|
||||
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
|
||||
set(new) = setImpl(new)
|
||||
set(newValue) = setImpl(newValue)
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
* Note that comparison is identity-based, not value-based.
|
||||
*
|
||||
* Legacy MM: if [new] value is not null, it must be frozen or permanent object.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
* @return the old value
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
|
||||
external public fun compareAndSwap(expected: T, new: T): T
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
* Note that comparison is identity-based, not value-based.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @return true if successful
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
|
||||
external public fun compareAndSet(expected: T, new: T): Boolean
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*
|
||||
* @return string representation of this object
|
||||
*/
|
||||
public override fun toString(): String =
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// TODO: Consider making this public.
|
||||
internal fun swap(new: T): T {
|
||||
public fun getAndSet(newValue: T): T {
|
||||
while (true) {
|
||||
val old = value
|
||||
if (old === new) {
|
||||
if (old === newValue) {
|
||||
return old
|
||||
}
|
||||
if (compareAndSet(old, new)) {
|
||||
if (compareAndSet(old, newValue)) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by reference.
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
|
||||
external public fun compareAndSet(expected: T, newValue: T): Boolean
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by reference.
|
||||
*
|
||||
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
|
||||
*
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
*/
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
|
||||
external public fun compareAndSwap(expected: T, newValue: T): T
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*/
|
||||
public override fun toString(): String =
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// Implementation details.
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_set")
|
||||
private external fun setImpl(new: Any?): Unit
|
||||
private external fun setImpl(newValue: Any?): Unit
|
||||
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_get")
|
||||
private external fun getImpl(): Any?
|
||||
}
|
||||
|
||||
/**
|
||||
* A [kotlinx.cinterop.NativePtr] value that is always updated atomically.
|
||||
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
|
||||
*
|
||||
* [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing
|
||||
* and [compareAndSet], [compareAndSwap] operations perform comparison by value.
|
||||
*
|
||||
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
|
||||
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
|
||||
* So shared frozen objects can have mutable fields of [AtomicNativePtr] type.
|
||||
*/
|
||||
@Frozen
|
||||
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
|
||||
@Deprecated("Use kotlin.concurrent.AtomicNativePtr instead.", ReplaceWith("kotlin.concurrent.AtomicNativePtr"))
|
||||
@DeprecatedSinceKotlin(warningSince = "1.9")
|
||||
public class AtomicNativePtr(public @Volatile var value: NativePtr) {
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] and returns the old value.
|
||||
*/
|
||||
public fun getAndSet(newValue: NativePtr): NativePtr {
|
||||
// Pointer types are allowed for atomicrmw xchg operand since LLVM 15.0,
|
||||
// after LLVM version update, it may be implemented via getAndSetField intrinsic.
|
||||
// Check: https://youtrack.jetbrains.com/issue/KT-57557
|
||||
while (true) {
|
||||
val old = value
|
||||
if (this::value.compareAndSetField(old, newValue)) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
|
||||
* returns true if the operation was successful and false only if the current value was not equal to the expected value.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by value.
|
||||
*/
|
||||
public fun compareAndSet(expected: NativePtr, newValue: NativePtr): Boolean =
|
||||
this::value.compareAndSetField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
|
||||
*
|
||||
* Comparison of values is done by value.
|
||||
*/
|
||||
public fun compareAndSwap(expected: NativePtr, newValue: NativePtr): NativePtr =
|
||||
this::value.compareAndExchangeField(expected, newValue)
|
||||
|
||||
/**
|
||||
* Returns the string representation of this object.
|
||||
*/
|
||||
public override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
|
||||
private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}"
|
||||
|
||||
private fun debugString(value: Any?): String {
|
||||
if (value == null) return "null"
|
||||
return "${value::class.qualifiedName}: ${idString(value)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this class is useful only with legacy memory manager. Please use [AtomicReference] instead.
|
||||
*
|
||||
@@ -291,6 +369,8 @@ public class AtomicReference<T> {
|
||||
@LeakDetectorCandidate
|
||||
@ExportTypeInfo("theFreezableAtomicReferenceTypeInfo")
|
||||
@FreezingIsDeprecated
|
||||
@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference"))
|
||||
@DeprecatedSinceKotlin(warningSince = "1.9")
|
||||
public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
// A spinlock to fix potential ARC race.
|
||||
private var lock: Int = 0
|
||||
@@ -300,53 +380,57 @@ public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
|
||||
/**
|
||||
* The referenced value.
|
||||
* Gets the value or sets the [new] value. If [new] value is not null,
|
||||
* Gets the value or sets to the given [new value][newValue]. If the [new value][newValue] is not null,
|
||||
* and `this` is frozen - it must be frozen or permanent object.
|
||||
*
|
||||
* @throws InvalidMutabilityException if the value is not frozen or a permanent object
|
||||
*/
|
||||
public var value: T
|
||||
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
|
||||
set(new) {
|
||||
set(newValue) {
|
||||
if (this.isShareable())
|
||||
setImpl(new)
|
||||
setImpl(newValue)
|
||||
else
|
||||
value_ = new
|
||||
value_ = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
* Legacy MM: If [new] value is not null and object is frozen, it must be frozen or permanent object.
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns the old value in any case.
|
||||
*
|
||||
* Legacy MM: If the [new value][newValue] value is not null and object is frozen, it must be frozen or permanent object.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @param newValue the new value
|
||||
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
|
||||
* @return the old value
|
||||
*/
|
||||
public fun compareAndSwap(expected: T, new: T): T {
|
||||
public fun compareAndSwap(expected: T, newValue: T): T {
|
||||
return if (this.isShareable()) {
|
||||
@Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, new) as T)
|
||||
@Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, newValue) as T)
|
||||
} else {
|
||||
val old = value_
|
||||
if (old === expected) value_ = new
|
||||
if (old === expected) value_ = newValue
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
|
||||
* and returns true if operation was successful.
|
||||
*
|
||||
* Note that comparison is identity-based, not value-based.
|
||||
*
|
||||
* @param expected the expected value
|
||||
* @param new the new value
|
||||
* @param newValue the new value
|
||||
* @return true if successful
|
||||
*/
|
||||
public fun compareAndSet(expected: T, new: T): Boolean {
|
||||
public fun compareAndSet(expected: T, newValue: T): Boolean {
|
||||
if (this.isShareable())
|
||||
return compareAndSetImpl(expected, new)
|
||||
return compareAndSetImpl(expected, newValue)
|
||||
val old = value_
|
||||
if (old === expected) {
|
||||
value_ = new
|
||||
value_ = newValue
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -362,13 +446,13 @@ public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// TODO: Consider making this public.
|
||||
internal fun swap(new: T): T {
|
||||
internal fun swap(newValue: T): T {
|
||||
while (true) {
|
||||
val old = value
|
||||
if (old === new) {
|
||||
if (old === newValue) {
|
||||
return old
|
||||
}
|
||||
if (compareAndSet(old, new)) {
|
||||
if (compareAndSet(old, newValue)) {
|
||||
return old
|
||||
}
|
||||
}
|
||||
@@ -376,145 +460,14 @@ public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
|
||||
// Implementation details.
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_set")
|
||||
private external fun setImpl(new: Any?): Unit
|
||||
private external fun setImpl(newValue: Any?): Unit
|
||||
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_get")
|
||||
private external fun getImpl(): Any?
|
||||
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
|
||||
private external fun compareAndSwapImpl(expected: Any?, new: Any?): Any?
|
||||
private external fun compareAndSwapImpl(expected: Any?, newValue: Any?): Any?
|
||||
|
||||
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
|
||||
private external fun compareAndSetImpl(expected: Any?, new: Any?): Boolean
|
||||
private external fun compareAndSetImpl(expected: Any?, newValue: Any?): Boolean
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the value of the field referenced by [this] to [expectedValue], and if they are equal,
|
||||
* atomically replaces it with [newValue].
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* Comparison is done by reference or value depending on field representation.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Returns true if the actual field value matched [expectedValue]
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.COMPARE_AND_SET_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.compareAndSetField(expectedValue: T, newValue: T): Boolean
|
||||
|
||||
/**
|
||||
* Compares the value of the field referenced by [this] to [expectedValue], and if they are equal,
|
||||
* atomically replaces it with [newValue].
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* Comparison is done by reference or value depending on field representation.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Returns true if the actual field value before operation.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.COMPARE_AND_SWAP_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.compareAndSwapField(expectedValue: T, newValue: T): T
|
||||
|
||||
/**
|
||||
* Atomically sets value of the field referenced by [this] to [newValue] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_SET_FIELD)
|
||||
internal external fun <T> KMutableProperty0<T>.getAndSetField(newValue: T): T
|
||||
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Short>.getAndAddField(delta: Short): Short
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Int>.getAndAddField(newValue: Int): Int
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Long>.getAndAddField(newValue: Long): Long
|
||||
|
||||
/**
|
||||
* Atomically increments value of the field referenced by [this] by [delta] and returns old field value.
|
||||
*
|
||||
* For now, it can be used only within the same file, where property is defined.
|
||||
* Check https://youtrack.jetbrains.com/issue/KT-55426 for details.
|
||||
*
|
||||
* If [this] is not a compile-time known reference to the property with [Volatile] annotation [IllegalArgumentException]
|
||||
* would be thrown.
|
||||
*
|
||||
* If property referenced by [this] has nontrivial setter it will not be called.
|
||||
*
|
||||
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
|
||||
*/
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
|
||||
internal external fun KMutableProperty0<Byte>.getAndAddField(newValue: Byte): Byte
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
package kotlin.native.concurrent
|
||||
|
||||
import kotlin.experimental.ExperimentalNativeApi
|
||||
import kotlin.native.internal.Frozen
|
||||
import kotlin.concurrent.AtomicReference
|
||||
|
||||
@FreezingIsDeprecated
|
||||
internal class FreezeAwareLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
||||
@@ -94,7 +96,7 @@ internal class AtomicLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
||||
|
||||
override val value: T
|
||||
get() {
|
||||
if (value_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) {
|
||||
if (value_.compareAndExchange(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) {
|
||||
// We execute exclusively here.
|
||||
val ctor = initializer_.value
|
||||
if (ctor != null && initializer_.compareAndSet(ctor, null)) {
|
||||
@@ -204,4 +206,4 @@ internal class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
||||
override fun isInitialized(): Boolean = valueRef.value !== UNINITIALIZED
|
||||
|
||||
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package kotlin.native.concurrent
|
||||
|
||||
import kotlin.experimental.ExperimentalNativeApi
|
||||
import kotlin.native.internal.Frozen
|
||||
import kotlin.concurrent.AtomicInt
|
||||
|
||||
@ThreadLocal
|
||||
@OptIn(FreezingIsDeprecated::class)
|
||||
@@ -24,11 +25,11 @@ internal class Lock {
|
||||
fun lock() {
|
||||
val lockData = CurrentThread.id.hashCode()
|
||||
loop@ do {
|
||||
val old = locker_.compareAndSwap(0, lockData)
|
||||
val old = locker_.compareAndExchange(0, lockData)
|
||||
when (old) {
|
||||
lockData -> {
|
||||
// Was locked by us already.
|
||||
reenterCount_.increment()
|
||||
reenterCount_.incrementAndGet()
|
||||
break@loop
|
||||
}
|
||||
0 -> {
|
||||
@@ -42,10 +43,10 @@ internal class Lock {
|
||||
|
||||
fun unlock() {
|
||||
if (reenterCount_.value > 0) {
|
||||
reenterCount_.decrement()
|
||||
reenterCount_.decrementAndGet()
|
||||
} else {
|
||||
val lockData = CurrentThread.id.hashCode()
|
||||
val old = locker_.compareAndSwap(lockData, 0)
|
||||
val old = locker_.compareAndExchange(lockData, 0)
|
||||
assert(old == lockData)
|
||||
}
|
||||
}
|
||||
@@ -58,4 +59,4 @@ internal inline fun <R> locked(lock: Lock, block: () -> R): R {
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package kotlin.native.concurrent
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.native.internal.Frozen
|
||||
import kotlin.concurrent.AtomicNativePtr
|
||||
|
||||
/**
|
||||
* Note: modern Kotlin/Native memory manager allows to share objects between threads without additional ceremonies,
|
||||
|
||||
@@ -83,11 +83,11 @@ internal class IntrinsicType {
|
||||
|
||||
// Atomic
|
||||
const val COMPARE_AND_SET_FIELD = "COMPARE_AND_SET_FIELD"
|
||||
const val COMPARE_AND_SWAP_FIELD = "COMPARE_AND_SWAP_FIELD"
|
||||
const val COMPARE_AND_EXCHANGE_FIELD = "COMPARE_AND_EXCHANGE_FIELD"
|
||||
const val GET_AND_SET_FIELD = "GET_AND_SET_FIELD"
|
||||
const val GET_AND_ADD_FIELD = "GET_AND_ADD_FIELD"
|
||||
const val COMPARE_AND_SET = "COMPARE_AND_SET"
|
||||
const val COMPARE_AND_SWAP = "COMPARE_AND_SWAP"
|
||||
const val COMPARE_AND_EXCHANGE = "COMPARE_AND_EXCHANGE"
|
||||
const val GET_AND_SET = "GET_AND_SET"
|
||||
const val GET_AND_ADD = "GET_AND_ADD"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION", "DEPRECATION_ERROR") // Char.toInt()
|
||||
package kotlin.native.internal
|
||||
|
||||
import kotlin.experimental.ExperimentalNativeApi
|
||||
import kotlin.internal.getProgressionLastElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.native.concurrent.FreezableAtomicReference
|
||||
import kotlin.native.concurrent.freeze
|
||||
import kotlin.native.concurrent.FreezableAtomicReference
|
||||
|
||||
@ExportForCppRuntime
|
||||
@PublishedApi
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
package kotlin.random
|
||||
|
||||
import kotlin.native.concurrent.AtomicLong
|
||||
import kotlin.concurrent.AtomicLong
|
||||
import kotlin.system.getTimeNanos
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,7 @@ package kotlin.text.regex
|
||||
|
||||
import kotlin.experimental.ExperimentalNativeApi
|
||||
import kotlin.collections.associate
|
||||
import kotlin.native.concurrent.AtomicReference
|
||||
import kotlin.concurrent.AtomicReference
|
||||
import kotlin.native.concurrent.freeze
|
||||
import kotlin.native.BitSet
|
||||
import kotlin.native.FreezingIsDeprecated
|
||||
@@ -653,7 +653,7 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
||||
fun getPredefinedClass(name: String, negative: Boolean): AbstractCharClass {
|
||||
val charClass = classCacheMap[name] ?: throw PatternSyntaxException("No such character class")
|
||||
val cachedClass = classCache[charClass.ordinal].value ?: run {
|
||||
classCache[charClass.ordinal].compareAndSwap(null, charClass.factory().freeze())
|
||||
classCache[charClass.ordinal].compareAndExchange(null, charClass.factory().freeze())
|
||||
classCache[charClass.ordinal].value!!
|
||||
}
|
||||
return cachedClass.getValue(negative)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 kotlin.concurrent
|
||||
|
||||
// Only for compatibility with shared K/N stdlib code
|
||||
|
||||
internal class AtomicReference<T>(public var value: T) {
|
||||
public fun compareAndExchange(expected: T, new: T): T {
|
||||
if (value == expected) {
|
||||
val old = value
|
||||
value = new
|
||||
return old
|
||||
}
|
||||
return value
|
||||
}
|
||||
public fun compareAndSet(expected: T, new: T): Boolean {
|
||||
if (value == expected) {
|
||||
value = new
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,3 @@ internal val Any?.isFrozen
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun <T> T.freeze(): T = this
|
||||
|
||||
internal class AtomicReference<T>(public var value: T) {
|
||||
public fun compareAndSwap(expected: T, new: T): T {
|
||||
if (value == expected) {
|
||||
val old = value
|
||||
value = new
|
||||
return old
|
||||
}
|
||||
return value
|
||||
}
|
||||
public fun compareAndSet(expected: T, new: T): Boolean {
|
||||
if (value == expected) {
|
||||
value = new
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user