Implement WorkerBoundReference (#4044)

This commit is contained in:
Alexander Shabalin
2020-04-13 15:45:03 +03:00
committed by GitHub
parent b2c2da72e8
commit 51bb86c4d7
8 changed files with 1290 additions and 2 deletions
+10
View File
@@ -873,6 +873,16 @@ task empty_substring(type: KonanLocalTest) {
source = "runtime/basic/empty_substring.kt"
}
task worker_bound_reference0(type: KonanLocalTest) {
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
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) {
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
goldValue = "Got Input processed\nOK\n"
@@ -0,0 +1,455 @@
/*
* 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_reference0
import kotlin.test.*
import kotlin.native.concurrent.*
import kotlin.native.internal.GC
import kotlin.native.ref.WeakReference
class A(var a: Int)
val global1: WorkerBoundReference<A> = WorkerBoundReference(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: WorkerBoundReference<A> = WorkerBoundReference(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: WorkerBoundReference<A> = WorkerBoundReference(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: WorkerBoundReference<A> = WorkerBoundReference(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: WorkerBoundReference<A> = WorkerBoundReference(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: WorkerBoundReference<A> = WorkerBoundReference(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
}
@Test
fun testLocal() {
val local = WorkerBoundReference(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 = WorkerBoundReference(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 = WorkerBoundReference(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 = WorkerBoundReference(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 = WorkerBoundReference(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, {}) {
WorkerBoundReference(A(3))
}
val value = future.result
assertFailsWith<IncorrectDereferenceException> {
value.value
}
worker.requestTermination().result
}
@Test
fun testLocalModification() {
val semaphore: AtomicInt = AtomicInt(0)
val local = WorkerBoundReference(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
}
fun getOwnerAndWeaks(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
val ref = WorkerBoundReference(A(initial))
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
val refWeak = WeakReference(ref)
val refValueWeak = WeakReference(ref.value)
return Triple(refOwner, refWeak, refValueWeak)
}
@Test
fun testCollect() {
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaks(3)
refOwner.value = null
GC.collect()
// Last reference to WorkerBoundReference is gone, so it and it's referent are destroyed.
assertNull(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
// WorkerBoundReference, 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 WorkerBoundReference no longer has a reference, so it's referent is destroyed.
// WorkerBoundReference, so referent is kept alive.
GC.collect()
assertNull(refValueWeak.value)
worker.requestTermination().result
}
fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
val ref = WorkerBoundReference(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
}
class B1 {
lateinit var b2: WorkerBoundReference<B2>
}
data class B2(val b1: WorkerBoundReference<B1>)
fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
val ref1 = WorkerBoundReference(B1())
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val ref2 = WorkerBoundReference(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 WorkerBoundReference managed to clean up cyclic garbage all by itself.
assertNotNull(ref1Weak.value)
assertNotNull(ref2Weak.value)
}
fun createCrossThreadCyclicGarbage(
worker: Worker
): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
val ref1 = WorkerBoundReference(B1())
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
val ref1Weak = WeakReference(ref1.value)
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
val ref2 = WorkerBoundReference(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 WorkerBoundReference managed to clean up cyclic garbage all by itself.
assertNotNull(ref1Weak.value)
assertNotNull(ref2Weak.value)
worker.requestTermination().result
}
@Test
fun concurrentAccess() {
val workerCount = 10
val workerUnlocker = AtomicInt(0)
val ref = WorkerBoundReference(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
}
}
@@ -0,0 +1,684 @@
/*
* 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
}
+9 -2
View File
@@ -35,6 +35,7 @@
#include "Natives.h"
#include "Porting.h"
#include "Runtime.h"
#include "WorkerBoundReference.h"
// If garbage collection algorithm for cyclic garbage to be used.
// We are using the Bacon's algorithm for GC, see
@@ -928,11 +929,17 @@ void freeAggregatingFrozenContainer(ContainerHeader* container) {
MEMORY_LOG("Freeing subcontainers done\n");
}
void runDeallocationHooks(ContainerHeader* container) {
// This is called from 2 places where it's unconditionally called,
// so better be inlined.
ALWAYS_INLINE void runDeallocationHooks(ContainerHeader* container) {
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
for (int index = 0; index < container->objectCount(); index++) {
auto* type_info = obj->type_info();
if (type_info == theWorkerBoundReferenceTypeInfo) {
DisposeWorkerBoundReference(obj);
}
#if USE_CYCLIC_GC
if ((obj->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
if ((type_info->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
cyclicRemoveAtomicRoot(obj);
}
#endif // USE_CYCLIC_GC
+1
View File
@@ -102,6 +102,7 @@ extern const TypeInfo* theShortArrayTypeInfo;
extern const TypeInfo* theStringTypeInfo;
extern const TypeInfo* theThrowableTypeInfo;
extern const TypeInfo* theUnitTypeInfo;
extern const TypeInfo* theWorkerBoundReferenceTypeInfo;
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
@@ -0,0 +1,44 @@
/*
* 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.
*/
#include "WorkerBoundReference.h"
#include "Alloc.h"
#include "Memory.h"
#include "MemorySharedRefs.hpp"
namespace {
struct WorkerBoundReference {
ObjHeader header;
KRefSharedHolder* holder;
};
WorkerBoundReference* asWorkerBoundReference(KRef thiz) {
return reinterpret_cast<WorkerBoundReference*>(thiz);
}
} // namespace
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) {
// DisposeSharedRef is only called when all references to thiz are gone.
auto* holder = asWorkerBoundReference(thiz)->holder;
holder->dispose();
konanDestructInstance(holder);
}
extern "C" {
KNativePtr Kotlin_WorkerBoundReference_create(KRef value) {
auto* holder = konanConstructInstance<KRefSharedHolder>();
holder->init(value);
return holder;
}
OBJ_GETTER(Kotlin_WorkerBoundReference_deref, KNativePtr holder) {
RETURN_OBJ(reinterpret_cast<KRefSharedHolder*>(holder)->ref());
}
}
@@ -0,0 +1,16 @@
/*
* 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.
*/
#ifndef RUNTIME_SHAREDREF_H
#define RUNTIME_SHAREDREF_H
#include "Common.h"
#include "Types.h"
// Not inlining this call as it affects deallocation performance for
// all types.
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) NO_INLINE;
#endif // RUNTIME_SHAREDREF_H
@@ -0,0 +1,71 @@
/*
* 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 kotlin.native.concurrent
import kotlin.native.internal.*
@SymbolName("Kotlin_WorkerBoundReference_create")
external private fun createWorkerBoundReference(value: Any): NativePtr
@SymbolName("Kotlin_WorkerBoundReference_deref")
external private fun derefWorkerBoundReference(ref: NativePtr): Any
/**
* 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,
* unless the referred object is frozen too
*
* Note: Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] or [DisposableWorkerBoundReference] in them.
* Consider using [DisposableWorkerBoundReference] to manually resolve cycles with explicit calls to [DisposableWorkerBoundReference.dispose]
*/
@Frozen
@NoReorderFields
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
public class WorkerBoundReference<out T : Any>(value: T) {
private val ptr = createWorkerBoundReference(value)
/**
* The referenced value.
* @throws IncorrectDereferenceException if referred object is not frozen and current worker is different from the one created [this]
*/
val value: T
get() = @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T)
}
/**
* 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
* fail with [IllegalStateException]
*/
fun dispose() {
ref.value = null
}
/**
* 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")
}