Improve WorkerBoundReference exceptions and add valueOrNull (#4115)
This commit is contained in:
committed by
GitHub
parent
1b6dac76d5
commit
5b75a009db
@@ -885,11 +885,6 @@ task worker_bound_reference0(type: KonanLocalTest) {
|
|||||||
source = "runtime/concurrent/worker_bound_reference0.kt"
|
source = "runtime/concurrent/worker_bound_reference0.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
task worker_bound_reference1(type: KonanLocalTest) {
|
|
||||||
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
|
||||||
source = "runtime/concurrent/worker_bound_reference1.kt"
|
|
||||||
}
|
|
||||||
|
|
||||||
task worker0(type: KonanLocalTest) {
|
task worker0(type: KonanLocalTest) {
|
||||||
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
goldValue = "Got Input processed\nOK\n"
|
goldValue = "Got Input processed\nOK\n"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
import kotlin.native.internal.GC
|
import kotlin.native.internal.GC
|
||||||
import kotlin.native.ref.WeakReference
|
import kotlin.native.ref.WeakReference
|
||||||
|
import kotlin.text.Regex
|
||||||
|
|
||||||
class A(var a: Int)
|
class A(var a: Int)
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ val global1: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
|||||||
@Test
|
@Test
|
||||||
fun testGlobal() {
|
fun testGlobal() {
|
||||||
assertEquals(3, global1.value.a)
|
assertEquals(3, global1.value.a)
|
||||||
|
assertEquals(3, global1.valueOrNull?.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
@@ -26,6 +28,7 @@ fun testGlobal() {
|
|||||||
|
|
||||||
val value = future.result
|
val value = future.result
|
||||||
assertEquals(3, value.value.a)
|
assertEquals(3, value.value.a)
|
||||||
|
assertEquals(3, value.valueOrNull?.a)
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +44,7 @@ fun testGlobalDenyAccessOnWorker() {
|
|||||||
assertFailsWith<IncorrectDereferenceException> {
|
assertFailsWith<IncorrectDereferenceException> {
|
||||||
local.value
|
local.value
|
||||||
}
|
}
|
||||||
|
assertEquals(null, local.valueOrNull)
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +57,7 @@ val global3: WorkerBoundReference<A> = WorkerBoundReference(A(3).freeze())
|
|||||||
@Test
|
@Test
|
||||||
fun testGlobalAccessOnWorkerFrozenInitially() {
|
fun testGlobalAccessOnWorkerFrozenInitially() {
|
||||||
assertEquals(3, global3.value.a)
|
assertEquals(3, global3.value.a)
|
||||||
|
assertEquals(3, global3.valueOrNull?.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
@@ -131,6 +136,7 @@ fun testGlobalModification() {
|
|||||||
|
|
||||||
val value = future.result
|
val value = future.result
|
||||||
assertEquals(4, value.value.a)
|
assertEquals(4, value.value.a)
|
||||||
|
assertEquals(4, value.valueOrNull?.a)
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +144,7 @@ fun testGlobalModification() {
|
|||||||
fun testLocal() {
|
fun testLocal() {
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3))
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
|
assertEquals(3, local.valueOrNull?.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
||||||
@@ -146,6 +153,7 @@ fun testLocal() {
|
|||||||
|
|
||||||
val value = future.result
|
val value = future.result
|
||||||
assertEquals(3, value.value.a)
|
assertEquals(3, value.value.a)
|
||||||
|
assertEquals(3, value.valueOrNull?.a)
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +167,7 @@ fun testLocalDenyAccessOnWorker() {
|
|||||||
assertFailsWith<IncorrectDereferenceException> {
|
assertFailsWith<IncorrectDereferenceException> {
|
||||||
local.value
|
local.value
|
||||||
}
|
}
|
||||||
|
assertEquals(null, local.valueOrNull)
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +179,7 @@ fun testLocalDenyAccessOnWorker() {
|
|||||||
fun testLocalAccessOnWorkerFrozenInitially() {
|
fun testLocalAccessOnWorkerFrozenInitially() {
|
||||||
val local = WorkerBoundReference(A(3).freeze())
|
val local = WorkerBoundReference(A(3).freeze())
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
|
assertEquals(3, local.valueOrNull?.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
||||||
@@ -235,6 +245,7 @@ fun testLocalDenyAccessOnMainThread() {
|
|||||||
assertFailsWith<IncorrectDereferenceException> {
|
assertFailsWith<IncorrectDereferenceException> {
|
||||||
value.value
|
value.value
|
||||||
}
|
}
|
||||||
|
assertEquals(null, value.valueOrNull)
|
||||||
|
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
@@ -261,6 +272,7 @@ fun testLocalModification() {
|
|||||||
|
|
||||||
val value = future.result
|
val value = future.result
|
||||||
assertEquals(4, value.value.a)
|
assertEquals(4, value.value.a)
|
||||||
|
assertEquals(4, value.valueOrNull?.a)
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +433,110 @@ fun doesNotCollectCrossThreadCyclicGarbage() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class C1 {
|
||||||
|
lateinit var c2: AtomicReference<WorkerBoundReference<C2>?>
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
c2.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class C2(val c1: AtomicReference<WorkerBoundReference<C1>>)
|
||||||
|
|
||||||
|
fun createCyclicGarbageWithAtomics(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
||||||
|
val ref1 = WorkerBoundReference(C1())
|
||||||
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
|
||||||
|
val ref2Weak = WeakReference(ref2.value)
|
||||||
|
|
||||||
|
ref1.value.c2 = AtomicReference(ref2)
|
||||||
|
|
||||||
|
return Triple(AtomicReference(ref1), ref1Weak, ref2Weak)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose(refOwner: AtomicReference<WorkerBoundReference<C1>?>) {
|
||||||
|
refOwner.value!!.value.dispose()
|
||||||
|
refOwner.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNotCollectCyclicGarbageWithAtomics() {
|
||||||
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
|
||||||
|
|
||||||
|
ref1Owner.value = null
|
||||||
|
GC.collect()
|
||||||
|
|
||||||
|
// If these asserts fail, that means AtomicReference<WorkerBoundReference> managed to clean up cyclic garbage all by itself.
|
||||||
|
assertNotNull(ref1Weak.value)
|
||||||
|
assertNotNull(ref2Weak.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun collectCyclicGarbageWithAtomics() {
|
||||||
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
|
||||||
|
|
||||||
|
dispose(ref1Owner)
|
||||||
|
GC.collect()
|
||||||
|
|
||||||
|
assertNull(ref1Weak.value)
|
||||||
|
assertNull(ref2Weak.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCrossThreadCyclicGarbageWithAtomics(
|
||||||
|
worker: Worker
|
||||||
|
): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
||||||
|
val ref1 = WorkerBoundReference(C1())
|
||||||
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
|
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
||||||
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
|
||||||
|
Pair(ref2, WeakReference(ref2.value))
|
||||||
|
}
|
||||||
|
val (ref2, ref2Weak) = future.result
|
||||||
|
|
||||||
|
ref1.value.c2 = AtomicReference(ref2)
|
||||||
|
|
||||||
|
return Triple(AtomicReference(ref1), ref1Weak, ref2Weak)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNotCollectCrossThreadCyclicGarbageWithAtomics() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
|
||||||
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
|
||||||
|
|
||||||
|
ref1Owner.value = null
|
||||||
|
GC.collect()
|
||||||
|
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
||||||
|
|
||||||
|
// If these asserts fail, that means AtomicReference<WorkerBoundReference> managed to clean up cyclic garbage all by itself.
|
||||||
|
assertNotNull(ref1Weak.value)
|
||||||
|
assertNotNull(ref2Weak.value)
|
||||||
|
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun collectCrossThreadCyclicGarbageWithAtomics() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
|
||||||
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
|
||||||
|
|
||||||
|
dispose(ref1Owner)
|
||||||
|
// This marks C2 as gone on the main thread
|
||||||
|
GC.collect()
|
||||||
|
// This cleans up all the references from the worker thread and destroys C2, but C1 is still alive.
|
||||||
|
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
||||||
|
// And this finally destroys C1
|
||||||
|
GC.collect()
|
||||||
|
|
||||||
|
assertNull(ref1Weak.value)
|
||||||
|
assertNull(ref2Weak.value)
|
||||||
|
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun concurrentAccess() {
|
fun concurrentAccess() {
|
||||||
val workerCount = 10
|
val workerCount = 10
|
||||||
@@ -453,3 +569,22 @@ fun concurrentAccess() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExceptionMessage() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
|
WorkerBoundReference(A(3))
|
||||||
|
}
|
||||||
|
val value = future.result
|
||||||
|
|
||||||
|
val ownerName = worker.name
|
||||||
|
val messagePattern = Regex("illegal attempt to access non-shared runtime\\.concurrent\\.worker_bound_reference0\\.A@[a-f0-9]+ bound to `$ownerName` from `${Worker.current.name}`")
|
||||||
|
|
||||||
|
val exception = assertFailsWith<IncorrectDereferenceException> {
|
||||||
|
value.value
|
||||||
|
}
|
||||||
|
assertTrue(messagePattern matches exception.message!!)
|
||||||
|
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,684 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package runtime.concurrent.worker_bound_reference1
|
|
||||||
|
|
||||||
import kotlin.test.*
|
|
||||||
|
|
||||||
import kotlin.native.concurrent.*
|
|
||||||
import kotlin.native.internal.GC
|
|
||||||
import kotlin.native.ref.WeakReference
|
|
||||||
|
|
||||||
class A(var a: Int)
|
|
||||||
|
|
||||||
val global1: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobal() {
|
|
||||||
assertEquals(3, global1.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
|
||||||
global1
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value.value.a)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global2: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalDenyAccessOnWorker() {
|
|
||||||
assertEquals(3, global2.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
|
||||||
val local = global2
|
|
||||||
assertFailsWith<IncorrectDereferenceException> {
|
|
||||||
local.value
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
future.result
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global3: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3).freeze())
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalAccessOnWorkerFrozenInitially() {
|
|
||||||
assertEquals(3, global3.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
|
||||||
global3.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global4: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalAccessOnWorkerFrozenBeforePassing() {
|
|
||||||
assertEquals(3, global4.value.a)
|
|
||||||
global4.value.freeze()
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
|
||||||
global4.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global5: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalAccessOnWorkerFrozenBeforeAccess() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
assertEquals(3, global5.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { semaphore }) { semaphore ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
global5.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
global5.value.freeze()
|
|
||||||
semaphore.increment()
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global6: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalModification() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
assertEquals(3, global6.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { semaphore }) { semaphore ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
global6
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
global6.value.a = 4
|
|
||||||
semaphore.increment()
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(4, value.value.a)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
val global7: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalDispose() {
|
|
||||||
assertEquals(3, global7.value.a)
|
|
||||||
|
|
||||||
global7.dispose()
|
|
||||||
global7.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
val global8: DisposableWorkerBoundReference<A> = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGlobalAccessAfterDispose() {
|
|
||||||
assertEquals(3, global8.value.a)
|
|
||||||
|
|
||||||
global8.dispose()
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
global8.value.a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocal() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
|
||||||
local
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value.value.a)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalDenyAccessOnWorker() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
|
||||||
assertFailsWith<IncorrectDereferenceException> {
|
|
||||||
local.value
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
future.result
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalAccessOnWorkerFrozenInitially() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3).freeze())
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
|
||||||
local.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalAccessOnWorkerFrozenBeforePassing() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
local.value.freeze()
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
|
||||||
local.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalAccessOnWorkerFrozenBeforeAccess() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(local, semaphore) }) { (local, semaphore) ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
local.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
local.value.freeze()
|
|
||||||
semaphore.increment()
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalDenyAccessOnMainThread() {
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
|
||||||
DisposableWorkerBoundReference(A(3))
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertFailsWith<IncorrectDereferenceException> {
|
|
||||||
value.value
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalModification() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(local, semaphore) }) { (local, semaphore) ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
local
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
local.value.a = 4
|
|
||||||
semaphore.increment()
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(4, value.value.a)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalDispose() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
local.dispose()
|
|
||||||
local.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLocalAccessAfterDispose() {
|
|
||||||
val local = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, local.value.a)
|
|
||||||
|
|
||||||
local.dispose()
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
local.value.a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOwnerAndWeaks(initial: Int): Triple<AtomicReference<DisposableWorkerBoundReference<A>?>, WeakReference<DisposableWorkerBoundReference<A>>, WeakReference<A>> {
|
|
||||||
val ref = DisposableWorkerBoundReference(A(initial))
|
|
||||||
val refOwner: AtomicReference<DisposableWorkerBoundReference<A>?> = AtomicReference(ref)
|
|
||||||
val refWeak = WeakReference(ref)
|
|
||||||
val refValueWeak = WeakReference(ref.value)
|
|
||||||
|
|
||||||
return Triple(refOwner, refWeak, refValueWeak)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> callDispose(ref: AtomicReference<DisposableWorkerBoundReference<T>?>) {
|
|
||||||
ref.value!!.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCollect() {
|
|
||||||
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaks(3)
|
|
||||||
|
|
||||||
refOwner.value = null
|
|
||||||
GC.collect()
|
|
||||||
|
|
||||||
// Last reference to DisposableWorkerBoundReference is gone, so it and it's referent are destroyed.
|
|
||||||
assertNull(refWeak.value)
|
|
||||||
assertNull(refValueWeak.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDisposeAndCollect() {
|
|
||||||
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaks(3)
|
|
||||||
|
|
||||||
callDispose(refOwner)
|
|
||||||
GC.collect()
|
|
||||||
|
|
||||||
// refOwner still contains a reference to DisposableWorkerBoundReference. But it's referent is
|
|
||||||
// destroyed because of explicit dispose call.
|
|
||||||
assertNotNull(refWeak.value)
|
|
||||||
assertNull(refValueWeak.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collectInWorker(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
|
|
||||||
val (refOwner, _, refValueWeak) = getOwnerAndWeaks(3)
|
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
refOwner.value = null
|
|
||||||
GC.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
// At this point worker is spinning on semaphore. refOwner still contains reference to
|
|
||||||
// DisposableWorkerBoundReference, so referent is kept alive.
|
|
||||||
GC.collect()
|
|
||||||
assertNotNull(refValueWeak.value)
|
|
||||||
|
|
||||||
return Pair(refValueWeak, future)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCollectInWorker() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
|
|
||||||
val (refValueWeak, future) = collectInWorker(worker, semaphore)
|
|
||||||
semaphore.increment()
|
|
||||||
future.result
|
|
||||||
|
|
||||||
// At this point DisposableWorkerBoundReference no longer has a reference, so it's referent is destroyed.
|
|
||||||
// DisposableWorkerBoundReference, so referent is kept alive.
|
|
||||||
GC.collect()
|
|
||||||
assertNull(refValueWeak.value)
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<DisposableWorkerBoundReference<A>> {
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
|
|
||||||
return worker.execute(TransferMode.SAFE, { Pair(ref, semaphore) }) { (ref, semaphore) ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
GC.collect()
|
|
||||||
ref
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDoNotCollectInWorker() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
|
|
||||||
val future = doNotCollectInWorker(worker, semaphore)
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
GC.collect()
|
|
||||||
semaphore.increment()
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(3, value.value.a)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun disposeInWorker(worker: Worker, semaphore: AtomicInt): Triple<WeakReference<DisposableWorkerBoundReference<A>>, WeakReference<A>, Future<AtomicReference<DisposableWorkerBoundReference<A>?>>> {
|
|
||||||
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaks(3)
|
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
|
|
||||||
semaphore.increment()
|
|
||||||
while (semaphore.value < 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
callDispose(refOwner)
|
|
||||||
GC.collect()
|
|
||||||
refOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
while (semaphore.value < 1) {
|
|
||||||
}
|
|
||||||
// At this point worker is spinning on semaphore. refOwner still contains reference to
|
|
||||||
// DisposableWorkerBoundReference, so referent is kept alive.
|
|
||||||
GC.collect()
|
|
||||||
assertNotNull(refValueWeak.value)
|
|
||||||
|
|
||||||
return Triple(refWeak, refValueWeak, future)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDisposeInWorker() {
|
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
|
|
||||||
val (refWeak, refValueWeak, future) = disposeInWorker(worker, semaphore)
|
|
||||||
semaphore.increment()
|
|
||||||
val refOwner = future.result
|
|
||||||
|
|
||||||
// At this point refOwner still has a reference, but it's explicitly disposed,
|
|
||||||
// so referent is destroyed.
|
|
||||||
GC.collect()
|
|
||||||
assertNotNull(refWeak.value)
|
|
||||||
assertNull(refValueWeak.value)
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDisposeOnMainThreadAndAccessInWorker() {
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, ref.value.a)
|
|
||||||
|
|
||||||
ref.dispose()
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { ref }) { ref ->
|
|
||||||
var result = 0
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
result = ref.value.a
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = future.result
|
|
||||||
assertEquals(0, value)
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDisposeInWorkerAndAccessOnMainThread() {
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, ref.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { ref }) { ref ->
|
|
||||||
ref.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
future.result
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
ref.value.a
|
|
||||||
}
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
class B1 {
|
|
||||||
lateinit var b2: DisposableWorkerBoundReference<B2>
|
|
||||||
}
|
|
||||||
|
|
||||||
data class B2(val b1: DisposableWorkerBoundReference<B1>)
|
|
||||||
|
|
||||||
fun createCyclicGarbage(): Triple<AtomicReference<DisposableWorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
|
||||||
val ref1 = DisposableWorkerBoundReference(B1())
|
|
||||||
val ref1Owner: AtomicReference<DisposableWorkerBoundReference<B1>?> = AtomicReference(ref1)
|
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
|
||||||
|
|
||||||
val ref2 = DisposableWorkerBoundReference(B2(ref1))
|
|
||||||
val ref2Weak = WeakReference(ref2.value)
|
|
||||||
|
|
||||||
ref1.value.b2 = ref2
|
|
||||||
|
|
||||||
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun doesNotCollectCyclicGarbage() {
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
|
|
||||||
|
|
||||||
ref1Owner.value = null
|
|
||||||
GC.collect()
|
|
||||||
|
|
||||||
// If these asserts fail, that means DisposableWorkerBoundReference managed to clean up cyclic garbage all by itself.
|
|
||||||
assertNotNull(ref1Weak.value)
|
|
||||||
assertNotNull(ref2Weak.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun collectCyclicGarbageWithExplicitDispose() {
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
|
|
||||||
|
|
||||||
callDispose(ref1Owner)
|
|
||||||
GC.collect()
|
|
||||||
|
|
||||||
assertNull(ref1Weak.value)
|
|
||||||
assertNull(ref2Weak.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createCrossThreadCyclicGarbage(
|
|
||||||
worker: Worker
|
|
||||||
): Triple<AtomicReference<DisposableWorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
|
||||||
val ref1 = DisposableWorkerBoundReference(B1())
|
|
||||||
val ref1Owner: AtomicReference<DisposableWorkerBoundReference<B1>?> = AtomicReference(ref1)
|
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
|
||||||
val ref2 = DisposableWorkerBoundReference(B2(ref1))
|
|
||||||
Pair(ref2, WeakReference(ref2.value))
|
|
||||||
}
|
|
||||||
val (ref2, ref2Weak) = future.result
|
|
||||||
|
|
||||||
ref1.value.b2 = ref2
|
|
||||||
|
|
||||||
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun doesNotCollectCrossThreadCyclicGarbage() {
|
|
||||||
val worker = Worker.start()
|
|
||||||
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbage(worker)
|
|
||||||
|
|
||||||
ref1Owner.value = null
|
|
||||||
GC.collect()
|
|
||||||
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
|
||||||
|
|
||||||
// If these asserts fail, that means DisposableWorkerBoundReference managed to clean up cyclic garbage all by itself.
|
|
||||||
assertNotNull(ref1Weak.value)
|
|
||||||
assertNotNull(ref2Weak.value)
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun collectCrossThreadCyclicGarbageWithExplicitDispose() {
|
|
||||||
val worker = Worker.start()
|
|
||||||
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbage(worker)
|
|
||||||
|
|
||||||
callDispose(ref1Owner)
|
|
||||||
GC.collect()
|
|
||||||
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
|
||||||
|
|
||||||
assertNull(ref1Weak.value)
|
|
||||||
assertNull(ref2Weak.value)
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun concurrentAccess() {
|
|
||||||
val workerCount = 10
|
|
||||||
val workerUnlocker = AtomicInt(0)
|
|
||||||
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, ref.value.a)
|
|
||||||
|
|
||||||
val workers = Array(workerCount) {
|
|
||||||
Worker.start()
|
|
||||||
}
|
|
||||||
val futures = Array(workers.size) {
|
|
||||||
workers[it].execute(TransferMode.SAFE, { Pair(ref, workerUnlocker) }) { (ref, workerUnlocker) ->
|
|
||||||
while (workerUnlocker.value < 1) {
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFailsWith<IncorrectDereferenceException> {
|
|
||||||
ref.value
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workerUnlocker.increment()
|
|
||||||
|
|
||||||
for (future in futures) {
|
|
||||||
future.result
|
|
||||||
}
|
|
||||||
|
|
||||||
for (worker in workers) {
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun concurrentDispose() {
|
|
||||||
val workerCount = 10
|
|
||||||
val workerUnlocker = AtomicInt(0)
|
|
||||||
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, ref.value.a)
|
|
||||||
|
|
||||||
val workers = Array(workerCount) {
|
|
||||||
Worker.start()
|
|
||||||
}
|
|
||||||
val futures = Array(workers.size) {
|
|
||||||
workers[it].execute(TransferMode.SAFE, { Pair(ref, workerUnlocker) }) { (ref, workerUnlocker) ->
|
|
||||||
while (workerUnlocker.value < 1) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workerUnlocker.increment()
|
|
||||||
|
|
||||||
for (future in futures) {
|
|
||||||
future.result
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
ref.value.a
|
|
||||||
}
|
|
||||||
|
|
||||||
for (worker in workers) {
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun concurrentDisposeAndAccess() {
|
|
||||||
val workerUnlocker = AtomicInt(0)
|
|
||||||
|
|
||||||
val ref = DisposableWorkerBoundReference(A(3))
|
|
||||||
assertEquals(3, ref.value.a)
|
|
||||||
|
|
||||||
val worker = Worker.start()
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(ref, workerUnlocker) }) { (ref, workerUnlocker) ->
|
|
||||||
while (workerUnlocker.value < 1) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.dispose()
|
|
||||||
}
|
|
||||||
workerUnlocker.increment()
|
|
||||||
|
|
||||||
var result = 0
|
|
||||||
// This is a race, but it should either get value successfully or get IllegalStateException.
|
|
||||||
// Any other kind of failure is unacceptable.
|
|
||||||
try {
|
|
||||||
result = ref.value.a
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
result = 3
|
|
||||||
}
|
|
||||||
assertEquals(3, result)
|
|
||||||
|
|
||||||
future.result
|
|
||||||
worker.requestTermination().result
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,33 @@
|
|||||||
#include "MemoryPrivate.hpp"
|
#include "MemoryPrivate.hpp"
|
||||||
#include "MemorySharedRefs.hpp"
|
#include "MemorySharedRefs.hpp"
|
||||||
#include "Runtime.h"
|
#include "Runtime.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
// Returns a string describing object at `address` of type `typeInfo`.
|
||||||
|
OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo, KConstNativePtr address);
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
inline bool isForeignRefAccessible(ObjHeader* object, ForeignRefContext context) {
|
||||||
|
if (!Kotlin_hasRuntime()) {
|
||||||
|
// So the object is either unowned or shared.
|
||||||
|
// In the former case initialized runtime is required to throw exceptions
|
||||||
|
// in the latter case -- to provide proper execution context for caller.
|
||||||
|
Kotlin_initRuntimeIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsForeignRefAccessible(object, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
RUNTIME_NORETURN inline void throwIllegalSharingException(ObjHeader* object) {
|
||||||
|
// TODO: add some info about the context.
|
||||||
|
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
|
||||||
|
ThrowIllegalObjectSharingException(object->type_info(), object);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void KRefSharedHolder::initLocal(ObjHeader* obj) {
|
void KRefSharedHolder::initLocal(ObjHeader* obj) {
|
||||||
RuntimeAssert(obj != nullptr, "must not be null");
|
RuntimeAssert(obj != nullptr, "must not be null");
|
||||||
@@ -21,24 +48,18 @@ void KRefSharedHolder::init(ObjHeader* obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjHeader* KRefSharedHolder::ref() const {
|
ObjHeader* KRefSharedHolder::ref() const {
|
||||||
ensureRefAccessible();
|
if (auto* result = refOrNull())
|
||||||
AdoptReferenceFromSharedVariable(obj_);
|
return result;
|
||||||
return obj_;
|
|
||||||
|
throwIllegalSharingException(obj_);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void ensureForeignRefAccessible(ObjHeader* object, ForeignRefContext context) {
|
ObjHeader* KRefSharedHolder::refOrNull() const {
|
||||||
if (!Kotlin_hasRuntime()) {
|
if (!isRefAccessible()) {
|
||||||
// So the object is either unowned or shared.
|
return nullptr;
|
||||||
// In the former case initialized runtime is required to throw the exception below,
|
|
||||||
// in the latter case -- to provide proper execution context for caller.
|
|
||||||
Kotlin_initRuntimeIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsForeignRefAccessible(object, context)) {
|
|
||||||
// TODO: add some info about the context.
|
|
||||||
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
|
|
||||||
ThrowIllegalObjectSharingException(object->type_info(), object);
|
|
||||||
}
|
}
|
||||||
|
AdoptReferenceFromSharedVariable(obj_);
|
||||||
|
return obj_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KRefSharedHolder::dispose() const {
|
void KRefSharedHolder::dispose() const {
|
||||||
@@ -50,8 +71,13 @@ void KRefSharedHolder::dispose() const {
|
|||||||
DeinitForeignRef(obj_, context_);
|
DeinitForeignRef(obj_, context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KRefSharedHolder::ensureRefAccessible() const {
|
OBJ_GETTER0(KRefSharedHolder::describe) const {
|
||||||
ensureForeignRefAccessible(obj_, context_);
|
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
|
||||||
|
RETURN_RESULT_OF(DescribeObjectForDebugging, obj_->type_info(), obj_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KRefSharedHolder::isRefAccessible() const {
|
||||||
|
return isForeignRefAccessible(obj_, context_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackRefFromAssociatedObject::initAndAddRef(ObjHeader* obj) {
|
void BackRefFromAssociatedObject::initAndAddRef(ObjHeader* obj) {
|
||||||
@@ -106,7 +132,9 @@ ObjHeader* BackRefFromAssociatedObject::ref() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BackRefFromAssociatedObject::ensureRefAccessible() const {
|
void BackRefFromAssociatedObject::ensureRefAccessible() const {
|
||||||
ensureForeignRefAccessible(obj_, context_);
|
if (!isForeignRefAccessible(obj_, context_)) {
|
||||||
|
throwIllegalSharingException(obj_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -125,4 +153,4 @@ RUNTIME_NOTHROW void KRefSharedHolder_dispose(const KRefSharedHolder* holder) {
|
|||||||
ObjHeader* KRefSharedHolder_ref(const KRefSharedHolder* holder) {
|
ObjHeader* KRefSharedHolder_ref(const KRefSharedHolder* holder) {
|
||||||
return holder->ref();
|
return holder->ref();
|
||||||
}
|
}
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#ifndef RUNTIME_MEMORYSHAREDREFS_HPP
|
#ifndef RUNTIME_MEMORYSHAREDREFS_HPP
|
||||||
#define RUNTIME_MEMORYSHAREDREFS_HPP
|
#define RUNTIME_MEMORYSHAREDREFS_HPP
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
|
||||||
class KRefSharedHolder {
|
class KRefSharedHolder {
|
||||||
@@ -15,16 +17,22 @@ class KRefSharedHolder {
|
|||||||
void init(ObjHeader* obj);
|
void init(ObjHeader* obj);
|
||||||
|
|
||||||
ObjHeader* ref() const;
|
ObjHeader* ref() const;
|
||||||
|
ObjHeader* refOrNull() const;
|
||||||
|
|
||||||
void dispose() const;
|
void dispose() const;
|
||||||
|
|
||||||
|
OBJ_GETTER0(describe) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ObjHeader* obj_;
|
ObjHeader* obj_;
|
||||||
ForeignRefContext context_;
|
ForeignRefContext context_;
|
||||||
|
|
||||||
void ensureRefAccessible() const;
|
bool isRefAccessible() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static_assert(std::is_trivially_destructible<KRefSharedHolder>::value,
|
||||||
|
"KRefSharedHolder destructor is not guaranteed to be called.");
|
||||||
|
|
||||||
class BackRefFromAssociatedObject {
|
class BackRefFromAssociatedObject {
|
||||||
public:
|
public:
|
||||||
void initAndAddRef(ObjHeader* obj);
|
void initAndAddRef(ObjHeader* obj);
|
||||||
@@ -49,4 +57,7 @@ class BackRefFromAssociatedObject {
|
|||||||
void ensureRefAccessible() const;
|
void ensureRefAccessible() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static_assert(std::is_trivially_destructible<BackRefFromAssociatedObject>::value,
|
||||||
|
"BackRefFromAssociatedObject destructor is not guaranteed to be called.");
|
||||||
|
|
||||||
#endif // RUNTIME_MEMORYSHAREDREFS_HPP
|
#endif // RUNTIME_MEMORYSHAREDREFS_HPP
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ KNativePtr Kotlin_WorkerBoundReference_create(KRef value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OBJ_GETTER(Kotlin_WorkerBoundReference_deref, KNativePtr holder) {
|
OBJ_GETTER(Kotlin_WorkerBoundReference_deref, KNativePtr holder) {
|
||||||
RETURN_OBJ(reinterpret_cast<KRefSharedHolder*>(holder)->ref());
|
RETURN_OBJ(reinterpret_cast<KRefSharedHolder*>(holder)->refOrNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER(Kotlin_WorkerBoundReference_describe, KNativePtr holder) {
|
||||||
|
RETURN_RESULT_OF0(reinterpret_cast<KRefSharedHolder*>(holder)->describe);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
|
import kotlin.native.internal.DescribeObjectForDebugging
|
||||||
import kotlin.native.internal.ExportForCppRuntime
|
import kotlin.native.internal.ExportForCppRuntime
|
||||||
|
import kotlin.native.internal.debugDescription
|
||||||
import kotlin.native.identityHashCode
|
import kotlin.native.identityHashCode
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
@@ -92,17 +94,9 @@ internal fun ThrowInvalidMutabilityException(where: Any): Nothing {
|
|||||||
|
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
internal fun ThrowIllegalObjectSharingException(typeInfo: NativePtr, address: NativePtr) {
|
internal fun ThrowIllegalObjectSharingException(typeInfo: NativePtr, address: NativePtr) {
|
||||||
val kClass = kotlin.native.internal.KClassImpl<Any>(typeInfo)
|
val description = DescribeObjectForDebugging(typeInfo, address)
|
||||||
val description = debugDescription(kClass, address.toLong().toInt())
|
|
||||||
throw IncorrectDereferenceException("illegal attempt to access non-shared $description from other thread")
|
throw IncorrectDereferenceException("illegal attempt to access non-shared $description from other thread")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun debugDescription(kClass: KClass<*>, identity: Int): String {
|
|
||||||
val className = kClass.qualifiedName ?: kClass.simpleName ?: "<object>"
|
|
||||||
val unsignedIdentity = identity.toLong() and 0xffffffffL
|
|
||||||
val identityStr = unsignedIdentity.toString(16)
|
|
||||||
return "$className@$identityStr"
|
|
||||||
}
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_AtomicReference_checkIfFrozen")
|
@SymbolName("Kotlin_AtomicReference_checkIfFrozen")
|
||||||
external internal fun checkIfFrozen(ref: Any?)
|
external internal fun checkIfFrozen(ref: Any?)
|
||||||
|
|||||||
@@ -11,16 +11,20 @@ import kotlin.native.internal.*
|
|||||||
external private fun createWorkerBoundReference(value: Any): NativePtr
|
external private fun createWorkerBoundReference(value: Any): NativePtr
|
||||||
|
|
||||||
@SymbolName("Kotlin_WorkerBoundReference_deref")
|
@SymbolName("Kotlin_WorkerBoundReference_deref")
|
||||||
external private fun derefWorkerBoundReference(ref: NativePtr): Any
|
external private fun derefWorkerBoundReference(ref: NativePtr): Any?
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_WorkerBoundReference_describe")
|
||||||
|
external private fun describeWorkerBoundReference(ref: NativePtr): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A frozen shared reference to a Kotlin object
|
* A frozen shared reference to a Kotlin object.
|
||||||
*
|
*
|
||||||
* Can be safely passed between workers, but [value] can only be accessed on the worker [WorkerBoundReference] was created on,
|
* Can be safely passed between workers, but [value] can only be accessed on the worker [WorkerBoundReference] was created on,
|
||||||
* unless the referred object is frozen too
|
* unless the referred object is frozen too.
|
||||||
*
|
*
|
||||||
* Note: Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] or [DisposableWorkerBoundReference] in them.
|
* Note: Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] in them.
|
||||||
* Consider using [DisposableWorkerBoundReference] to manually resolve cycles with explicit calls to [DisposableWorkerBoundReference.dispose]
|
* To resolve such cycles consider using [AtomicReference<WorkerBoundReference?>] which can be explicitly
|
||||||
|
* nulled out.
|
||||||
*/
|
*/
|
||||||
@Frozen
|
@Frozen
|
||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
@@ -28,44 +32,21 @@ external private fun derefWorkerBoundReference(ref: NativePtr): Any
|
|||||||
public class WorkerBoundReference<out T : Any>(value: T) {
|
public class WorkerBoundReference<out T : Any>(value: T) {
|
||||||
|
|
||||||
private val ptr = createWorkerBoundReference(value)
|
private val ptr = createWorkerBoundReference(value)
|
||||||
|
private val ownerName = Worker.current.name
|
||||||
|
|
||||||
|
private val valueDescription
|
||||||
|
get() = describeWorkerBoundReference(ptr)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The referenced value.
|
* The referenced value.
|
||||||
* @throws IncorrectDereferenceException if referred object is not frozen and current worker is different from the one created [this]
|
* @throws IncorrectDereferenceException if referred object is not frozen and current worker is different from the one created [this].
|
||||||
*/
|
*/
|
||||||
val value: T
|
val value: T
|
||||||
get() = @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T)
|
get() = valueOrNull ?: throw IncorrectDereferenceException("illegal attempt to access non-shared $valueDescription bound to `$ownerName` from `${Worker.current.name}`")
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A frozen shared reference to a Kotlin object
|
|
||||||
*
|
|
||||||
* Can be safely passed between workers, but [value] can only be accessed on the worker [DisposableWorkerBoundReference] was created on,
|
|
||||||
* unless the referred object is frozen too
|
|
||||||
* Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] or [DisposableWorkerBoundReference] in them.
|
|
||||||
* Call [dispose] manually to resolve cycles
|
|
||||||
*
|
|
||||||
* Note: This class has more expensive [value] getter than [WorkerBoundReference]. If you don't have reference
|
|
||||||
* cycles with [WorkerBoundReference] or [DisposableWorkerBoundReference], consider using [WorkerBoundReference]
|
|
||||||
*/
|
|
||||||
@Frozen
|
|
||||||
public class DisposableWorkerBoundReference<out T : Any>(value: T) {
|
|
||||||
|
|
||||||
private val ref: AtomicReference<WorkerBoundReference<T>?> = AtomicReference(WorkerBoundReference(value))
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Free the reference. Any call to [DisposableWorkerBoundReference.value] after that will
|
* The referenced value or null if referred object is not frozen and current worker is different from the one created [this].
|
||||||
* fail with [IllegalStateException]
|
|
||||||
*/
|
*/
|
||||||
fun dispose() {
|
val valueOrNull: T?
|
||||||
ref.value = null
|
get() = @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T?)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The referenced value.
|
|
||||||
* @throws IncorrectDereferenceException if referred object is not frozen and current worker is different from the one created [this]
|
|
||||||
* @throws IllegalStateException if [DisposableWorkerBoundReference.dispose] was called on this reference.
|
|
||||||
*/
|
|
||||||
val value: T
|
|
||||||
get() = ref.value?.value ?: throw IllegalStateException("illegal attempt to dereference disposed $this")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.native.internal
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun DescribeObjectForDebugging(typeInfo: NativePtr, address: NativePtr): String {
|
||||||
|
val kClass = kotlin.native.internal.KClassImpl<Any>(typeInfo)
|
||||||
|
return debugDescription(kClass, address.toLong().toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun debugDescription(kClass: KClass<*>, identity: Int): String {
|
||||||
|
val className = kClass.qualifiedName ?: kClass.simpleName ?: "<object>"
|
||||||
|
val unsignedIdentity = identity.toLong() and 0xffffffffL
|
||||||
|
val identityStr = unsignedIdentity.toString(16)
|
||||||
|
return "$className@$identityStr"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user