Make WorkerBoundReference initially unfrozen (#4288)
This commit is contained in:
committed by
GitHub
parent
34c48f69e3
commit
d3d8c8040d
@@ -14,6 +14,7 @@ import kotlin.text.Regex
|
|||||||
|
|
||||||
class A(var a: Int)
|
class A(var a: Int)
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global1: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global1: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -32,6 +33,7 @@ fun testGlobal() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global2: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global2: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -52,6 +54,7 @@ fun testGlobalDenyAccessOnWorker() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global3: WorkerBoundReference<A> = WorkerBoundReference(A(3).freeze())
|
val global3: WorkerBoundReference<A> = WorkerBoundReference(A(3).freeze())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -69,6 +72,7 @@ fun testGlobalAccessOnWorkerFrozenInitially() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global4: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global4: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -86,6 +90,7 @@ fun testGlobalAccessOnWorkerFrozenBeforePassing() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global5: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global5: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -113,6 +118,7 @@ fun testGlobalAccessOnWorkerFrozenBeforeAccess() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global6: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global6: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -140,6 +146,7 @@ fun testGlobalModification() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SharedImmutable
|
||||||
val global7: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
val global7: WorkerBoundReference<A> = WorkerBoundReference(A(3))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -162,6 +169,13 @@ 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)
|
assertEquals(3, local.valueOrNull?.a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalFrozen() {
|
||||||
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
|
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 ->
|
||||||
@@ -175,8 +189,8 @@ fun testLocal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalDenyAccessOnWorker() {
|
fun testLocalDenyAccessOnWorkerFrozen() {
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
@@ -193,8 +207,8 @@ fun testLocalDenyAccessOnWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalAccessOnWorkerFrozenInitially() {
|
fun testLocalAccessOnWorkerFrozenInitiallyFrozen() {
|
||||||
val local = WorkerBoundReference(A(3).freeze())
|
val local = WorkerBoundReference(A(3).freeze()).freeze()
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
assertEquals(3, local.valueOrNull?.a)
|
assertEquals(3, local.valueOrNull?.a)
|
||||||
|
|
||||||
@@ -209,8 +223,8 @@ fun testLocalAccessOnWorkerFrozenInitially() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalAccessOnWorkerFrozenBeforePassing() {
|
fun testLocalAccessOnWorkerFrozenBeforePassingFrozen() {
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
local.value.freeze()
|
local.value.freeze()
|
||||||
|
|
||||||
@@ -225,10 +239,10 @@ fun testLocalAccessOnWorkerFrozenBeforePassing() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalAccessOnWorkerFrozenBeforeAccess() {
|
fun testLocalAccessOnWorkerFrozenBeforeAccessFrozen() {
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
val semaphore: AtomicInt = AtomicInt(0)
|
||||||
|
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
@@ -250,14 +264,25 @@ fun testLocalAccessOnWorkerFrozenBeforeAccess() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalDenyAccessOnMainThread() {
|
fun testLocalAccessOnMainThread() {
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
WorkerBoundReference(A(3))
|
WorkerBoundReference(A(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertEquals(3, future.result.value.a)
|
||||||
|
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalDenyAccessOnMainThreadFrozen() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
|
WorkerBoundReference(A(3)).freeze()
|
||||||
|
}
|
||||||
|
|
||||||
val value = future.result
|
val value = future.result
|
||||||
assertFailsWith<IncorrectDereferenceException> {
|
assertFailsWith<IncorrectDereferenceException> {
|
||||||
value.value
|
value.value
|
||||||
@@ -268,10 +293,10 @@ fun testLocalDenyAccessOnMainThread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalModification() {
|
fun testLocalModificationFrozen() {
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
val semaphore: AtomicInt = AtomicInt(0)
|
||||||
|
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
assertEquals(3, local.value.a)
|
assertEquals(3, local.value.a)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
@@ -294,8 +319,8 @@ fun testLocalModification() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLocalGetWorker() {
|
fun testLocalGetWorkerFrozen() {
|
||||||
val local = WorkerBoundReference(A(3))
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
|
|
||||||
val ownerId = Worker.current.id
|
val ownerId = Worker.current.id
|
||||||
assertEquals(ownerId, local.worker.id)
|
assertEquals(ownerId, local.worker.id)
|
||||||
@@ -326,9 +351,60 @@ fun testLocalForeignGetWorker() {
|
|||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOwnerAndWeaks(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
|
@Test
|
||||||
|
fun testLocalForeignGetWorkerFrozen() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
val ownerId = worker.id
|
||||||
|
val future = worker.execute(TransferMode.SAFE, { ownerId }) { ownerId ->
|
||||||
|
val local = WorkerBoundReference(A(3)).freeze()
|
||||||
|
assertEquals(ownerId, local.worker.id)
|
||||||
|
local
|
||||||
|
}
|
||||||
|
|
||||||
|
val value = future.result
|
||||||
|
assertEquals(ownerId, value.worker.id)
|
||||||
|
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wrapper(val ref: WorkerBoundReference<A>)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalWithWrapperFrozen() {
|
||||||
|
val local = Wrapper(WorkerBoundReference(A(3))).freeze()
|
||||||
|
assertEquals(3, local.ref.value.a)
|
||||||
|
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
||||||
|
local
|
||||||
|
}
|
||||||
|
|
||||||
|
val value = future.result
|
||||||
|
assertEquals(3, value.ref.value.a)
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalDenyAccessWithWrapperFrozen() {
|
||||||
|
val local = Wrapper(WorkerBoundReference(A(3))).freeze()
|
||||||
|
assertEquals(3, local.ref.value.a)
|
||||||
|
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, { local }) { local ->
|
||||||
|
assertFailsWith<IncorrectDereferenceException> {
|
||||||
|
local.ref.value
|
||||||
|
}
|
||||||
|
assertEquals(null, local.ref.valueOrNull)
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
future.result
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOwnerAndWeaks(initial: Int): Triple<FreezableAtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
|
||||||
val ref = WorkerBoundReference(A(initial))
|
val ref = WorkerBoundReference(A(initial))
|
||||||
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
|
val refOwner: FreezableAtomicReference<WorkerBoundReference<A>?> = FreezableAtomicReference(ref)
|
||||||
val refWeak = WeakReference(ref)
|
val refWeak = WeakReference(ref)
|
||||||
val refValueWeak = WeakReference(ref.value)
|
val refValueWeak = WeakReference(ref.value)
|
||||||
|
|
||||||
@@ -347,8 +423,29 @@ fun testCollect() {
|
|||||||
assertNull(refValueWeak.value)
|
assertNull(refValueWeak.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collectInWorker(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
|
fun getOwnerAndWeaksFrozen(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
|
||||||
val (refOwner, _, refValueWeak) = getOwnerAndWeaks(3)
|
val ref = WorkerBoundReference(A(initial)).freeze()
|
||||||
|
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
|
||||||
|
val refWeak = WeakReference(ref)
|
||||||
|
val refValueWeak = WeakReference(ref.value)
|
||||||
|
|
||||||
|
return Triple(refOwner, refWeak, refValueWeak)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCollectFrozen() {
|
||||||
|
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaksFrozen(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 collectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
|
||||||
|
val (refOwner, _, refValueWeak) = getOwnerAndWeaksFrozen(3)
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
|
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
|
||||||
semaphore.increment()
|
semaphore.increment()
|
||||||
@@ -370,25 +467,24 @@ fun collectInWorker(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCollectInWorker() {
|
fun testCollectInWorkerFrozen() {
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
val semaphore: AtomicInt = AtomicInt(0)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
|
|
||||||
val (refValueWeak, future) = collectInWorker(worker, semaphore)
|
val (refValueWeak, future) = collectInWorkerFrozen(worker, semaphore)
|
||||||
semaphore.increment()
|
semaphore.increment()
|
||||||
future.result
|
future.result
|
||||||
|
|
||||||
// At this point WorkerBoundReference no longer has a reference, so it's referent is destroyed.
|
// At this point WorkerBoundReference no longer has a reference, so it's referent is destroyed.
|
||||||
// WorkerBoundReference, so referent is kept alive.
|
|
||||||
GC.collect()
|
GC.collect()
|
||||||
assertNull(refValueWeak.value)
|
assertNull(refValueWeak.value)
|
||||||
|
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
|
fun doNotCollectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
|
||||||
val ref = WorkerBoundReference(A(3))
|
val ref = WorkerBoundReference(A(3)).freeze()
|
||||||
|
|
||||||
return worker.execute(TransferMode.SAFE, { Pair(ref, semaphore) }) { (ref, semaphore) ->
|
return worker.execute(TransferMode.SAFE, { Pair(ref, semaphore) }) { (ref, semaphore) ->
|
||||||
semaphore.increment()
|
semaphore.increment()
|
||||||
@@ -401,12 +497,12 @@ fun doNotCollectInWorker(worker: Worker, semaphore: AtomicInt): Future<WorkerBou
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDoNotCollectInWorker() {
|
fun testDoNotCollectInWorkerFrozen() {
|
||||||
val semaphore: AtomicInt = AtomicInt(0)
|
val semaphore: AtomicInt = AtomicInt(0)
|
||||||
|
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
|
|
||||||
val future = doNotCollectInWorker(worker, semaphore)
|
val future = doNotCollectInWorkerFrozen(worker, semaphore)
|
||||||
while (semaphore.value < 1) {
|
while (semaphore.value < 1) {
|
||||||
}
|
}
|
||||||
GC.collect()
|
GC.collect()
|
||||||
@@ -423,9 +519,9 @@ class B1 {
|
|||||||
|
|
||||||
data class B2(val b1: WorkerBoundReference<B1>)
|
data class B2(val b1: WorkerBoundReference<B1>)
|
||||||
|
|
||||||
fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
fun createCyclicGarbage(): Triple<FreezableAtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
||||||
val ref1 = WorkerBoundReference(B1())
|
val ref1 = WorkerBoundReference(B1())
|
||||||
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
val ref1Owner: FreezableAtomicReference<WorkerBoundReference<B1>?> = FreezableAtomicReference(ref1)
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
val ref2 = WorkerBoundReference(B2(ref1))
|
val ref2 = WorkerBoundReference(B2(ref1))
|
||||||
@@ -437,26 +533,50 @@ fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, We
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doesNotCollectCyclicGarbage() {
|
fun collectCyclicGarbage() {
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
|
||||||
|
|
||||||
ref1Owner.value = null
|
ref1Owner.value = null
|
||||||
GC.collect()
|
GC.collect()
|
||||||
|
|
||||||
|
assertNull(ref1Weak.value)
|
||||||
|
assertNull(ref2Weak.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCyclicGarbageFrozen(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
||||||
|
val ref1 = WorkerBoundReference(B1()).freeze()
|
||||||
|
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
||||||
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
|
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
|
||||||
|
val ref2Weak = WeakReference(ref2.value)
|
||||||
|
|
||||||
|
ref1.value.b2 = ref2
|
||||||
|
|
||||||
|
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNotCollectCyclicGarbageFrozen() {
|
||||||
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageFrozen()
|
||||||
|
|
||||||
|
ref1Owner.value = null
|
||||||
|
GC.collect()
|
||||||
|
|
||||||
// If these asserts fail, that means WorkerBoundReference managed to clean up cyclic garbage all by itself.
|
// If these asserts fail, that means WorkerBoundReference managed to clean up cyclic garbage all by itself.
|
||||||
assertNotNull(ref1Weak.value)
|
assertNotNull(ref1Weak.value)
|
||||||
assertNotNull(ref2Weak.value)
|
assertNotNull(ref2Weak.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCrossThreadCyclicGarbage(
|
fun createCrossThreadCyclicGarbageFrozen(
|
||||||
worker: Worker
|
worker: Worker
|
||||||
): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
||||||
val ref1 = WorkerBoundReference(B1())
|
val ref1 = WorkerBoundReference(B1()).freeze()
|
||||||
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
||||||
val ref2 = WorkerBoundReference(B2(ref1))
|
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
|
||||||
Pair(ref2, WeakReference(ref2.value))
|
Pair(ref2, WeakReference(ref2.value))
|
||||||
}
|
}
|
||||||
val (ref2, ref2Weak) = future.result
|
val (ref2, ref2Weak) = future.result
|
||||||
@@ -467,10 +587,10 @@ fun createCrossThreadCyclicGarbage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doesNotCollectCrossThreadCyclicGarbage() {
|
fun doesNotCollectCrossThreadCyclicGarbageFrozen() {
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbage(worker)
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageFrozen(worker)
|
||||||
|
|
||||||
ref1Owner.value = null
|
ref1Owner.value = null
|
||||||
GC.collect()
|
GC.collect()
|
||||||
@@ -493,11 +613,11 @@ class C1 {
|
|||||||
|
|
||||||
data class C2(val c1: AtomicReference<WorkerBoundReference<C1>>)
|
data class C2(val c1: AtomicReference<WorkerBoundReference<C1>>)
|
||||||
|
|
||||||
fun createCyclicGarbageWithAtomics(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
fun createCyclicGarbageWithAtomicsFrozen(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
||||||
val ref1 = WorkerBoundReference(C1())
|
val ref1 = WorkerBoundReference(C1()).freeze()
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
|
||||||
val ref2Weak = WeakReference(ref2.value)
|
val ref2Weak = WeakReference(ref2.value)
|
||||||
|
|
||||||
ref1.value.c2 = AtomicReference(ref2)
|
ref1.value.c2 = AtomicReference(ref2)
|
||||||
@@ -511,8 +631,8 @@ fun dispose(refOwner: AtomicReference<WorkerBoundReference<C1>?>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doesNotCollectCyclicGarbageWithAtomics() {
|
fun doesNotCollectCyclicGarbageWithAtomicsFrozen() {
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
|
||||||
|
|
||||||
ref1Owner.value = null
|
ref1Owner.value = null
|
||||||
GC.collect()
|
GC.collect()
|
||||||
@@ -523,8 +643,8 @@ fun doesNotCollectCyclicGarbageWithAtomics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun collectCyclicGarbageWithAtomics() {
|
fun collectCyclicGarbageWithAtomicsFrozen() {
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomics()
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
|
||||||
|
|
||||||
dispose(ref1Owner)
|
dispose(ref1Owner)
|
||||||
GC.collect()
|
GC.collect()
|
||||||
@@ -533,14 +653,14 @@ fun collectCyclicGarbageWithAtomics() {
|
|||||||
assertNull(ref2Weak.value)
|
assertNull(ref2Weak.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCrossThreadCyclicGarbageWithAtomics(
|
fun createCrossThreadCyclicGarbageWithAtomicsFrozen(
|
||||||
worker: Worker
|
worker: Worker
|
||||||
): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
||||||
val ref1 = WorkerBoundReference(C1())
|
val ref1 = WorkerBoundReference(C1()).freeze()
|
||||||
val ref1Weak = WeakReference(ref1.value)
|
val ref1Weak = WeakReference(ref1.value)
|
||||||
|
|
||||||
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
||||||
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1)))
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
|
||||||
Pair(ref2, WeakReference(ref2.value))
|
Pair(ref2, WeakReference(ref2.value))
|
||||||
}
|
}
|
||||||
val (ref2, ref2Weak) = future.result
|
val (ref2, ref2Weak) = future.result
|
||||||
@@ -551,10 +671,10 @@ fun createCrossThreadCyclicGarbageWithAtomics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doesNotCollectCrossThreadCyclicGarbageWithAtomics() {
|
fun doesNotCollectCrossThreadCyclicGarbageWithAtomicsFrozen() {
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
|
||||||
|
|
||||||
ref1Owner.value = null
|
ref1Owner.value = null
|
||||||
GC.collect()
|
GC.collect()
|
||||||
@@ -568,10 +688,10 @@ fun doesNotCollectCrossThreadCyclicGarbageWithAtomics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun collectCrossThreadCyclicGarbageWithAtomics() {
|
fun collectCrossThreadCyclicGarbageWithAtomicsFrozen() {
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
|
|
||||||
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomics(worker)
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
|
||||||
|
|
||||||
dispose(ref1Owner)
|
dispose(ref1Owner)
|
||||||
// This marks C2 as gone on the main thread
|
// This marks C2 as gone on the main thread
|
||||||
@@ -588,11 +708,11 @@ fun collectCrossThreadCyclicGarbageWithAtomics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun concurrentAccess() {
|
fun concurrentAccessFrozen() {
|
||||||
val workerCount = 10
|
val workerCount = 10
|
||||||
val workerUnlocker = AtomicInt(0)
|
val workerUnlocker = AtomicInt(0)
|
||||||
|
|
||||||
val ref = WorkerBoundReference(A(3))
|
val ref = WorkerBoundReference(A(3)).freeze()
|
||||||
assertEquals(3, ref.value.a)
|
assertEquals(3, ref.value.a)
|
||||||
|
|
||||||
val workers = Array(workerCount) {
|
val workers = Array(workerCount) {
|
||||||
@@ -621,10 +741,10 @@ fun concurrentAccess() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExceptionMessage() {
|
fun testExceptionMessageFrozen() {
|
||||||
val worker = Worker.start()
|
val worker = Worker.start()
|
||||||
val future = worker.execute(TransferMode.SAFE, {}) {
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
WorkerBoundReference(A(3))
|
WorkerBoundReference(A(3)).freeze()
|
||||||
}
|
}
|
||||||
val value = future.result
|
val value = future.result
|
||||||
|
|
||||||
@@ -638,3 +758,23 @@ fun testExceptionMessage() {
|
|||||||
|
|
||||||
worker.requestTermination().result
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDoubleFreeze() {
|
||||||
|
val ref = WorkerBoundReference(A(3))
|
||||||
|
val wrapper = Wrapper(ref)
|
||||||
|
ref.freeze()
|
||||||
|
ref.freeze()
|
||||||
|
wrapper.freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDoubleFreezeWithFreezeBlocker() {
|
||||||
|
val ref = WorkerBoundReference(A(3))
|
||||||
|
val wrapper = Wrapper(ref)
|
||||||
|
wrapper.ensureNeverFrozen()
|
||||||
|
assertFailsWith<FreezingException> {
|
||||||
|
wrapper.freeze()
|
||||||
|
}
|
||||||
|
ref.freeze()
|
||||||
|
}
|
||||||
|
|||||||
@@ -748,17 +748,23 @@ inline void traverseReferredObjects(ObjHeader* obj, func process) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename func>
|
template <typename func>
|
||||||
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
inline void traverseContainerObjects(ContainerHeader* container, func process) {
|
||||||
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers");
|
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers");
|
||||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
|
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
|
||||||
|
for (int i = 0; i < container->objectCount(); ++i) {
|
||||||
for (int object = 0; object < container->objectCount(); object++) {
|
process(obj);
|
||||||
traverseObjectFields(obj, process);
|
|
||||||
obj = reinterpret_cast<ObjHeader*>(
|
obj = reinterpret_cast<ObjHeader*>(
|
||||||
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename func>
|
||||||
|
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
||||||
|
traverseContainerObjects(container, [process](ObjHeader* obj) {
|
||||||
|
traverseObjectFields(obj, process);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
template <typename func>
|
template <typename func>
|
||||||
inline void traverseContainerReferredObjects(ContainerHeader* container, func process) {
|
inline void traverseContainerReferredObjects(ContainerHeader* container, func process) {
|
||||||
traverseContainerObjectFields(container, [process](ObjHeader** location) {
|
traverseContainerObjectFields(container, [process](ObjHeader** location) {
|
||||||
@@ -2582,6 +2588,34 @@ void freezeCyclic(ObjHeader* root,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These hooks are only allowed to modify `obj` subgraph.
|
||||||
|
void runFreezeHooks(ObjHeader* obj) {
|
||||||
|
if (obj->type_info() == theWorkerBoundReferenceTypeInfo) {
|
||||||
|
WorkerBoundReferenceFreezeHook(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void runFreezeHooksRecursive(ObjHeader* root) {
|
||||||
|
KStdUnorderedSet<KRef> seen;
|
||||||
|
KStdVector<KRef> toVisit;
|
||||||
|
seen.insert(root);
|
||||||
|
toVisit.push_back(root);
|
||||||
|
while (!toVisit.empty()) {
|
||||||
|
KRef obj = toVisit.back();
|
||||||
|
toVisit.pop_back();
|
||||||
|
|
||||||
|
runFreezeHooks(obj);
|
||||||
|
|
||||||
|
traverseReferredObjects(obj, [&seen, &toVisit](ObjHeader* field) {
|
||||||
|
auto wasNotSeenYet = seen.insert(field).second;
|
||||||
|
// Only iterating on unseen objects which containers will get frozen by freezeCyclic or freezeAcyclic.
|
||||||
|
if (wasNotSeenYet && canFreeze(field->container())) {
|
||||||
|
toVisit.push_back(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theory of operations.
|
* Theory of operations.
|
||||||
*
|
*
|
||||||
@@ -2612,6 +2646,12 @@ void freezeSubgraph(ObjHeader* root) {
|
|||||||
ContainerHeader* rootContainer = root->container();
|
ContainerHeader* rootContainer = root->container();
|
||||||
if (isPermanentOrFrozen(rootContainer)) return;
|
if (isPermanentOrFrozen(rootContainer)) return;
|
||||||
|
|
||||||
|
MEMORY_LOG("Run freeze hooks on subgraph of %p\n", root);
|
||||||
|
|
||||||
|
// Note: Actual freezing can fail, but these hooks won't be undone, and moreover
|
||||||
|
// these hooks will run again on a repeated freezing attempt.
|
||||||
|
runFreezeHooksRecursive(root);
|
||||||
|
|
||||||
MEMORY_LOG("Freeze subgraph of %p\n", root)
|
MEMORY_LOG("Freeze subgraph of %p\n", root)
|
||||||
|
|
||||||
#if USE_GC
|
#if USE_GC
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ WorkerBoundReference* asWorkerBoundReference(KRef thiz) {
|
|||||||
|
|
||||||
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) {
|
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) {
|
||||||
// DisposeSharedRef is only called when all references to thiz are gone.
|
// DisposeSharedRef is only called when all references to thiz are gone.
|
||||||
auto* holder = asWorkerBoundReference(thiz)->holder;
|
// Can be null if WorkerBoundReference wasn't frozen.
|
||||||
holder->dispose();
|
if (auto* holder = asWorkerBoundReference(thiz)->holder) {
|
||||||
konanDestructInstance(holder);
|
holder->dispose();
|
||||||
|
konanDestructInstance(holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined in WorkerBoundReference.kt
|
||||||
|
extern "C" void Kotlin_WorkerBoundReference_freezeHook(KRef thiz);
|
||||||
|
|
||||||
|
RUNTIME_NOTHROW void WorkerBoundReferenceFreezeHook(KRef thiz) {
|
||||||
|
Kotlin_WorkerBoundReference_freezeHook(thiz);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
@@ -13,4 +13,6 @@
|
|||||||
// all types.
|
// all types.
|
||||||
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) NO_INLINE;
|
RUNTIME_NOTHROW void DisposeWorkerBoundReference(KRef thiz) NO_INLINE;
|
||||||
|
|
||||||
|
RUNTIME_NOTHROW void WorkerBoundReferenceFreezeHook(KRef thiz);
|
||||||
|
|
||||||
#endif // RUNTIME_SHAREDREF_H
|
#endif // RUNTIME_SHAREDREF_H
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ external private fun derefWorkerBoundReference(ref: NativePtr): Any?
|
|||||||
external private fun describeWorkerBoundReference(ref: NativePtr): String
|
external private fun describeWorkerBoundReference(ref: NativePtr): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A frozen shared reference to a Kotlin object.
|
* A shared reference to a Kotlin object that doesn't freeze the referred object when it gets frozen itself.
|
||||||
*
|
*
|
||||||
* Can be safely passed between workers, but [value] can only be accessed on the worker [WorkerBoundReference] was created on,
|
* After freezing can be safely passed between workers, but [value] can only be accessed on
|
||||||
* unless the referred object is frozen too.
|
* the worker [WorkerBoundReference] was created on, unless the referred object is frozen too.
|
||||||
*
|
*
|
||||||
* Note: Garbage collector currently cannot free any reference cycles with [WorkerBoundReference] in them.
|
* Note: Garbage collector currently cannot free any reference cycles with frozen [WorkerBoundReference] in them.
|
||||||
* To resolve such cycles consider using [AtomicReference<WorkerBoundReference?>] which can be explicitly
|
* To resolve such cycles consider using [AtomicReference<WorkerBoundReference?>] which can be explicitly
|
||||||
* nulled out.
|
* nulled out.
|
||||||
*/
|
*/
|
||||||
@Frozen
|
|
||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
|
@ExportTypeInfo("theWorkerBoundReferenceTypeInfo")
|
||||||
public class WorkerBoundReference<out T : Any>(value: T) {
|
public class WorkerBoundReference<out T : Any>(value: T) {
|
||||||
|
|
||||||
private val ptr = createWorkerBoundReference(value)
|
private var ptr = NativePtr.NULL
|
||||||
private val ownerName = Worker.current.name
|
private val ownerName = Worker.current.name
|
||||||
|
private var valueBeforeFreezing: T? = value
|
||||||
|
|
||||||
private val valueDescription
|
private val valueDescription
|
||||||
get() = describeWorkerBoundReference(ptr)
|
get() = describeWorkerBoundReference(ptr)
|
||||||
@@ -48,10 +48,19 @@ public class WorkerBoundReference<out T : Any>(value: T) {
|
|||||||
* The referenced value or null if referred object is not frozen and current worker is different from the one created [this].
|
* The referenced value or null if referred object is not frozen and current worker is different from the one created [this].
|
||||||
*/
|
*/
|
||||||
val valueOrNull: T?
|
val valueOrNull: T?
|
||||||
get() = @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T?)
|
get() = valueBeforeFreezing ?: @Suppress("UNCHECKED_CAST") (derefWorkerBoundReference(ptr) as T?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worker that [value] is bound to.
|
* Worker that [value] is bound to.
|
||||||
*/
|
*/
|
||||||
val worker: Worker = Worker.current
|
val worker: Worker = Worker.current
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_WorkerBoundReference_freezeHook")
|
||||||
|
private fun freezeHook() {
|
||||||
|
// If this hook was already run, do nothing.
|
||||||
|
if (valueBeforeFreezing == null)
|
||||||
|
return
|
||||||
|
ptr = createWorkerBoundReference(valueBeforeFreezing!!)
|
||||||
|
valueBeforeFreezing = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user